# Aztec Protocol Documentation > Build private smart contracts on Ethereum's leading privacy-first L2 zkRollup. - [Aztec Protocol Documentation](/index.md) ## developers - [AI Tooling](/developers/testnet/ai_tooling.md): Set up AI coding tools like Claude Code, Cursor, and Codex for Aztec and Noir development. - [Aztec.js](/developers/testnet/docs/aztec-js.md): Complete guide to Aztec.js library for managing accounts and interacting with contracts on the Aztec network, including installation, importing, and core workflow functions. - [Reference](/developers/testnet/docs/aztec-js/aztec_js_reference.md): Comprehensive auto-generated reference for the Aztec.js TypeScript library with all classes, interfaces, types, and functions. - [Connect to Local Network](/developers/testnet/docs/aztec-js/how_to_connect_to_local_network.md): Connect your application to the Aztec local network and interact with accounts. - [Creating Accounts](/developers/testnet/docs/aztec-js/how_to_create_account.md): Step-by-step guide to creating and deploying new user accounts in Aztec.js applications. - [Deploying Contracts](/developers/testnet/docs/aztec-js/how_to_deploy_contract.md): Deploy smart contracts to Aztec using generated TypeScript classes. - [Paying Fees](/developers/testnet/docs/aztec-js/how_to_pay_fees.md): Pay transaction fees on Aztec, understand mana costs, estimate gas, and retrieve fees from receipts. - [Reading Contract Data](/developers/testnet/docs/aztec-js/how_to_read_data.md): How to read data from contracts including simulating functions, reading logs, and retrieving events. - [Sending Transactions](/developers/testnet/docs/aztec-js/how_to_send_transaction.md): Send transactions to Aztec contracts using Aztec.js with various options and error handling - [Simulate without signing prompts](/developers/testnet/docs/aztec-js/how_to_simulate_without_signing.md): How to call .simulate() on a view function or estimate gas without prompting the user to sign authentication witnesses. - [Testing Smart Contracts](/developers/testnet/docs/aztec-js/how_to_test.md): Learn how to write and run tests for your Aztec smart contracts using Aztec.js and a local network. - [Using Authentication Witnesses](/developers/testnet/docs/aztec-js/how_to_use_authwit.md): Step-by-step guide to implementing authentication witnesses in Aztec.js for delegated transactions. - [Pay Fees Privately](/developers/testnet/docs/aztec-js/how_to_use_private_fee_juice.md): Learn how private fee payment works on Aztec and walk through an example using a community-built fully private Fee Payment Contract. - [TypeScript API Reference](/developers/testnet/docs/aztec-js/typescript_api_reference.md): API reference documentation for Aztec TypeScript packages including aztec.js, accounts, PXE, and core libraries. - [Overview](/developers/testnet/docs/aztec-nr.md): Comprehensive guide to writing smart contracts for the Aztec network using Noir. - [Aztec.nr API Reference](/developers/testnet/docs/aztec-nr/api.md): Auto-generated API reference documentation for the Aztec.nr smart contract framework. - [Compiling Contracts](/developers/testnet/docs/aztec-nr/compiling_contracts.md): Compile your Aztec smart contracts into deployable artifacts using aztec command. - [Contract Deployment Reference](/developers/testnet/docs/aztec-nr/contract_readiness_states.md): A practical guide to determine which deployment steps your Aztec contract needs and when functions become callable. - [Debugging Aztec Code](/developers/testnet/docs/aztec-nr/debugging.md): This guide shows you how to debug issues in your Aztec contracts. - [Profiling Transactions](/developers/testnet/docs/aztec-nr/framework-description/advanced/how_to_profile_transactions.md): How to profile Aztec transactions and identify performance bottlenecks using aztec profile, aztec-wallet, and aztec.js. - [Proving historic state](/developers/testnet/docs/aztec-nr/framework-description/advanced/how_to_prove_history.md): Prove historical notes, nullifiers, contract deployment, and public storage in your Aztec smart contracts. - [Retrieving and Filtering Notes](/developers/testnet/docs/aztec-nr/framework-description/advanced/how_to_retrieve_filter_notes.md): Step-by-step guide to retrieving, filtering, and sorting notes from private storage in Aztec contracts. - [Using Capsules](/developers/testnet/docs/aztec-nr/framework-description/advanced/how_to_use_capsules.md): Learn how to use capsules for per-contract non-volatile storage in the PXE. - [Partial notes](/developers/testnet/docs/aztec-nr/framework-description/advanced/partial_notes.md): How partial notes work, how they are completed, and how they enable use cases like AMM swaps and payment endpoints. - [Partial notes as payment endpoints](/developers/testnet/docs/aztec-nr/framework-description/advanced/partial_notes_as_payment_endpoints.md): Using partial notes as a recipient's offer to be paid, enabling naming-service style flows with no recipient action at payment time. - [Oracle Functions](/developers/testnet/docs/aztec-nr/framework-description/advanced/protocol_oracles.md): Learn about oracles in Aztec, which provide external data to smart contracts during execution. - [Writing Efficient Contracts](/developers/testnet/docs/aztec-nr/framework-description/advanced/writing_efficient_contracts.md): Best practices and techniques for writing gas-efficient contracts on Aztec, optimizing for both proving and execution costs. - [Authentication Witnesses](/developers/testnet/docs/aztec-nr/framework-description/authentication_witnesses.md): Enable contracts to execute actions on behalf of user accounts using authentication witnesses. - [Calling Other Contracts](/developers/testnet/docs/aztec-nr/framework-description/calling_contracts.md): Call functions in other contracts from your Aztec smart contracts to enable composability. - [Contract Artifacts](/developers/testnet/docs/aztec-nr/framework-description/contract_artifact.md): Understand the structure and contents of Aztec smart contract artifacts. - [Contract Structure](/developers/testnet/docs/aztec-nr/framework-description/contract_structure.md): Learn the fundamental structure of Aztec smart contracts including the contract keyword, directory layout, and how contracts manage state and functions. - [Contract Upgrades](/developers/testnet/docs/aztec-nr/framework-description/contract_upgrades.md): Understand contract upgrade patterns in Aztec and how to implement upgradeable contracts. - [Custom notes](/developers/testnet/docs/aztec-nr/framework-description/custom_notes.md): Learn how to create and use custom note types for specialized private data storage in Aztec contracts - [Data Packing and Serialization](/developers/testnet/docs/aztec-nr/framework-description/data_packing.md): Understand Serialize, Deserialize, and Packable traits, when each is used, how to write custom packing, and the cost implications. - [Aztec.nr Dependencies](/developers/testnet/docs/aztec-nr/framework-description/dependencies.md): Reference list of available Aztec.nr libraries and their Nargo.toml dependency paths. - [Ethereum<>Aztec Messaging](/developers/testnet/docs/aztec-nr/framework-description/ethereum_aztec_messaging.md): Send messages and data between L1 and L2 contracts using portal contracts and cross-chain messaging. - [Events and Logs](/developers/testnet/docs/aztec-nr/framework-description/events_and_logs.md): Learn how to emit events from your Aztec smart contracts for offchain applications to consume. - [Defining Functions](/developers/testnet/docs/aztec-nr/framework-description/functions.md): Overview of Aztec contract functions, including private, public, and utility function types. - [Attributes and Macros](/developers/testnet/docs/aztec-nr/framework-description/functions/attributes.md): Reference for Aztec contract attributes that control function visibility, execution context, storage, and notes. - [Understanding Function Context](/developers/testnet/docs/aztec-nr/framework-description/functions/context.md): Learn about the execution context available to Aztec contract functions, including caller information and block data. - [Inner Workings of Functions](/developers/testnet/docs/aztec-nr/framework-description/functions/function_transforms.md): Understand how Aztec transforms contract functions during compilation for privacy and efficiency. - [How to Define Functions](/developers/testnet/docs/aztec-nr/framework-description/functions/how_to_define_functions.md): Define different types of functions in your Aztec contracts for private, public, and utility execution. - [Visibility](/developers/testnet/docs/aztec-nr/framework-description/functions/visibility.md): Understand function visibility modifiers in Aztec and how they affect function execution and accessibility. - [Global Variables](/developers/testnet/docs/aztec-nr/framework-description/globals.md): Access chain ID, block number, timestamps, and gas information in your Aztec contracts - [Immutables via Salt](/developers/testnet/docs/aztec-nr/framework-description/immutables.md): Commit immutable values into a contract's address salt, eliminating initialization transactions. - [Aztec Macros](/developers/testnet/docs/aztec-nr/framework-description/macros.md): Learn about macros available in Aztec.nr for code generation and abstraction. - [Note Delivery](/developers/testnet/docs/aztec-nr/framework-description/note_delivery.md): Learn how to deliver notes to recipients in Aztec smart contracts using different delivery modes to balance proving time, transaction costs, and delivery guarantees. - [State Variables](/developers/testnet/docs/aztec-nr/framework-description/state_variables.md): Define and manage storage state in your Aztec smart contracts using various storage types. - [Noir VSCode Extension](/developers/testnet/docs/aztec-nr/installation.md): Learn how to install and configure the Noir Language Server for a better development experience. - [Logging from Contracts](/developers/testnet/docs/aztec-nr/logging.md): Add log statements to your Aztec contracts and control log verbosity in tests and local networks. - [Aztec Contract Standards](/developers/testnet/docs/aztec-nr/standards.md): Overview of Aztec Improvement Proposal (AIP) contract standards maintained by DeFi Wonderland. - [AIP-20: Fungible Token](/developers/testnet/docs/aztec-nr/standards/aip-20.md): Fungible token standard with private balances, partial-note transfers, and recursive note consumption. - [AIP-4626: Tokenized Vault](/developers/testnet/docs/aztec-nr/standards/aip-4626.md): Yield-bearing vault standard with share conversion across private and public contexts. - [AIP-721: Non-Fungible Token](/developers/testnet/docs/aztec-nr/standards/aip-721.md): Non-fungible token standard with private ownership, partial-note support, and commitment-based transfers. - [Dripper (Development Faucet)](/developers/testnet/docs/aztec-nr/standards/dripper.md): Convenience faucet for minting tokens into private or public balances during development. - [Escrow](/developers/testnet/docs/aztec-nr/standards/escrow.md): Minimal token and NFT custody contract with salt-based authorization. - [Generic Proxy](/developers/testnet/docs/aztec-nr/standards/generic-proxy.md): Forwarding layer for account abstraction that routes calls by argument count. - [Testing Contracts](/developers/testnet/docs/aztec-nr/testing_contracts.md): Write and run tests for your Aztec smart contracts using Noir's TestEnvironment. - [Aztec CLI Reference](/developers/testnet/docs/cli/aztec_cli_reference.md): Comprehensive auto-generated reference for the Aztec CLI with all commands and options. - [Aztec Up CLI Reference](/developers/testnet/docs/cli/aztec_up_cli_reference.md): Comprehensive auto-generated reference for the Aztec Version Manager with all commands and options. - [Aztec Wallet CLI Reference](/developers/testnet/docs/cli/aztec_wallet_cli_reference.md): Comprehensive auto-generated reference for the Aztec Wallet CLI with all commands and options. - [Aztec Overview](/developers/testnet/docs/foundational-topics.md): Overview of Aztec, a privacy-first Layer 2 on Ethereum supporting smart contracts with private and public state and execution. - [Understanding Accounts in Aztec](/developers/testnet/docs/foundational-topics/accounts.md): Deep dive into Aztec's native account abstraction system - understanding how smart contract accounts work, their architecture, key management, and authorization mechanisms in a privacy-preserving blockchain. - [Keys](/developers/testnet/docs/foundational-topics/accounts/keys.md): Understand the specialized key pairs used in Aztec accounts - nullifier keys, incoming viewing keys, and signing keys - and how they enable privacy, security, and flexible authentication. - [Authentication Witness (Authwit)](/developers/testnet/docs/foundational-topics/advanced/authwit.md): Learn about Aztec's Authentication Witness scheme that enables secure third-party actions on behalf of users, providing a privacy-preserving alternative to traditional token approvals. - [Circuits](/developers/testnet/docs/foundational-topics/advanced/circuits.md): Explore Aztec's core protocol circuits that enforce privacy rules and transaction validity through zero-knowledge proofs, enabling private state and function execution. - [AVM Cryptographic Compatibility](/developers/testnet/docs/foundational-topics/advanced/circuits/avm_compatibility.md): Which Noir cryptographic primitives work in public (AVM) functions vs private, and workarounds for unsupported operations. - [Private Kernel Circuit](/developers/testnet/docs/foundational-topics/advanced/circuits/private_kernel.md): Learn about the Private Kernel Circuit, the only zero-knowledge circuit in Aztec that handles private data and ensures transaction privacy by executing on user devices. - [Public Execution (AVM)](/developers/testnet/docs/foundational-topics/advanced/circuits/public_execution.md): Learn how the Aztec Virtual Machine (AVM) executes public functions and manages public state transitions. - [Rollup Circuits](/developers/testnet/docs/foundational-topics/advanced/circuits/rollup_circuits.md): Learn how Rollup Circuits compress transactions into a single proof using a hierarchical tree topology for efficient verification on Ethereum. - [Indexed Merkle Tree (Nullifier Tree)](/developers/testnet/docs/foundational-topics/advanced/storage/indexed_merkle_tree.md): Learn about indexed merkle trees, an efficient data structure for nullifier trees that enables fast non-membership proofs and batch insertions in Aztec. - [Note Discovery](/developers/testnet/docs/foundational-topics/advanced/storage/note_discovery.md): Understand how Aztec's note tagging system allows users to efficiently discover and decrypt notes that belong to them without relying on brute force or offchain communication. - [Storage Slots](/developers/testnet/docs/foundational-topics/advanced/storage/storage_slots.md): Understand how storage slots work in Aztec for both public and private state, including siloing mechanisms and note hash commitments. - [Call Types](/developers/testnet/docs/foundational-topics/call_types.md): Understand the different types of contract calls in Aztec, including private and public execution modes, and how they compare to Ethereum's call types. - [Contract Deployment](/developers/testnet/docs/foundational-topics/contract_creation.md): Learn how contract classes and instances are created and deployed on the Aztec network. - [L1-L2 Communication (Portals)](/developers/testnet/docs/foundational-topics/ethereum-aztec-messaging.md): A conceptual introduction to Portals and how Aztec communicates with L1 (Ethereum) - [Data Structures](/developers/testnet/docs/foundational-topics/ethereum-aztec-messaging/data_structures.md): Learn about the data structures used in Aztec portals for L1-L2 communication. - [Inbox](/developers/testnet/docs/foundational-topics/ethereum-aztec-messaging/inbox.md): Learn about the inbox mechanism in Aztec portals for receiving messages from L1. - [Outbox](/developers/testnet/docs/foundational-topics/ethereum-aztec-messaging/outbox.md): Learn about the outbox mechanism in Aztec portals for sending messages to L1. - [Registry](/developers/testnet/docs/foundational-topics/ethereum-aztec-messaging/registry.md): Learn about the portal registry and how it manages L1-L2 contract mappings. - [Fees](/developers/testnet/docs/foundational-topics/fees.md): Understand Aztec's fee system including mana-based transaction pricing, Aztec token and Fee Juice payments, and how L1 and L2 costs are transparently calculated for users. - [Private Execution Environment (PXE)](/developers/testnet/docs/foundational-topics/pxe.md): Explore the PXE, a client-side library that handles private function execution, proof generation, secret management, and transaction orchestration in Aztec. - [Execution hooks](/developers/testnet/docs/foundational-topics/pxe/execution_hooks.md): How wallets use PXE execution hooks to apply custom policies during client-side simulation. - [Kernelless simulations](/developers/testnet/docs/foundational-topics/pxe/kernelless_simulations.md): How the PXE simulates transactions without running the private kernel circuits, why it is the default for .simulate(), and what it skips. - [State Management](/developers/testnet/docs/foundational-topics/state_management.md): How public and private state work in Aztec, including storage slots, notes, and the UTXO model - [Transactions](/developers/testnet/docs/foundational-topics/transactions.md): Comprehensive guide to the Aztec transaction lifecycle, covering private execution, PXE interactions, kernel circuits, and the step-by-step process from user request to L1 settlement. - [Wallets](/developers/testnet/docs/foundational-topics/wallets.md): Overview of wallet responsibilities in Aztec including account management, private state tracking, transaction execution, key management, and authorization handling. - [Community Calls](/developers/testnet/docs/resources/community_calls.md): Join live calls to connect with the Aztec team and other builders. - [Limitations](/developers/testnet/docs/resources/considerations/limitations.md): Understand the current limitations of the Aztec network and its implications for developers. - [Privacy Considerations](/developers/testnet/docs/resources/considerations/privacy_considerations.md): Learn about key privacy considerations when building applications on Aztec. - [Glossary](/developers/testnet/docs/resources/glossary.md): Comprehensive glossary of terms used throughout the Aztec documentation and protocol. - [Migration notes](/developers/testnet/docs/resources/migration_notes.md): Read about migration notes from previous versions, which could solve problems while updating - [Video lessons](/developers/testnet/docs/resources/video_lessons.md): Learn Aztec through short video explainers covering what Aztec is, private and public state, private composability, and getting started. - [Counter contract](/developers/testnet/docs/tutorials/contract_tutorials/counter_contract.md): Code-along tutorial for creating a simple counter contract on Aztec. - [Verify Noir Proofs in Aztec Contracts](/developers/testnet/docs/tutorials/contract_tutorials/recursive_verification.md): Learn to generate offchain ZK proofs with Noir and verify them onchain in Aztec private smart contracts - [Private Token Contract](/developers/testnet/docs/tutorials/contract_tutorials/token_contract.md): Build a privacy-preserving token for employee mental health benefits that keeps spending habits confidential. - [Deposit to Aave from Aztec](/developers/testnet/docs/tutorials/js_tutorials/aave_bridge.md): Build a cross-chain DeFi integration that deposits tokens into Aave from Aztec L2 and claims yield back. - [Deploying a Token Contract](/developers/testnet/docs/tutorials/js_tutorials/aztecjs-getting-started.md): A tutorial going through how to deploy a token contract to the local network using typescript. - [Bridge Your NFT to Aztec](/developers/testnet/docs/tutorials/js_tutorials/token_bridge.md): Build a private NFT bridge between Ethereum and Aztec using custom notes, PrivateSet, and cross-chain messaging portals. - [Cross-Chain Token Swap (L1 <> L2)](/developers/testnet/docs/tutorials/js_tutorials/uniswap_swap.md): Build a cross-chain token swap that exits L2, swaps on L1, and deposits the output back to L2 using Aztec's messaging protocol. - [Building a Wallet Extension for Aztec](/developers/testnet/docs/tutorials/js_tutorials/wallet-extension.md): Learn how to build a Chrome extension wallet for Aztec with encrypted key storage, SponsoredFPC fee payment, and transaction approval flows - [Account Management](/developers/testnet/docs/tutorials/js_tutorials/wallet-extension/accounts.md): Key derivation, encrypted storage, and SchnorrAccountContract for Aztec wallet accounts - [Approval UI](/developers/testnet/docs/tutorials/js_tutorials/wallet-extension/approval-ui.md): Building React popups for connection and transaction approval in Aztec wallet extensions - [Extension Architecture](/developers/testnet/docs/tutorials/js_tutorials/wallet-extension/architecture.md): Understanding Chrome extension architecture for Aztec wallets - service workers, offscreen documents, and message passing - [PXE Integration](/developers/testnet/docs/tutorials/js_tutorials/wallet-extension/pxe-integration.md): Running the Private eXecution Environment in a browser extension and extending BaseWallet - [Testing the Wallet Extension](/developers/testnet/docs/tutorials/js_tutorials/wallet-extension/testing.md): Loading the wallet extension in Chrome and testing with the Pod Racing dApp - [Transaction Handling](/developers/testnet/docs/tutorials/js_tutorials/wallet-extension/transactions.md): The sendTx flow, proof generation, and SponsoredFPC fee payment in Aztec wallet extensions - [Wallet Protocol](/developers/testnet/docs/tutorials/js_tutorials/wallet-extension/wallet-protocol.md): Implementing the Aztec wallet SDK protocol - discovery, ECDH key exchange, and secure messaging - [Building a Webapp on Aztec](/developers/testnet/docs/tutorials/js_tutorials/webapp.md): Build a Pod Racing game webapp with Vite, React, and Aztec — featuring private state, wallet connections, and zero-knowledge proofs. - [Contract Interaction & Gameplay](/developers/testnet/docs/tutorials/js_tutorials/webapp/contract-interaction.md): Deploy and call the Pod Racing contract from the webapp, handle game lobby and private gameplay - [Network & Wallet](/developers/testnet/docs/tutorials/js_tutorials/webapp/network-and-wallet.md): Connect to Aztec using an embedded wallet for local dev or the wallet SDK for browser extensions - [Project Setup](/developers/testnet/docs/tutorials/js_tutorials/webapp/project-setup.md): Understand the webapp project structure, Vite configuration, and environment setup - [Putting It Together](/developers/testnet/docs/tutorials/js_tutorials/webapp/putting-it-together.md): Wire all components into a complete Pod Racing webapp and run it - [The Contract](/developers/testnet/docs/tutorials/js_tutorials/webapp/the-contract.md): Walk through the Pod Racing smart contract, compile it, and deploy and interact with it via a TypeScript script - [Transactions & Fees](/developers/testnet/docs/tutorials/js_tutorials/webapp/transactions-and-fees.md): Learn about the Aztec transaction lifecycle and fee payment with SponsoredFPC - [Wallet SDK](/developers/testnet/docs/tutorials/js_tutorials/webapp/wallet-sdk.md): Aztec wallet SDK — secure wallet discovery, encrypted messaging, and capability-based permissions for dApps and wallet extensions - [dApp Integration](/developers/testnet/docs/tutorials/js_tutorials/webapp/wallet-sdk/dapp-integration.md): Connect your dApp to Aztec wallet extensions — discovery, secure channels, capabilities, and wallet usage - [Wallet Extension Integration](/developers/testnet/docs/tutorials/js_tutorials/webapp/wallet-sdk/wallet-integration.md): Build an Aztec wallet extension — handle discovery, manage sessions, route messages, and extend BaseWallet - [Testing Governance Rollup Upgrade on Local Network](/developers/testnet/docs/tutorials/testing_governance_rollup_upgrade.md): Deploy a new rollup and execute a governance upgrade on a local Aztec network for testing. - [Getting Started on Local Network](/developers/testnet/getting_started_on_local_network.md): Guide for developers to get started with the Aztec local network, including account creation and contract deployment. - [Getting Started on Testnet](/developers/testnet/getting_started_on_testnet.md): Deploy contracts and send transactions on the Aztec testnet using the CLI wallet and the Sponsored FPC for fee payment. - [Aztec Overview](/developers/testnet/overview.md): Overview of Aztec, a privacy-first Layer 2 on Ethereum supporting smart contracts with private and public state and execution. - [Support](/developers/testnet/support.md): Where to ask questions, report bugs, request features, and disclose security issues for the Aztec stack. - [AI Tooling](/developers/ai_tooling.md): Set up AI coding tools like Claude Code, Cursor, and Codex for Aztec and Noir development. - [Aztec.js](/developers/docs/aztec-js.md): Complete guide to Aztec.js library for managing accounts and interacting with contracts on the Aztec network, including installation, importing, and core workflow functions. - [Reference](/developers/docs/aztec-js/aztec_js_reference.md): Comprehensive auto-generated reference for the Aztec.js TypeScript library with all classes, interfaces, types, and functions. - [Connect to Local Network](/developers/docs/aztec-js/how_to_connect_to_local_network.md): Connect your application to the Aztec local network and interact with accounts. - [Creating Accounts](/developers/docs/aztec-js/how_to_create_account.md): Step-by-step guide to creating and deploying new user accounts in Aztec.js applications. - [Deploying Contracts](/developers/docs/aztec-js/how_to_deploy_contract.md): Deploy smart contracts to Aztec using generated TypeScript classes. - [Paying Fees](/developers/docs/aztec-js/how_to_pay_fees.md): Pay transaction fees on Aztec, understand mana costs, estimate gas, and retrieve fees from receipts. - [Reading Contract Data](/developers/docs/aztec-js/how_to_read_data.md): How to read data from contracts including simulating functions, reading logs, and retrieving events. - [Sending Transactions](/developers/docs/aztec-js/how_to_send_transaction.md): Send transactions to Aztec contracts using Aztec.js with various options and error handling - [Testing Smart Contracts](/developers/docs/aztec-js/how_to_test.md): Learn how to write and run tests for your Aztec smart contracts using Aztec.js and a local network. - [Using Authentication Witnesses](/developers/docs/aztec-js/how_to_use_authwit.md): Step-by-step guide to implementing authentication witnesses in Aztec.js for delegated transactions. - [Pay Fees Privately](/developers/docs/aztec-js/how_to_use_private_fee_juice.md): Learn how private fee payment works on Aztec and walk through an example using a community-built fully private Fee Payment Contract. - [TypeScript API Reference](/developers/docs/aztec-js/typescript_api_reference.md): API reference documentation for Aztec TypeScript packages including aztec.js, accounts, PXE, and core libraries. - [Wallet SDK](/developers/docs/aztec-js/wallet-sdk.md): How dApps and wallet extensions communicate on Aztec, covering discovery, encrypted channels, capability permissions, and BaseWallet. - [Connecting a dApp to a wallet](/developers/docs/aztec-js/wallet-sdk/dapp_integration.md): Discover wallet extensions, establish an encrypted channel, request capabilities, and use the wallet from a dApp on Aztec. - [Building a wallet extension](/developers/docs/aztec-js/wallet-sdk/wallet_integration.md): Implement the Aztec wallet SDK protocol in a browser extension. Covers discovery, sessions, message routing, and BaseWallet. - [Overview](/developers/docs/aztec-nr.md): Comprehensive guide to writing smart contracts for the Aztec network using Noir. - [Aztec.nr API Reference](/developers/docs/aztec-nr/api.md): Auto-generated API reference documentation for the Aztec.nr smart contract framework. - [Compiling Contracts](/developers/docs/aztec-nr/compiling_contracts.md): Compile your Aztec smart contracts into deployable artifacts using aztec command. - [Contract Deployment Reference](/developers/docs/aztec-nr/contract_readiness_states.md): A practical guide to determine which deployment steps your Aztec contract needs and when functions become callable. - [Debugging Aztec Code](/developers/docs/aztec-nr/debugging.md): This guide shows you how to debug issues in your Aztec contracts. - [Profiling Transactions](/developers/docs/aztec-nr/framework-description/advanced/how_to_profile_transactions.md): How to profile Aztec transactions and identify performance bottlenecks using aztec profile, aztec-wallet, and aztec.js. - [Proving Historic State](/developers/docs/aztec-nr/framework-description/advanced/how_to_prove_history.md): Prove historical state and note inclusion in your Aztec smart contracts using the Archive tree. - [Retrieving and Filtering Notes](/developers/docs/aztec-nr/framework-description/advanced/how_to_retrieve_filter_notes.md): Step-by-step guide to retrieving, filtering, and sorting notes from private storage in Aztec contracts. - [Using Capsules](/developers/docs/aztec-nr/framework-description/advanced/how_to_use_capsules.md): Learn how to use capsules for per-contract non-volatile storage in the PXE. - [Partial Notes](/developers/docs/aztec-nr/framework-description/advanced/partial_notes.md): How partial notes work and how they can be used. - [Oracle Functions](/developers/docs/aztec-nr/framework-description/advanced/protocol_oracles.md): Learn about oracles in Aztec, which provide external data to smart contracts during execution. - [Writing Efficient Contracts](/developers/docs/aztec-nr/framework-description/advanced/writing_efficient_contracts.md): Best practices and techniques for writing gas-efficient contracts on Aztec, optimizing for both proving and execution costs. - [Authentication Witnesses](/developers/docs/aztec-nr/framework-description/authentication_witnesses.md): Enable contracts to execute actions on behalf of user accounts using authentication witnesses. - [Calling Other Contracts](/developers/docs/aztec-nr/framework-description/calling_contracts.md): Call functions in other contracts from your Aztec smart contracts to enable composability. - [Contract Artifacts](/developers/docs/aztec-nr/framework-description/contract_artifact.md): Understand the structure and contents of Aztec smart contract artifacts. - [Contract Structure](/developers/docs/aztec-nr/framework-description/contract_structure.md): Learn the fundamental structure of Aztec smart contracts including the contract keyword, directory layout, and how contracts manage state and functions. - [Contract Upgrades](/developers/docs/aztec-nr/framework-description/contract_upgrades.md): Understand contract upgrade patterns in Aztec and how to implement upgradeable contracts. - [Custom notes](/developers/docs/aztec-nr/framework-description/custom_notes.md): Learn how to create and use custom note types for specialized private data storage in Aztec contracts - [Aztec.nr Dependencies](/developers/docs/aztec-nr/framework-description/dependencies.md): Reference list of available Aztec.nr libraries and their Nargo.toml dependency paths. - [Ethereum<>Aztec Messaging](/developers/docs/aztec-nr/framework-description/ethereum_aztec_messaging.md): Send messages and data between L1 and L2 contracts using portal contracts and cross-chain messaging. - [Events and Logs](/developers/docs/aztec-nr/framework-description/events_and_logs.md): Learn how to emit events from your Aztec smart contracts for offchain applications to consume. - [Defining Functions](/developers/docs/aztec-nr/framework-description/functions.md): Overview of Aztec contract functions, including private, public, and utility function types. - [Attributes and Macros](/developers/docs/aztec-nr/framework-description/functions/attributes.md): Reference for Aztec contract attributes that control function visibility, execution context, storage, and notes. - [Understanding Function Context](/developers/docs/aztec-nr/framework-description/functions/context.md): Learn about the execution context available to Aztec contract functions, including caller information and block data. - [Inner Workings of Functions](/developers/docs/aztec-nr/framework-description/functions/function_transforms.md): Understand how Aztec transforms contract functions during compilation for privacy and efficiency. - [How to Define Functions](/developers/docs/aztec-nr/framework-description/functions/how_to_define_functions.md): Define different types of functions in your Aztec contracts for private, public, and utility execution. - [Visibility](/developers/docs/aztec-nr/framework-description/functions/visibility.md): Understand function visibility modifiers in Aztec and how they affect function execution and accessibility. - [Global Variables](/developers/docs/aztec-nr/framework-description/globals.md): Access chain ID, block number, timestamps, and gas information in your Aztec contracts - [Immutables via Salt](/developers/docs/aztec-nr/framework-description/immutables.md): Commit immutable values into a contract's address salt, eliminating initialization transactions. - [Aztec Macros](/developers/docs/aztec-nr/framework-description/macros.md): Learn about macros available in Aztec.nr for code generation and abstraction. - [Note Delivery](/developers/docs/aztec-nr/framework-description/note_delivery.md): Learn how to deliver notes to recipients in Aztec smart contracts using different delivery modes to balance proving time, transaction costs, and delivery guarantees. - [State Variables](/developers/docs/aztec-nr/framework-description/state_variables.md): Define and manage storage state in your Aztec smart contracts using various storage types. - [Noir VSCode Extension](/developers/docs/aztec-nr/installation.md): Learn how to install and configure the Noir Language Server for a better development experience. - [Aztec Contract Standards](/developers/docs/aztec-nr/standards.md): Overview of Aztec Improvement Proposal (AIP) contract standards maintained by DeFi Wonderland. - [AIP-20: Fungible Token](/developers/docs/aztec-nr/standards/aip-20.md): Fungible token standard with private balances, partial-note transfers, and recursive note consumption. - [AIP-4626: Tokenized Vault](/developers/docs/aztec-nr/standards/aip-4626.md): Yield-bearing vault standard with share conversion across private and public contexts. - [AIP-721: Non-Fungible Token](/developers/docs/aztec-nr/standards/aip-721.md): Non-fungible token standard with private ownership, partial-note support, and commitment-based transfers. - [Dripper (Development Faucet)](/developers/docs/aztec-nr/standards/dripper.md): Convenience faucet for minting tokens into private or public balances during development. - [Escrow](/developers/docs/aztec-nr/standards/escrow.md): Minimal token and NFT custody contract with salt-based authorization. - [Generic Proxy](/developers/docs/aztec-nr/standards/generic-proxy.md): Forwarding layer for account abstraction that routes calls by argument count. - [Testing Contracts](/developers/docs/aztec-nr/testing_contracts.md): Write and run tests for your Aztec smart contracts using Noir's TestEnvironment. - [Aztec CLI Reference](/developers/docs/cli/aztec_cli_reference.md): Comprehensive auto-generated reference for the Aztec CLI with all commands and options. - [Aztec Up CLI Reference](/developers/docs/cli/aztec_up_cli_reference.md): Comprehensive auto-generated reference for the Aztec Version Manager with all commands and options. - [Aztec Wallet CLI Reference](/developers/docs/cli/aztec_wallet_cli_reference.md): Comprehensive auto-generated reference for the Aztec Wallet CLI with all commands and options. - [Aztec Overview](/developers/docs/foundational-topics.md): Overview of Aztec, a privacy-first Layer 2 on Ethereum supporting smart contracts with private and public state and execution. - [Understanding Accounts in Aztec](/developers/docs/foundational-topics/accounts.md): Deep dive into Aztec's native account abstraction system - understanding how smart contract accounts work, their architecture, key management, and authorization mechanisms in a privacy-preserving blockchain. - [Keys](/developers/docs/foundational-topics/accounts/keys.md): Understand the specialized key pairs used in Aztec accounts - nullifier keys, incoming viewing keys, and signing keys - and how they enable privacy, security, and flexible authentication. - [Authentication Witness (Authwit)](/developers/docs/foundational-topics/advanced/authwit.md): Learn about Aztec's Authentication Witness scheme that enables secure third-party actions on behalf of users, providing a privacy-preserving alternative to traditional token approvals. - [Circuits](/developers/docs/foundational-topics/advanced/circuits.md): Explore Aztec's core protocol circuits that enforce privacy rules and transaction validity through zero-knowledge proofs, enabling private state and function execution. - [AVM Cryptographic Compatibility](/developers/docs/foundational-topics/advanced/circuits/avm_compatibility.md): Which Noir cryptographic primitives work in public (AVM) functions vs private, and workarounds for unsupported operations. - [Private Kernel Circuit](/developers/docs/foundational-topics/advanced/circuits/private_kernel.md): Learn about the Private Kernel Circuit, the only zero-knowledge circuit in Aztec that handles private data and ensures transaction privacy by executing on user devices. - [Public Execution (AVM)](/developers/docs/foundational-topics/advanced/circuits/public_execution.md): Learn how the Aztec Virtual Machine (AVM) executes public functions and manages public state transitions. - [Rollup Circuits](/developers/docs/foundational-topics/advanced/circuits/rollup_circuits.md): Learn how Rollup Circuits compress transactions into a single proof using a hierarchical tree topology for efficient verification on Ethereum. - [Indexed Merkle Tree (Nullifier Tree)](/developers/docs/foundational-topics/advanced/storage/indexed_merkle_tree.md): Learn about indexed merkle trees, an efficient data structure for nullifier trees that enables fast non-membership proofs and batch insertions in Aztec. - [Note Discovery](/developers/docs/foundational-topics/advanced/storage/note_discovery.md): Understand how Aztec's note tagging system allows users to efficiently discover and decrypt notes that belong to them without relying on brute force or offchain communication. - [Storage Slots](/developers/docs/foundational-topics/advanced/storage/storage_slots.md): Understand how storage slots work in Aztec for both public and private state, including siloing mechanisms and note hash commitments. - [Call Types](/developers/docs/foundational-topics/call_types.md): Understand the different types of contract calls in Aztec, including private and public execution modes, and how they compare to Ethereum's call types. - [Contract Deployment](/developers/docs/foundational-topics/contract_creation.md): Learn how contract classes and instances are created and deployed on the Aztec network. - [L1-L2 Communication (Portals)](/developers/docs/foundational-topics/ethereum-aztec-messaging.md): A conceptual introduction to Portals and how Aztec communicates with L1 (Ethereum) - [Data Structures](/developers/docs/foundational-topics/ethereum-aztec-messaging/data_structures.md): Learn about the data structures used in Aztec portals for L1-L2 communication. - [Inbox](/developers/docs/foundational-topics/ethereum-aztec-messaging/inbox.md): Learn about the inbox mechanism in Aztec portals for receiving messages from L1. - [Outbox](/developers/docs/foundational-topics/ethereum-aztec-messaging/outbox.md): Learn about the outbox mechanism in Aztec portals for sending messages to L1. - [Registry](/developers/docs/foundational-topics/ethereum-aztec-messaging/registry.md): Learn about the portal registry and how it manages L1-L2 contract mappings. - [Fees](/developers/docs/foundational-topics/fees.md): Understand Aztec's fee system including mana-based transaction pricing, Aztec token and Fee Juice payments, and how L1 and L2 costs are transparently calculated for users. - [Private Execution Environment (PXE)](/developers/docs/foundational-topics/pxe.md): Explore the PXE, a client-side library that handles private function execution, proof generation, secret management, and transaction orchestration in Aztec. - [State Management](/developers/docs/foundational-topics/state_management.md): How public and private state work in Aztec, including storage slots, notes, and the UTXO model - [Transactions](/developers/docs/foundational-topics/transactions.md): Comprehensive guide to the Aztec transaction lifecycle, covering private execution, PXE interactions, kernel circuits, and the step-by-step process from user request to L1 settlement. - [Wallets](/developers/docs/foundational-topics/wallets.md): Overview of wallet responsibilities in Aztec including account management, private state tracking, transaction execution, key management, and authorization handling. - [Community Calls](/developers/docs/resources/community_calls.md): Join live calls to connect with the Aztec team and other builders. - [Limitations](/developers/docs/resources/considerations/limitations.md): Understand the current limitations of the Aztec network and its implications for developers. - [Privacy Considerations](/developers/docs/resources/considerations/privacy_considerations.md): Learn about key privacy considerations when building applications on Aztec. - [Glossary](/developers/docs/resources/glossary.md): Comprehensive glossary of terms used throughout the Aztec documentation and protocol. - [Migration notes](/developers/docs/resources/migration_notes.md): Read about migration notes from previous versions, which could solve problems while updating - [Video lessons](/developers/docs/resources/video_lessons.md): Learn Aztec through short video explainers covering what Aztec is, private and public state, private composability, and getting started. - [Counter Contract](/developers/docs/tutorials/contract_tutorials/counter_contract.md): Code-along tutorial for creating a simple counter contract on Aztec. - [Verify Noir Proofs in Aztec Contracts](/developers/docs/tutorials/contract_tutorials/recursive_verification.md): Learn to generate offchain ZK proofs with Noir and verify them onchain in Aztec private smart contracts - [Private Token Contract](/developers/docs/tutorials/contract_tutorials/token_contract.md): Build a privacy-preserving token for employee mental health benefits that keeps spending habits confidential. - [Deploying a Token Contract](/developers/docs/tutorials/js_tutorials/aztecjs-getting-started.md): A tutorial going through how to deploy a token contract to the local network using typescript. - [Bridge Your NFT to Aztec](/developers/docs/tutorials/js_tutorials/token_bridge.md): Build a private NFT bridge between Ethereum and Aztec using custom notes, PrivateSet, and cross-chain messaging portals. - [Run Aztec in a Local Network](/developers/docs/tutorials/local_network.md): Information about running the Aztec local network development environment. - [Testing Governance Rollup Upgrade on Local Network](/developers/docs/tutorials/testing_governance_rollup_upgrade.md): Deploy a new rollup and execute a governance upgrade on a local Aztec network for testing. - [Getting Started on Local Network](/developers/getting_started_on_local_network.md): Guide for developers to get started with the Aztec local network, including account creation and contract deployment. - [Getting Started on Testnet](/developers/getting_started_on_testnet.md): Deploy contracts and send transactions on the Aztec testnet using the CLI wallet and the Sponsored FPC for fee payment. - [Aztec Overview](/developers/overview.md): Overview of Aztec, a privacy-first Layer 2 on Ethereum supporting smart contracts with private and public state and execution. - [Support](/developers/support.md): Where to ask questions, report bugs, request features, and disclose security issues for the Aztec stack. ## operate - [Operating Aztec Infrastructure](/operate/testnet/operators.md): Run Aztec network infrastructure - nodes, sequencers, provers, and monitoring. - [Advanced Keystore Usage](/operate/testnet/operators/keystore.md): Learn how to configure keystores with remote signers, mnemonics, JSON V3 keystores, and multiple publishers for enhanced security and flexibility. - [Sample configuration patterns](/operate/testnet/operators/keystore/advanced-patterns.md): Learn about advanced keystore patterns including multiple publishers, multiple sequencers, and infrastructure provider scenarios. - [Creating Sequencer Keystores](/operate/testnet/operators/keystore/creating_keystores.md): Learn how to create sequencer keystores for running validators on the Aztec network using the Aztec CLI. - [Key storage methods](/operate/testnet/operators/keystore/storage-methods.md): Learn about different methods for storing and accessing private keys in Aztec keystores, including inline keys, remote signers, JSON V3 keystores, and mnemonics. - [Troubleshooting and Best Practices](/operate/testnet/operators/keystore/troubleshooting.md): Common issues, troubleshooting steps, security best practices, and CLI reference for keystore configuration and operation on the Aztec network. - [Monitoring and Observability](/operate/testnet/operators/monitoring.md): Learn how to monitor your Aztec node with metrics, OpenTelemetry, Prometheus, and Grafana. - [Grafana Setup](/operate/testnet/operators/monitoring/grafana-setup.md): Configure Grafana to visualize Aztec node metrics and set up alerts for monitoring your node's health. - [Key Metrics Reference](/operate/testnet/operators/monitoring/metrics-reference.md): Comprehensive guide to understanding and using the metrics exposed by your Aztec node for monitoring and observability. - [OpenTelemetry Collector Setup](/operate/testnet/operators/monitoring/otel-setup.md): Configure OpenTelemetry Collector to receive metrics from your Aztec node and export them to Prometheus. - [Prometheus Setup](/operate/testnet/operators/monitoring/prometheus-setup.md): Configure Prometheus to scrape and store metrics from your Aztec node's OpenTelemetry Collector. - [Complete Example and Troubleshooting](/operate/testnet/operators/monitoring/troubleshooting.md): Complete Docker Compose example with all monitoring components and troubleshooting guide for common monitoring issues. - [FAQs & Common Issues](/operate/testnet/operators/operator-faq.md): Troubleshooting guide for common Aztec node operator issues including sync errors, RPC limits, and update procedures. - [Prerequisites](/operate/testnet/operators/prerequisites.md): Common prerequisites and requirements for running nodes on the Aztec network, including hardware, software, and network configuration. - [Changelog](/operate/testnet/operators/reference/changelog.md): Comprehensive changelog documenting configuration changes, new features, and breaking changes across Aztec node versions. - [v2.0.2 (from v1.2.1)](/operate/testnet/operators/reference/changelog/v2.0.2.md): Major release with configuration simplification, keystore integration, and enhanced features. Includes breaking changes requiring migration. - [v4.x (Upgrade from Ignition)](/operate/testnet/operators/reference/changelog/v4.md): Breaking changes and migration guide for upgrading from Ignition (v2.x) to Alpha (v4.x). - [v4.2.0](/operate/testnet/operators/reference/changelog/v4.2.md): New features and configuration options for node operators. - [v4.3.0](/operate/testnet/operators/reference/changelog/v4.3.md): Operator-facing changes for the v4.3.0 release. - [Cli Reference](/operate/testnet/operators/reference/cli-reference.md): A reference of the --help output when running aztec start. - [Ethereum RPC call reference](/operate/testnet/operators/reference/ethereum_rpc_reference.md): A comprehensive reference of Ethereum RPC calls used by different Aztec node components, including archiver, sequencer, prover, and slasher nodes. - [Glossary](/operate/testnet/operators/reference/glossary.md): A comprehensive glossary of terms used throughout the Aztec network documentation, covering node operations, consensus, cryptography, and infrastructure concepts. - [Node JSON RPC API reference](/operate/testnet/operators/reference/node_api_reference.md): Complete reference for the Aztec Node JSON RPC API, including block queries, transaction submission, world state access, and administrative operations. - [Sequencer Management](/operate/testnet/operators/sequencer-management.md): Learn how to manage your sequencer operations including governance participation, delegated stake, and contract queries. - [Claiming Rewards](/operate/testnet/operators/sequencer-management/claiming-rewards.md): Learn how to claim your sequencer rewards from the Aztec Rollup contract using cast commands. - [Governance and Proposal Process](/operate/testnet/operators/sequencer-management/creating_and_voting_on_proposals.md): Learn how to participate in protocol governance as a sequencer, including signaling support, creating proposals, and voting - [Slashing and Offenses](/operate/testnet/operators/sequencer-management/slashing_and_offenses.md): Learn how the slashing mechanism works, what offenses are detected, and how to configure your sequencer to participate in consensus-based slashing - [Useful Commands](/operate/testnet/operators/sequencer-management/useful-commands.md): Essential cast commands for querying Registry, Rollup, and Governance contracts as a sequencer operator. - [Become a Staking Provider](/operate/testnet/operators/setup/become_a_staking_provider.md): Learn how to run a sequencer with delegated stake on the Aztec network, including provider registration and sequencer identity management. - [Blob retrieval](/operate/testnet/operators/setup/blob_storage.md): Learn how Aztec nodes retrieve blob data for L1 transactions. - [Blob upload](/operate/testnet/operators/setup/blob_upload.md): Learn how to host a blob file store to contribute to the Aztec network. - [Using and running a bootnode](/operate/testnet/operators/setup/bootnode_operation.md): Learn how to connect to and operate bootnodes for peer discovery in the Aztec network. - [Building Node Software from Source](/operate/testnet/operators/setup/building_from_source.md): Build the Aztec node Docker image from source code for development, testing, or running a specific version. - [High Availability Sequencers](/operate/testnet/operators/setup/high_availability_sequencers.md): Learn how to run highly available sequencers across multiple nodes with database-backed coordination to prevent double-signing and ensure redundancy. - [Registering a Sequencer](/operate/testnet/operators/setup/registering_sequencer.md): Learn how to register your sequencer on the Aztec network using the staking dashboard for self-staking. - [Running a Full Node](/operate/testnet/operators/setup/running_a_node.md): A comprehensive guide on how to run a full node on the Aztec network using Docker Compose. - [Running a Prover](/operate/testnet/operators/setup/running_a_prover.md): A comprehensive guide on how to run an Aztec prover on the network using Docker Compose in a distributed configuration. - [Running a Sequencer](/operate/testnet/operators/setup/sequencer_management.md): Learn how to manage your sequencer on the Aztec network, including registration, keystore configuration, stake management, and status monitoring. - [Using and uploading snapshots](/operate/testnet/operators/setup/syncing_best_practices.md): Learn sync modes and snapshot strategies to efficiently sync your Aztec node with the network. - [v4.x (Upgrade from Ignition)](/operate/testnet/reference/changelog/v4.md): Breaking changes and migration guide for upgrading from Ignition (v2.x) to Alpha (v4.x). - [Operating Aztec Infrastructure](/operate/operators.md): Run Aztec network infrastructure - nodes, sequencers, provers, and monitoring. - [Advanced Keystore Usage](/operate/operators/keystore.md): Learn how to configure keystores with remote signers, mnemonics, JSON V3 keystores, and multiple publishers for enhanced security and flexibility. - [Sample configuration patterns](/operate/operators/keystore/advanced-patterns.md): Learn about advanced keystore patterns including multiple publishers, multiple sequencers, and infrastructure provider scenarios. - [Creating Sequencer Keystores](/operate/operators/keystore/creating_keystores.md): Learn how to create sequencer keystores for running validators on the Aztec network using the Aztec CLI. - [Key storage methods](/operate/operators/keystore/storage-methods.md): Learn about different methods for storing and accessing private keys in Aztec keystores, including inline keys, remote signers, JSON V3 keystores, and mnemonics. - [Troubleshooting and Best Practices](/operate/operators/keystore/troubleshooting.md): Common issues, troubleshooting steps, security best practices, and CLI reference for keystore configuration and operation on the Aztec network. - [Monitoring and Observability](/operate/operators/monitoring.md): Learn how to monitor your Aztec node with metrics, OpenTelemetry, Prometheus, and Grafana. - [Grafana Setup](/operate/operators/monitoring/grafana-setup.md): Configure Grafana to visualize Aztec node metrics and set up alerts for monitoring your node's health. - [Key Metrics Reference](/operate/operators/monitoring/metrics-reference.md): Comprehensive guide to understanding and using the metrics exposed by your Aztec node for monitoring and observability. - [OpenTelemetry Collector Setup](/operate/operators/monitoring/otel-setup.md): Configure OpenTelemetry Collector to receive metrics from your Aztec node and export them to Prometheus. - [Prometheus Setup](/operate/operators/monitoring/prometheus-setup.md): Configure Prometheus to scrape and store metrics from your Aztec node's OpenTelemetry Collector. - [Complete Example and Troubleshooting](/operate/operators/monitoring/troubleshooting.md): Complete Docker Compose example with all monitoring components and troubleshooting guide for common monitoring issues. - [FAQs & Common Issues](/operate/operators/operator-faq.md): Troubleshooting guide for common Aztec node operator issues including sync errors, RPC limits, and update procedures. - [Prerequisites](/operate/operators/prerequisites.md): Common prerequisites and requirements for running nodes on the Aztec network, including hardware, software, and network configuration. - [Changelog](/operate/operators/reference/changelog.md): Comprehensive changelog documenting configuration changes, new features, and breaking changes across Aztec node versions. - [v2.0.2 (from v1.2.1)](/operate/operators/reference/changelog/v2.0.2.md): Major release with configuration simplification, keystore integration, and enhanced features. Includes breaking changes requiring migration. - [v4.x (Upgrade from Ignition)](/operate/operators/reference/changelog/v4.md): Breaking changes and migration guide for upgrading from Ignition (v2.x) to Alpha (v4.x). - [v4.2.0](/operate/operators/reference/changelog/v4.2.md): New features and configuration options for node operators. - [v4.3](/operate/operators/reference/changelog/v4.3.md): Operator-facing changes for the v4.3.x releases. - [Cli Reference](/operate/operators/reference/cli-reference.md): A reference of the --help output when running aztec start. - [Ethereum RPC call reference](/operate/operators/reference/ethereum_rpc_reference.md): A comprehensive reference of Ethereum RPC calls used by different Aztec node components, including archiver, sequencer, prover, and slasher nodes. - [Glossary](/operate/operators/reference/glossary.md): A comprehensive glossary of terms used throughout the Aztec network documentation, covering node operations, consensus, cryptography, and infrastructure concepts. - [Node JSON RPC API reference](/operate/operators/reference/node_api_reference.md): Complete reference for the Aztec Node JSON RPC API, including block queries, transaction submission, world state access, and administrative operations. - [Sequencer Management](/operate/operators/sequencer-management.md): Learn how to manage your sequencer operations including governance participation, delegated stake, and contract queries. - [Claiming Rewards](/operate/operators/sequencer-management/claiming-rewards.md): Learn how to claim your sequencer rewards from the Aztec Rollup contract using cast commands. - [Governance and Proposal Process](/operate/operators/sequencer-management/creating_and_voting_on_proposals.md): Learn how to participate in protocol governance as a sequencer, including signaling support, creating proposals, and voting - [Slashing and Offenses](/operate/operators/sequencer-management/slashing_and_offenses.md): Learn how the slashing mechanism works, what offenses are detected, and how to configure your sequencer to participate in consensus-based slashing - [Useful Commands](/operate/operators/sequencer-management/useful-commands.md): Essential cast commands for querying Registry, Rollup, and Governance contracts as a sequencer operator. - [Become a Staking Provider](/operate/operators/setup/become_a_staking_provider.md): Learn how to run a sequencer with delegated stake on the Aztec network, including provider registration and sequencer identity management. - [Blob retrieval](/operate/operators/setup/blob_storage.md): Learn how Aztec nodes retrieve blob data for L1 transactions. - [Blob upload](/operate/operators/setup/blob_upload.md): Learn how to host a blob file store to contribute to the Aztec network. - [Using and running a bootnode](/operate/operators/setup/bootnode_operation.md): Learn how to connect to and operate bootnodes for peer discovery in the Aztec network. - [Building Node Software from Source](/operate/operators/setup/building_from_source.md): Build the Aztec node Docker image from source code for development, testing, or running a specific version. - [High Availability Sequencers](/operate/operators/setup/high_availability_sequencers.md): Learn how to run highly available sequencers across multiple nodes with database-backed coordination to prevent double-signing and ensure redundancy. - [Registering a Sequencer](/operate/operators/setup/registering_sequencer.md): Learn how to register your sequencer on the Aztec network using the staking dashboard for self-staking. - [Running a Full Node](/operate/operators/setup/running_a_node.md): A comprehensive guide on how to run a full node on the Aztec network using Docker Compose. - [Running a Prover](/operate/operators/setup/running_a_prover.md): A comprehensive guide on how to run an Aztec prover on the network using Docker Compose in a distributed configuration. - [Running a Sequencer](/operate/operators/setup/sequencer_management.md): Learn how to manage your sequencer on the Aztec network, including registration, keystore configuration, stake management, and status monitoring. - [Using and uploading snapshots](/operate/operators/setup/syncing_best_practices.md): Learn sync modes and snapshot strategies to efficiently sync your Aztec node with the network. - [v4.x (Upgrade from Ignition)](/operate/reference/changelog/v4.md): Breaking changes and migration guide for upgrading from Ignition (v2.x) to Alpha (v4.x). ## participate - [Participate in the Aztec Network](/participate.md): Participate in the Aztec network - learn how it works, its token economics, and governance. - [Alpha Network](/participate/alpha.md): Understand the Aztec Alpha network: its purpose, known limitations, expected issues, and what to expect as the protocol matures. - [Basics of Aztec](/participate/basics.md): Learn the fundamentals of how the Aztec network works - addresses, wallets, fees, transactions, blocks, and bridging. - [Addresses on Aztec](/participate/basics/addresses.md): Learn how addresses work on Aztec - from smart contract accounts to deterministic address derivation. - [Blocks and Epochs](/participate/basics/blocks.md): Learn how blocks are produced on Aztec, the role of sequencers and provers, and how epochs organize proving work. - [Bridging Between Ethereum and Aztec](/participate/basics/bridging.md): Understand how assets move between Ethereum (L1) and Aztec (L2) through portals and message passing. - [Fees on Aztec](/participate/basics/fees.md): Understand how transaction fees work on Aztec, including mana and the fee token. - [Transactions on Aztec](/participate/basics/transactions.md): Learn about the Aztec transaction lifecycle, from creation to settlement, including client-side proving. - [Wallets on Aztec](/participate/basics/wallets.md): Explore Aztec wallets, their unique responsibilities, and hardware wallet support. - [Governance](/participate/governance.md): Learn how the Aztec network is governed through onchain voting, sequencer signaling, and stake-based voting power. - [L1 Contracts](/participate/governance/contracts.md): Overview of the L1 smart contracts that power Aztec network governance. - [Governance Staking Escrow (GSE)](/participate/governance/gse.md): Learn how the Governance Staking Escrow (GSE) enables seamless stake migration during rollup upgrades. - [Proposal Lifecycle](/participate/governance/proposal-lifecycle.md): Learn how proposals move through the governance process, from initial signaling to final execution. - [Network Upgrades](/participate/governance/upgrades.md): Learn how the Aztec network upgrades to new rollup versions and how validators transition. - [Voting](/participate/governance/voting.md): Learn how voting power works in Aztec governance, including deposits, withdrawals, delegation, and vote casting. - [$AZTEC Token Overview](/participate/token.md): Learn about the $AZTEC token - its utility, economics, and how to participate in the network. - [Delegating Stake](/participate/token/delegation.md): Learn how to delegate your stake to operators on the Aztec network without running infrastructure. - [Economics & Rewards](/participate/token/economics.md): Learn about Aztec network economics including reward distribution, sequencer and prover incentives, and the activity score system. - [Staking Tokens](/participate/token/staking.md): Learn how to stake tokens on the Aztec network to participate in network security and earn rewards. - [Voting on Proposals](/participate/token/voting.md): Learn how to vote on governance proposals on the Aztec network using your staked tokens. ## aztec_connect_sunset - [Aztec Connect Sunset](/aztec_connect_sunset.md): Important information about the Aztec Connect deprecation, including withdrawal instructions and guidance for running your own instance of the rollup infrastructure. ## networks - [Aztec networks overview](/networks.md): Connect to Aztec Networks: Alpha (Mainnet) and Testnet, choose the right network for your use case, and find the version each network is running. --- # Full Documentation Content # AI Tooling Aztec is new, rapidly evolving, and spans novel concepts like private state, notes, and nullifiers. AI coding tools can accelerate your learning and development, but they need up-to-date context to be useful. This page shows you how to set that up. caution LLMs have limited training data for zero-knowledge circuit development. Noir and Aztec.nr are newer languages with smaller codebases than mainstream languages, so AI tools will make more mistakes than you might be used to. The tools on this page help by providing up-to-date context, but you should always verify generated code and test thoroughly. ## Project-level instructions (CLAUDE.md / AGENTS.md files)[​](#project-level-instructions-claudemd--agentsmd-files "Direct link to Project-level instructions (CLAUDE.md / AGENTS.md files)") MCP servers and skills provide context on demand, but AI tools don't always invoke them at the right time. The most reliable way to prevent common mistakes is to add **project-level instruction files** that your AI tool reads automatically at the start of every conversation. You can add to these files over time as you discover new gotchas or best practices. They ensure your AI tool always has the critical context it needs, without relying on you to remember to invoke the right skills or MCP servers. For Claude Code, create a `CLAUDE.md` file in your project root. For Codex, create an `AGENTS.md` file in your project root. For other tools, check their documentation for equivalent configuration. ### Recommended CLAUDE.md / AGENTS.md[​](#recommended-claudemd--agentsmd "Direct link to Recommended CLAUDE.md / AGENTS.md") ``` # Aztec Project ## Critical: Use the `aztec` CLI, not `nargo` or `bb` directly This is an Aztec smart contract project. Always use the `aztec` CLI wrapper instead of calling `nargo` or `bb` (the Barretenberg prover) directly: - **Compile**: `aztec compile` (NOT `nargo compile`). Using `nargo compile` alone produces incomplete artifacts. - **Test**: `aztec test` (NOT `nargo test`). - **Prove**: NEVER call `bb` directly. Proof generation is handled for you by the PXE through the `aztec` CLI and `aztec.js`. There is no contract-development workflow that runs `bb` by hand. - **Other nargo commands** like `aztec-nargo fmt` and `aztec-nargo doc` are fine to use directly. The Aztec installer exposes the bundled `nargo` as `aztec-nargo`; bare `nargo` resolves to your own install (if any), not the bundled one. ## Error Handling - NEVER silently swallow errors or fall back to default values. If a value is required, throw if it's missing. - NEVER use fallback values like `AztecAddress.ZERO`, `"unknown"`, `0`, or `null` to mask missing data. These hide bugs and cause failures elsewhere that are harder to trace. - NEVER add retry/polling logic unless explicitly asked. Retry loops with long timeouts may brick application loops and mask the real error. - NEVER wrap calls in try/catch that returns null or a default. Let errors propagate. - If a precondition isn't met, throw immediately with a descriptive message — don't try to "work around" it. - Prefer `T` return types over `T | null` when null would indicate a bug rather than a valid state. - Do not add `.catch(() => defaultValue)` to promises. If something fails, the caller needs to know. ## Version Compatibility The Aztec developer SDK/aztec-nr version (used for writing and compiling contracts) may differ from the node version (used by operators to run the network). Check the [Networks page](https://docs.aztec.network/networks) for current network versions. When in doubt, use the version from the developer docs you are reading, it is the correct SDK version for contract development on that network. ## Hashing: Default to Poseidon2 When writing Aztec.nr contract code that requires hashing, **always use Poseidon2** unless a specific protocol or interoperability requirement calls for a different hash. - **Default**: `use aztec::protocol::hash::poseidon2_hash;` - **Do NOT** default to Pedersen (`pedersen_hash`). Pedersen is available but Poseidon2 is cheaper in circuits and is the standard across Aztec. - If you are unsure which hash to use, use Poseidon2. ``` This prevents the two most common AI mistakes: using `nargo compile`/`nargo test` instead of their Aztec wrappers, and defaulting to Pedersen hashes instead of Poseidon2. ### Why this matters[​](#why-this-matters "Direct link to Why this matters") LLMs have extensive training data for `nargo` (the standalone Noir compiler) and `bb` (the Barretenberg prover CLI) but limited exposure to the `aztec` CLI wrapper. Without explicit instructions, they default to `nargo compile` (which produces artifacts missing the AVM transpilation step) or reach for `bb` to generate proofs. In an Aztec project, compilation and proving both go through the `aztec` tooling. ## MCP servers[​](#mcp-servers "Direct link to MCP servers") The highest-leverage tools are the Aztec and Noir MCP servers. They clone reference repositories locally and give your AI tool code search, documentation search, and example discovery across the Aztec and Noir ecosystems. They work with any AI coding tool that supports MCP (Claude Code, Cursor, Windsurf, Codex, and others). The MCP servers help manage the problem of focusing LLMs on the correct Aztec versions for your project. Aztec is under active development and there may be multiple versions in use at any given time (e.g. mainnet, devnet and testnet may be on different versions). They make it easy to switch between versions if needed, and to keep your context up to date as the repos evolve. Start here if you're unsure what to set up. ### Claude Code[​](#claude-code "Direct link to Claude Code") Add the MCP servers directly: ``` claude mcp add aztec -- npx @aztec/mcp-server@latest claude mcp add noir -- npx noir-mcp-server@latest ``` ### Cursor / Windsurf / other MCP clients[​](#cursor--windsurf--other-mcp-clients "Direct link to Cursor / Windsurf / other MCP clients") Add the servers to your MCP configuration JSON: ``` { "mcpServers": { "aztec": { "command": "npx", "args": ["-y", "@aztec/mcp-server@latest"] }, "noir": { "command": "npx", "args": ["-y", "noir-mcp-server@latest"] } } } ``` ### OpenAI Codex[​](#openai-codex "Direct link to OpenAI Codex") Use the same MCP configuration format, pointing at `@aztec/mcp-server` and `noir-mcp-server`. ## For learning and exploration[​](#for-learning-and-exploration "Direct link to For learning and exploration") These resources help you understand Aztec concepts, read docs, or provide additional context to your AI tool. * **API reference docs** - The docs site publishes auto-generated API references that are useful to feed to AI tools: * [Aztec.nr API reference](/developers/testnet/docs/aztec-nr/api.md) - generated from aztec-nr source with `nargo doc` * [TypeScript API reference](/developers/testnet/docs/aztec-js/typescript_api_reference.md) - generated from yarn-project packages with TypeDoc These are especially useful as context for code generation since they reflect the current API surface. * **llms.txt** - The docs site publishes `llms.txt` and `llms-full.txt` at [docs.aztec.network/llms.txt](https://docs.aztec.network/llms.txt) for automatic LLM discovery. Many AI tools can consume these files directly to index documentation. * **Reference repositories** - Point your AI tool at these repos for additional context: * [AztecProtocol/aztec-packages](https://github.com/AztecProtocol/aztec-packages) - main monorepo, best general reference * [AztecProtocol/aztec-starter](https://github.com/AztecProtocol/aztec-starter) - smaller starter project, easier for onboarding * [AztecProtocol/aztec-examples](https://github.com/AztecProtocol/aztec-examples) - official contract examples * [noir-lang/noir](https://github.com/noir-lang/noir) - Noir language source of truth * [noir-lang/noir-examples](https://github.com/noir-lang/noir-examples) - common Noir patterns * [awesome-noir](https://github.com/noir-lang/awesome-noir) - community Noir resources * [awesome-aztec](https://github.com/AztecProtocol/awesome-aztec) - community Aztec resources * **Copy docs into context** - Copy docs pages directly into your AI tool's context or conversation using the "Copy page" button at the top of each page. * **Context7** - [Context7](https://context7.com) is a generic MCP server with Aztec docs available at [context7.com/aztecprotocol/aztec-packages](https://context7.com/aztecprotocol/aztec-packages). Note that it may be less current than the MCP servers above. ## Aztec and Noir tool reference[​](#aztec-and-noir-tool-reference "Direct link to Aztec and Noir tool reference") | Tool | Works with | Description | | --------------------------------------------------------------------------- | ----------------------------------------------------- | ----------------------------------------------------------------------------------- | | [@aztec/mcp-server](https://github.com/AztecProtocol/mcp-server) | Any MCP client (Claude Code, Cursor, Windsurf, Codex) | Clones Aztec repos locally, provides code search, doc search, and example discovery | | [noir-claude-plugin](https://github.com/critesjosh/noir-claude-plugin) | Claude Code | Skills and commands for Noir circuit development | | [noir-mcp-server](https://github.com/critesjosh/noir-mcp-server) | Any MCP client | Clones Noir repos, stdlib, and community libraries; provides search and examples | | [aztec-skills](https://github.com/NethermindEth/aztec-skills) | Claude Code, Codex | Installable skills for Aztec contracts, deployment, Aztec.js, and testing | | [noir skills](https://github.com/noir-lang/noir/tree/master/.claude/skills) | Claude Code, Codex | Skills for Noir compiler development, SSA debugging, fuzzing, and ACIR optimization | --- # Aztec.js Aztec.js is a library that provides APIs for managing accounts and interacting with contracts on the Aztec network. It communicates with the [Private eXecution Environment (PXE)](/developers/testnet/docs/foundational-topics/pxe.md) through a `PXE` implementation, allowing developers to easily register new accounts, deploy contracts, view functions, and send transactions. ## Installing[​](#installing "Direct link to Installing") ``` npm install @aztec/aztec.js@5.0.0-rc.2 ``` ## Common Dependencies[​](#common-dependencies "Direct link to Common Dependencies") Most applications will need additional packages alongside `@aztec/aztec.js`, e.g.: ``` npm install @aztec/aztec.js@5.0.0-rc.2 \ @aztec/accounts@5.0.0-rc.2 \ @aztec/wallets@5.0.0-rc.2 \ @aztec/noir-contracts.js@5.0.0-rc.2 ``` | Package | Description | | -------------------------- | ------------------------------------------------------------- | | `@aztec/aztec.js` | Core SDK for contracts, transactions, and network interaction | | `@aztec/accounts` | Account contract implementations (Schnorr, ECDSA) | | `@aztec/wallets` | Simplified wallets for local development and scripting | | `@aztec/noir-contracts.js` | Pre-compiled contract interfaces (Token, NFT, etc.) | ## Package Structure[​](#package-structure "Direct link to Package Structure") `@aztec/aztec.js` uses subpath exports. You must import from specific subpaths rather than the package root: ``` import { createAztecNodeClient, waitForNode } from "@aztec/aztec.js/node"; import { Fr } from "@aztec/aztec.js/fields"; import { AztecAddress } from "@aztec/aztec.js/addresses"; ``` ## AI-Friendly Reference[​](#ai-friendly-reference "Direct link to AI-Friendly Reference") The [TypeScript API reference](/developers/testnet/docs/aztec-js/typescript_api_reference.md) links to markdown interface files for common packages for easy use with AI coding assistants. Copy relevant sections to give your AI tool accurate context about Aztec.js APIs. ## Guides[​](#guides "Direct link to Guides") ## [📄️Connect to Local Network](/developers/testnet/docs/aztec-js/how_to_connect_to_local_network.md) [Connect your application to the Aztec local network and interact with accounts.](/developers/testnet/docs/aztec-js/how_to_connect_to_local_network.md) ## [📄️Creating Accounts](/developers/testnet/docs/aztec-js/how_to_create_account.md) [Step-by-step guide to creating and deploying new user accounts in Aztec.js applications.](/developers/testnet/docs/aztec-js/how_to_create_account.md) ## [📄️Deploying Contracts](/developers/testnet/docs/aztec-js/how_to_deploy_contract.md) [Deploy smart contracts to Aztec using generated TypeScript classes.](/developers/testnet/docs/aztec-js/how_to_deploy_contract.md) ## [📄️Sending Transactions](/developers/testnet/docs/aztec-js/how_to_send_transaction.md) [Send transactions to Aztec contracts using Aztec.js with various options and error handling](/developers/testnet/docs/aztec-js/how_to_send_transaction.md) ## [📄️Reading Contract Data](/developers/testnet/docs/aztec-js/how_to_read_data.md) [How to read data from contracts including simulating functions, reading logs, and retrieving events.](/developers/testnet/docs/aztec-js/how_to_read_data.md) ## [📄️Simulate without signing prompts](/developers/testnet/docs/aztec-js/how_to_simulate_without_signing.md) [How to call .simulate() on a view function or estimate gas without prompting the user to sign authentication witnesses.](/developers/testnet/docs/aztec-js/how_to_simulate_without_signing.md) ## [📄️Using Authentication Witnesses](/developers/testnet/docs/aztec-js/how_to_use_authwit.md) [Step-by-step guide to implementing authentication witnesses in Aztec.js for delegated transactions.](/developers/testnet/docs/aztec-js/how_to_use_authwit.md) ## [📄️Paying Fees](/developers/testnet/docs/aztec-js/how_to_pay_fees.md) [Pay transaction fees on Aztec, understand mana costs, estimate gas, and retrieve fees from receipts.](/developers/testnet/docs/aztec-js/how_to_pay_fees.md) ## [📄️Testing Smart Contracts](/developers/testnet/docs/aztec-js/how_to_test.md) [Learn how to write and run tests for your Aztec smart contracts using Aztec.js and a local network.](/developers/testnet/docs/aztec-js/how_to_test.md) ## [📄️Pay Fees Privately](/developers/testnet/docs/aztec-js/how_to_use_private_fee_juice.md) [Learn how private fee payment works on Aztec and walk through an example using a community-built fully private Fee Payment Contract.](/developers/testnet/docs/aztec-js/how_to_use_private_fee_juice.md) ## [📄️Reference](/developers/testnet/docs/aztec-js/aztec_js_reference.md) [Comprehensive auto-generated reference for the Aztec.js TypeScript library with all classes, interfaces, types, and functions.](/developers/testnet/docs/aztec-js/aztec_js_reference.md) ## [📄️TypeScript API Reference](/developers/testnet/docs/aztec-js/typescript_api_reference.md) [API reference documentation for Aztec TypeScript packages including aztec.js, accounts, PXE, and core libraries.](/developers/testnet/docs/aztec-js/typescript_api_reference.md) --- # Reference *This documentation is auto-generated from the Aztec.js TypeScript source code.* info This is an auto-generated reference. For tutorials and guides, see the [Aztec.js Guide](/developers/testnet/docs/aztec-js.md). *Package: @aztec/aztec.js* *Generated: 2025-12-10T22:27:41.987Z* This document provides a comprehensive reference for all public APIs in the Aztec.js library. Each section is organized by module, with classes, interfaces, types, and functions documented with their full signatures, parameters, and return types. ## Table of Contents[​](#table-of-contents "Direct link to Table of Contents") * [Account](#account) * [AccountContract](#accountcontract) * [getAccountContractAddress](#getaccountcontractaddress) * [AccountWithSecretKey](#accountwithsecretkey) * [Account](#account) * [BaseAccount](#baseaccount) * [AccountInterface](#accountinterface) * [SignerlessAccount](#signerlessaccount) * [Authorization](#authorization) * [CallAuthorizationRequest](#callauthorizationrequest) * [Contract](#contract) * [BaseContractInteraction](#basecontractinteraction) * [BatchCall](#batchcall) * [abiChecker](#abichecker) * [ContractMethod](#contractmethod) * [ContractStorageLayout](#contractstoragelayout) * [ContractBase](#contractbase) * [ContractFunctionInteraction](#contractfunctioninteraction) * [Contract](#contract) * [RequestDeployOptions](#requestdeployoptions) * [DeployOptions](#deployoptions) * [SimulateDeployOptions](#simulatedeployoptions) * [DeployMethod](#deploymethod) * [DeployedWaitOpts](#deployedwaitopts) * [DeployTxReceipt](#deploytxreceipt) * [DeploySentTx](#deploysenttx) * [getGasLimits](#getgaslimits) * [FeeEstimationOptions](#feeestimationoptions) * [FeePaymentMethodOption](#feepaymentmethodoption) * [GasSettingsOption](#gassettingsoption) * [InteractionFeeOptions](#interactionfeeoptions) * [SimulationInteractionFeeOptions](#simulationinteractionfeeoptions) * [RequestInteractionOptions](#requestinteractionoptions) * [SendInteractionOptions](#sendinteractionoptions) * [SimulateInteractionOptions](#simulateinteractionoptions) * [ProfileInteractionOptions](#profileinteractionoptions) * [SimulationReturn](#simulationreturn) * [toSendOptions](#tosendoptions) * [toSimulateOptions](#tosimulateoptions) * [toProfileOptions](#toprofileoptions) * [getClassRegistryContract](#getclassregistrycontract) * [getInstanceRegistryContract](#getinstanceregistrycontract) * [getFeeJuice](#getfeejuice) * [WaitOpts](#waitopts) * [DefaultWaitOpts](#defaultwaitopts) * [SentTx](#senttx) * [UnsafeContract](#unsafecontract) * [WaitForProvenOpts](#waitforprovenopts) * [DefaultWaitForProvenOpts](#defaultwaitforprovenopts) * [waitForProven](#waitforproven) * [Deployment](#deployment) * [broadcastPrivateFunction](#broadcastprivatefunction) * [broadcastUtilityFunction](#broadcastutilityfunction) * [ContractDeployer](#contractdeployer) * [publishContractClass](#publishcontractclass) * [publishInstance](#publishinstance) * [Ethereum](#ethereum) * [L2Claim](#l2claim) * [L2AmountClaim](#l2amountclaim) * [L2AmountClaimWithRecipient](#l2amountclaimwithrecipient) * [generateClaimSecret](#generateclaimsecret) * [L1TokenManager](#l1tokenmanager) * [L1FeeJuicePortalManager](#l1feejuiceportalmanager) * [L1ToL2TokenPortalManager](#l1tol2tokenportalmanager) * [L1TokenPortalManager](#l1tokenportalmanager) * [Fee](#fee) * [FeeJuicePaymentMethodWithClaim](#feejuicepaymentmethodwithclaim) * [FeePaymentMethod](#feepaymentmethod) * [PrivateFeePaymentMethod](#privatefeepaymentmethod) * [PublicFeePaymentMethod](#publicfeepaymentmethod) * [SponsoredFeePaymentMethod](#sponsoredfeepaymentmethod) * [Utils](#utils) * [FieldLike](#fieldlike) * [EthAddressLike](#ethaddresslike) * [AztecAddressLike](#aztecaddresslike) * [FunctionSelectorLike](#functionselectorlike) * [EventSelectorLike](#eventselectorlike) * [U128Like](#u128like) * [WrappedFieldLike](#wrappedfieldlike) * [IntentInnerHash](#intentinnerhash) * [CallIntent](#callintent) * [ContractFunctionInteractionCallIntent](#contractfunctioninteractioncallintent) * [computeAuthWitMessageHash](#computeauthwitmessagehash) * [getMessageHashFromIntent](#getmessagehashfromintent) * [computeInnerAuthWitHashFromAction](#computeinnerauthwithashfromaction) * [lookupValidity](#lookupvalidity) * [SetPublicAuthwitContractInteraction](#setpublicauthwitcontractinteraction) * [waitForL1ToL2MessageReady](#waitforl1tol2messageready) * [isL1ToL2MessageReady](#isl1tol2messageready) * [getFeeJuiceBalance](#getfeejuicebalance) * [readFieldCompressedString](#readfieldcompressedstring) * [waitForNode](#waitfornode) * [createAztecNodeClient](#createaztecnodeclient) * [AztecNode](#aztecnode) * [generatePublicKey](#generatepublickey) * [Wallet](#wallet) * [AccountEntrypointMetaPaymentMethod](#accountentrypointmetapaymentmethod) * [AccountManager](#accountmanager) * [RequestDeployAccountOptions](#requestdeployaccountoptions) * [DeployAccountOptions](#deployaccountoptions) * [SimulateDeployAccountOptions](#simulatedeployaccountoptions) * [DeployAccountMethod](#deployaccountmethod) * [Aliased](#aliased) * [SimulateOptions](#simulateoptions) * [ProfileOptions](#profileoptions) * [SendOptions](#sendoptions) * [BatchableMethods](#batchablemethods) * [BatchedMethod](#batchedmethod) * [BatchedMethodResult](#batchedmethodresult) * [BatchedMethodResultWrapper](#batchedmethodresultwrapper) * [BatchResults](#batchresults) * [PrivateEventFilter](#privateeventfilter) * [PrivateEvent](#privateevent) * [Wallet](#wallet) * [FunctionCallSchema](#functioncallschema) * [ExecutionPayloadSchema](#executionpayloadschema) * [GasSettingsOptionSchema](#gassettingsoptionschema) * [WalletSimulationFeeOptionSchema](#walletsimulationfeeoptionschema) * [SendOptionsSchema](#sendoptionsschema) * [SimulateOptionsSchema](#simulateoptionsschema) * [ProfileOptionsSchema](#profileoptionsschema) * [MessageHashOrIntentSchema](#messagehashorintentschema) * [BatchedMethodSchema](#batchedmethodschema) * [ContractMetadataSchema](#contractmetadataschema) * [ContractClassMetadataSchema](#contractclassmetadataschema) * [EventMetadataDefinitionSchema](#eventmetadatadefinitionschema) * [PrivateEventSchema](#privateeventschema) * [PrivateEventFilterSchema](#privateeventfilterschema) * [WalletSchema](#walletschema) *** ## Account[​](#account "Direct link to Account") *** ### `account/account_contract.ts`[​](#accountaccount_contractts "Direct link to accountaccount_contractts") #### AccountContract[​](#accountcontract "Direct link to AccountContract") **Type:** Interface An account contract instance. Knows its artifact, deployment arguments, how to create transaction execution requests out of function calls, and how to authorize actions. #### Methods[​](#methods "Direct link to Methods") ##### getContractArtifact[​](#getcontractartifact "Direct link to getContractArtifact") Returns the artifact of this account contract. **Signature:** ``` getContractArtifact(): Promise ``` **Returns:** `Promise` ##### getInitializationFunctionAndArgs[​](#getinitializationfunctionandargs "Direct link to getInitializationFunctionAndArgs") Returns the initializer function name and arguments for this instance, or undefined if this contract does not require initialization. **Signature:** ``` getInitializationFunctionAndArgs(): Promise<{ constructorName: string; constructorArgs: any[]; } | undefined> ``` **Returns:** ``` Promise< | { /** The name of the function used to initialize the contract */ constructorName: string; /** The args to the function used to initialize the contract */ constructorArgs: any[]; } | undefined > ``` ##### getInterface[​](#getinterface "Direct link to getInterface") Returns the account interface for this account contract given an instance at the provided address. The account interface is responsible for assembling tx requests given requested function calls, and for creating signed auth witnesses given action identifiers (message hashes). **Signature:** ``` getInterface( address: CompleteAddress, chainInfo: ChainInfo ): AccountInterface ``` **Parameters:** * `address`: `CompleteAddress` * Address of this account contract. * `chainInfo`: `ChainInfo` * Chain id and version of the rollup where the account contract is initialized / published. **Returns:** `AccountInterface` - An account interface instance for creating tx requests and authorizing actions. ##### getAuthWitnessProvider[​](#getauthwitnessprovider "Direct link to getAuthWitnessProvider") Returns the auth witness provider for the given address. **Signature:** ``` getAuthWitnessProvider(address: CompleteAddress): AuthWitnessProvider ``` **Parameters:** * `address`: `CompleteAddress` * Address for which to create auth witnesses. **Returns:** `AuthWitnessProvider` #### getAccountContractAddress[​](#getaccountcontractaddress "Direct link to getAccountContractAddress") **Type:** Function Compute the address of an account contract from secret and salt. **Signature:** ``` export async getAccountContractAddress( accountContract: AccountContract, secret: Fr, salt: Fr ) ``` **Parameters:** * `accountContract`: `AccountContract` * `secret`: `Fr` * `salt`: `Fr` **Returns:** `Promise` *** ### `account/account_with_secret_key.ts`[​](#accountaccount_with_secret_keyts "Direct link to accountaccount_with_secret_keyts") #### AccountWithSecretKey[​](#accountwithsecretkey "Direct link to AccountWithSecretKey") **Type:** Class Extends Account with the encryption private key. Not required for implementing the wallet interface but useful for testing purposes or exporting an account to another pxe. **Extends:** `BaseAccount` #### Constructor[​](#constructor "Direct link to Constructor") **Signature:** ``` constructor( account: AccountInterface, private secretKey: Fr, public readonly salt: Salt ) ``` **Parameters:** * `account`: `AccountInterface` * `secretKey`: `Fr` * `salt`: `Salt` * Deployment salt for this account contract. #### Methods[​](#methods-1 "Direct link to Methods") ##### getSecretKey[​](#getsecretkey "Direct link to getSecretKey") Returns the encryption private key associated with this account. **Signature:** ``` public getSecretKey() ``` **Returns:** `Fr` ##### getEncryptionSecret[​](#getencryptionsecret "Direct link to getEncryptionSecret") Returns the encryption secret, the secret of the encryption point—the point that others use to encrypt messages to this account note - this ensures that the address secret always corresponds to an address point with y being positive dev - this is also referred to as the address secret, which decrypts payloads encrypted to an address point **Signature:** ``` public async getEncryptionSecret() ``` **Returns:** `Promise` *** ### `account/account.ts`[​](#accountaccountts "Direct link to accountaccountts") #### Account[​](#account-1 "Direct link to Account") **Type:** Type Alias A type defining an account, capable of both creating authwits and using them to authenticate transaction execution requests. **Signature:** ``` export type Account = AccountInterface & AuthwitnessIntentProvider; ``` #### BaseAccount[​](#baseaccount "Direct link to BaseAccount") **Type:** Class An account implementation that uses authwits as an authentication mechanism and can assemble transaction execution requests for an entrypoint. **Implements:** `Account` #### Constructor[​](#constructor-1 "Direct link to Constructor") **Signature:** ``` constructor(protected account: AccountInterface) ``` **Parameters:** * `account`: `AccountInterface` #### Methods[​](#methods-2 "Direct link to Methods") ##### createTxExecutionRequest[​](#createtxexecutionrequest "Direct link to createTxExecutionRequest") **Signature:** ``` createTxExecutionRequest( exec: ExecutionPayload, gasSettings: GasSettings, options: DefaultAccountEntrypointOptions ): Promise ``` **Parameters:** * `exec`: `ExecutionPayload` * `gasSettings`: `GasSettings` * `options`: `DefaultAccountEntrypointOptions` **Returns:** `Promise` ##### getChainId[​](#getchainid "Direct link to getChainId") **Signature:** ``` getChainId(): Fr ``` **Returns:** `Fr` ##### getVersion[​](#getversion "Direct link to getVersion") **Signature:** ``` getVersion(): Fr ``` **Returns:** `Fr` ##### getCompleteAddress[​](#getcompleteaddress "Direct link to getCompleteAddress") Returns the complete address of the account that implements this wallet. **Signature:** ``` public getCompleteAddress() ``` **Returns:** `CompleteAddress` ##### getAddress[​](#getaddress "Direct link to getAddress") Returns the address of the account that implements this wallet. **Signature:** ``` public getAddress() ``` **Returns:** `any` ##### createAuthWit[​](#createauthwit "Direct link to createAuthWit") Computes an authentication witness from either a message hash or an intent. If a message hash is provided, it will create a witness for the hash directly. Otherwise, it will compute the message hash using the intent, along with the chain id and the version values provided by the wallet. **Signature:** ``` async createAuthWit(messageHashOrIntent: Fr | Buffer | CallIntent | IntentInnerHash): Promise ``` **Parameters:** * `messageHashOrIntent`: `Fr | Buffer | CallIntent | IntentInnerHash` * The message hash of the intent to approve **Returns:** `Promise` - The authentication witness *** ### `account/interface.ts`[​](#accountinterfacets "Direct link to accountinterfacets") #### AccountInterface[​](#accountinterface "Direct link to AccountInterface") **Type:** Interface Handler for interfacing with an account. Knows how to create transaction execution requests and authorize actions for its corresponding account. **Extends:** `EntrypointInterface`, `AuthWitnessProvider` #### Methods[​](#methods-3 "Direct link to Methods") ##### getCompleteAddress[​](#getcompleteaddress-1 "Direct link to getCompleteAddress") Returns the complete address for this account. **Signature:** ``` getCompleteAddress(): CompleteAddress ``` **Returns:** `CompleteAddress` ##### getAddress[​](#getaddress-1 "Direct link to getAddress") Returns the address for this account. **Signature:** ``` getAddress(): AztecAddress ``` **Returns:** `AztecAddress` ##### getChainId[​](#getchainid-1 "Direct link to getChainId") Returns the chain id for this account **Signature:** ``` getChainId(): Fr ``` **Returns:** `Fr` ##### getVersion[​](#getversion-1 "Direct link to getVersion") Returns the rollup version for this account **Signature:** ``` getVersion(): Fr ``` **Returns:** `Fr` *** ### `account/signerless_account.ts`[​](#accountsignerless_accountts "Direct link to accountsignerless_accountts") #### SignerlessAccount[​](#signerlessaccount "Direct link to SignerlessAccount") **Type:** Class Account implementation which creates a transaction using the multicall protocol contract as entrypoint. **Implements:** `Account` #### Constructor[​](#constructor-2 "Direct link to Constructor") **Signature:** ``` constructor(chainInfo: ChainInfo) ``` **Parameters:** * `chainInfo`: `ChainInfo` #### Methods[​](#methods-4 "Direct link to Methods") ##### createTxExecutionRequest[​](#createtxexecutionrequest-1 "Direct link to createTxExecutionRequest") **Signature:** ``` createTxExecutionRequest( exec: ExecutionPayload, gasSettings: GasSettings ): Promise ``` **Parameters:** * `exec`: `ExecutionPayload` * `gasSettings`: `GasSettings` **Returns:** `Promise` ##### getChainId[​](#getchainid-2 "Direct link to getChainId") **Signature:** ``` getChainId(): Fr ``` **Returns:** `Fr` ##### getVersion[​](#getversion-2 "Direct link to getVersion") **Signature:** ``` getVersion(): Fr ``` **Returns:** `Fr` ##### getCompleteAddress[​](#getcompleteaddress-2 "Direct link to getCompleteAddress") **Signature:** ``` getCompleteAddress(): CompleteAddress ``` **Returns:** `CompleteAddress` ##### getAddress[​](#getaddress-2 "Direct link to getAddress") **Signature:** ``` getAddress(): AztecAddress ``` **Returns:** `AztecAddress` ##### createAuthWit[​](#createauthwit-1 "Direct link to createAuthWit") **Signature:** ``` createAuthWit(_intent: Fr | Buffer | IntentInnerHash | CallIntent): Promise ``` **Parameters:** * `_intent`: `Fr | Buffer | IntentInnerHash | CallIntent` **Returns:** `Promise` ## Authorization[​](#authorization "Direct link to Authorization") *** ### `authorization/call_authorization_request.ts`[​](#authorizationcall_authorization_requestts "Direct link to authorizationcall_authorization_requestts") #### CallAuthorizationRequest[​](#callauthorizationrequest "Direct link to CallAuthorizationRequest") **Type:** Class An authwit request for a function call. Includes the preimage of the data to be signed, as opposed of just the inner hash. #### Constructor[​](#constructor-3 "Direct link to Constructor") **Signature:** ``` constructor( public selector: AuthorizationSelector, public innerHash: Fr, public msgSender: AztecAddress, public functionSelector: FunctionSelector, public argsHash: Fr, public args: Fr[] ) ``` **Parameters:** * `selector`: `AuthorizationSelector` * The selector of the authwit type, used to identify it when emitted from `emit_offchain_effect`oracle. Computed as poseidon2("CallAuthwit((Field),(u32),Field)".to\_bytes()) * `innerHash`: `Fr` * The inner hash of the authwit, computed as poseidon2(\[msg\_sender, selector, args\_hash]) * `msgSender`: `AztecAddress` * The address performing the call * `functionSelector`: `FunctionSelector` * The selector of the function that is to be authorized * `argsHash`: `Fr` * The hash of the arguments to the function call, * `args`: `Fr[]` * The arguments to the function call. #### Methods[​](#methods-5 "Direct link to Methods") ##### getSelector[​](#getselector "Direct link to getSelector") **Signature:** ``` static getSelector(): Promise ``` **Returns:** `Promise` ##### fromFields[​](#fromfields "Direct link to fromFields") **Signature:** ``` static async fromFields(fields: Fr[]): Promise ``` **Parameters:** * `fields`: `Fr[]` **Returns:** `Promise` ## Contract[​](#contract "Direct link to Contract") *** ### `contract/base_contract_interaction.ts`[​](#contractbase_contract_interactionts "Direct link to contractbase_contract_interactionts") #### BaseContractInteraction[​](#basecontractinteraction "Direct link to BaseContractInteraction") **Type:** Class Base class for an interaction with a contract, be it a deployment, a function call, or a batch. Implements the sequence create/simulate/send. #### Constructor[​](#constructor-4 "Direct link to Constructor") **Signature:** ``` constructor( protected wallet: Wallet, protected authWitnesses: AuthWitness[] = [], protected capsules: Capsule[] = [] ) ``` **Parameters:** * `wallet`: `Wallet` * `authWitnesses` (optional): `AuthWitness[]` * `capsules` (optional): `Capsule[]` #### Properties[​](#properties "Direct link to Properties") ##### log[​](#log "Direct link to log") **Type:** `any` #### Methods[​](#methods-6 "Direct link to Methods") ##### request[​](#request "Direct link to request") Returns an execution request that represents this operation. Can be used as a building block for constructing batch requests. **Signature:** ``` public abstract request(options?: RequestInteractionOptions): Promise ``` **Parameters:** * `options` (optional): `RequestInteractionOptions` * An optional object containing additional configuration for the transaction. **Returns:** `Promise` - An execution request wrapped in promise. ##### send[​](#send "Direct link to send") Sends a transaction to the contract function with the specified options. This function throws an error if called on a utility function. It creates and signs the transaction if necessary, and returns a SentTx instance, which can be used to track the transaction status, receipt, and events. **Signature:** ``` public send(options: SendInteractionOptions): SentTx ``` **Parameters:** * `options`: `SendInteractionOptions` * An object containing 'from' property representing the AztecAddress of the sender and optional fee configuration **Returns:** `SentTx` - A SentTx instance for tracking the transaction status and information. *** ### `contract/batch_call.ts`[​](#contractbatch_callts "Direct link to contractbatch_callts") #### BatchCall[​](#batchcall "Direct link to BatchCall") **Type:** Class A batch of function calls to be sent as a single transaction through a wallet. **Extends:** `BaseContractInteraction` #### Constructor[​](#constructor-5 "Direct link to Constructor") **Signature:** ``` constructor( wallet: Wallet, protected interactions: (BaseContractInteraction | ExecutionPayload)[] ) ``` **Parameters:** * `wallet`: `Wallet` * `interactions`: `(BaseContractInteraction | ExecutionPayload)[]` #### Methods[​](#methods-7 "Direct link to Methods") ##### request[​](#request-1 "Direct link to request") Returns an execution request that represents this operation. **Signature:** ``` public async request(options: RequestInteractionOptions = {}): Promise ``` **Parameters:** * `options` (optional): `RequestInteractionOptions` * An optional object containing additional configuration for the request generation. **Returns:** `Promise` - An execution payload wrapped in promise. ##### simulate[​](#simulate "Direct link to simulate") Simulates the batch, supporting private, public and utility functions. Although this is a single interaction with the wallet, private and public functions will be grouped into a single ExecutionPayload that the wallet will simulate as a single transaction. Utility function calls will simply be executed one by one. **Signature:** ``` public async simulate(options: SimulateInteractionOptions): Promise ``` **Parameters:** * `options`: `SimulateInteractionOptions` * An optional object containing additional configuration for the interaction. **Returns:** `Promise` - The results of all the interactions that make up the batch ##### getExecutionPayloads[​](#getexecutionpayloads "Direct link to getExecutionPayloads") **Signature:** ``` protected async getExecutionPayloads(): Promise ``` **Returns:** `Promise` *** ### `contract/checker.ts`[​](#contractcheckerts "Direct link to contractcheckerts") #### abiChecker[​](#abichecker "Direct link to abiChecker") **Type:** Function Validates the given ContractArtifact object by checking its functions and their parameters. Ensures that the ABI has at least one function, a constructor, valid bytecode, and correct parameter types. Throws an error if any inconsistency is detected during the validation process. **Signature:** ``` export abiChecker(artifact: ContractArtifact) ``` **Parameters:** * `artifact`: `ContractArtifact` * The ContractArtifact object to be validated. **Returns:** `boolean` - A boolean value indicating whether the artifact is valid or not. *** ### `contract/contract_base.ts`[​](#contractcontract_basets "Direct link to contractcontract_basets") #### ContractMethod[​](#contractmethod "Direct link to ContractMethod") **Type:** Type Alias Type representing a contract method that returns a ContractFunctionInteraction instance and has a readonly 'selector' property of type Buffer. Takes any number of arguments. **Signature:** ``` export type ContractMethod = ((...args: any[]) => ContractFunctionInteraction) & { selector: () => Promise; }; ``` **Type Members:** ##### selector[​](#selector "Direct link to selector") The unique identifier for a contract function in bytecode. **Type:** `() => Promise` #### ContractStorageLayout[​](#contractstoragelayout "Direct link to ContractStorageLayout") **Type:** Type Alias Type representing the storage layout of a contract. **Signature:** ``` export type ContractStorageLayout = { [K in T]: FieldLayout; }; ``` **Type Members:** ##### \[K in T][​](#k-in-t "Direct link to \[K in T]") **Signature:** `[K in T]: FieldLayout` **Key Type:** `T` **Value Type:** `FieldLayout` #### ContractBase[​](#contractbase "Direct link to ContractBase") **Type:** Class Abstract implementation of a contract extended by the Contract class and generated contract types. #### Constructor[​](#constructor-6 "Direct link to Constructor") **Signature:** ``` protected constructor( public readonly address: AztecAddress, public readonly artifact: ContractArtifact, public wallet: Wallet ) ``` **Parameters:** * `address`: `AztecAddress` * The contract's address. * `artifact`: `ContractArtifact` * The Application Binary Interface for the contract. * `wallet`: `Wallet` * The wallet used for interacting with this contract. #### Properties[​](#properties-1 "Direct link to Properties") ##### methods[​](#methods-8 "Direct link to methods") An object containing contract methods mapped to their respective names. **Type:** `{ [name: string]: ContractMethod }` #### Methods[​](#methods-9 "Direct link to Methods") ##### withWallet[​](#withwallet "Direct link to withWallet") Creates a new instance of the contract wrapper attached to a different wallet. **Signature:** ``` public withWallet(wallet: Wallet): this ``` **Parameters:** * `wallet`: `Wallet` * Wallet to use for sending txs. **Returns:** `this` - A new contract instance. *** ### `contract/contract_function_interaction.ts`[​](#contractcontract_function_interactionts "Direct link to contractcontract_function_interactionts") #### ContractFunctionInteraction[​](#contractfunctioninteraction "Direct link to ContractFunctionInteraction") **Type:** Class This is the class that is returned when calling e.g. `contract.methods.myMethod(arg0, arg1)`. It contains available interactions one can call on a method, including view. **Extends:** `BaseContractInteraction` #### Constructor[​](#constructor-7 "Direct link to Constructor") **Signature:** ``` constructor( wallet: Wallet, protected contractAddress: AztecAddress, protected functionDao: FunctionAbi, protected args: any[], authWitnesses: AuthWitness[] = [], capsules: Capsule[] = [], private extraHashedArgs: HashedValues[] = [] ) ``` **Parameters:** * `wallet`: `Wallet` * `contractAddress`: `AztecAddress` * `functionDao`: `FunctionAbi` * `args`: `any[]` * `authWitnesses` (optional): `AuthWitness[]` * `capsules` (optional): `Capsule[]` * `extraHashedArgs` (optional): `HashedValues[]` #### Methods[​](#methods-10 "Direct link to Methods") ##### getFunctionCall[​](#getfunctioncall "Direct link to getFunctionCall") Returns the encoded function call wrapped by this interaction Useful when generating authwits **Signature:** ``` public async getFunctionCall() ``` **Returns:** `Promise<{ name: any; args: any; selector: any; type: any; to: AztecAddress; isStatic: any; hideMsgSender: boolean; returnTypes: any; }>` - An encoded function call ##### request[​](#request-2 "Direct link to request") Returns the execution payload that allows this operation to happen on chain. **Signature:** ``` public override async request(options: RequestInteractionOptions = {}): Promise ``` **Parameters:** * `options` (optional): `RequestInteractionOptions` * Configuration options. **Returns:** `Promise` - The execution payload for this operation ##### simulate[​](#simulate-1 "Direct link to simulate") Simulate a transaction and get information from its execution. Differs from prove in a few important ways: 1. It returns the values of the function execution, plus additional metadata if requested 2. It supports `utility`, `private` and `public` functions **Signature:** ``` public async simulate(options: T): Promise['estimateGas']>> ``` **Parameters:** * `options`: `T` * An optional object containing additional configuration for the simulation. **Returns:** `Promise['estimateGas']>>` - Depending on the simulation options, this method directly returns the result value of the executed function or a rich object containing extra metadata, such as estimated gas costs (if requested via options), execution statistics and emitted offchain effects ##### simulate[​](#simulate-2 "Direct link to simulate") **Signature:** ``` public async simulate(options: T): Promise> ``` **Parameters:** * `options`: `T` **Returns:** `Promise>` ##### simulate[​](#simulate-3 "Direct link to simulate") **Signature:** ``` public async simulate(options: SimulateInteractionOptions): Promise> ``` **Parameters:** * `options`: `SimulateInteractionOptions` **Returns:** `Promise>` ##### profile[​](#profile "Direct link to profile") Simulate a transaction and profile the gate count for each function in the transaction. **Signature:** ``` public async profile(options: ProfileInteractionOptions): Promise ``` **Parameters:** * `options`: `ProfileInteractionOptions` * Same options as `simulate`, plus profiling method **Returns:** `Promise` - An object containing the function return value and profile result. ##### with[​](#with "Direct link to with") Augments this ContractFunctionInteraction with additional metadata, such as authWitnesses, capsules, and extraHashedArgs. This is useful when creating a "batteries included" interaction, such as registering a contract class with its associated capsule instead of having the user provide them externally. **Signature:** ``` public with({ authWitnesses = [], capsules = [], extraHashedArgs = [], }: { authWitnesses?: AuthWitness[]; capsules?: Capsule[]; extraHashedArgs?: HashedValues[]; }): ContractFunctionInteraction ``` **Parameters:** * `{ authWitnesses = [], capsules = [], extraHashedArgs = [], }`: `{ /** The authWitnesses to add to the interaction */ authWitnesses?: AuthWitness[]; /** The capsules to add to the interaction */ capsules?: Capsule[]; /** The extra hashed args to add to the interaction */ extraHashedArgs?: HashedValues[]; }` **Returns:** `ContractFunctionInteraction` - A new ContractFunctionInteraction with the added metadata, but calling the same original function in the same manner *** ### `contract/contract.ts`[​](#contractcontractts "Direct link to contractcontractts") #### Contract[​](#contract-1 "Direct link to Contract") **Type:** Class The Contract class represents a contract and provides utility methods for interacting with it. It enables the creation of ContractFunctionInteraction instances for each function in the contract's ABI, allowing users to call or send transactions to these functions. Additionally, the Contract class can be used to attach the contract instance to a deployed contract onchain through the PXE, which facilitates interaction with Aztec's privacy protocol. **Extends:** `ContractBase` #### Methods[​](#methods-11 "Direct link to Methods") ##### at[​](#at "Direct link to at") Gets a contract instance. **Signature:** ``` public static at( address: AztecAddress, artifact: ContractArtifact, wallet: Wallet ): Contract ``` **Parameters:** * `address`: `AztecAddress` * The address of the contract instance. * `artifact`: `ContractArtifact` * Build artifact of the contract. * `wallet`: `Wallet` * The wallet to use when interacting with the contract. **Returns:** `Contract` - A promise that resolves to a new Contract instance. ##### deploy[​](#deploy "Direct link to deploy") Creates a tx to deploy (initialize and/or publish) a new instance of a contract. **Signature:** ``` public static deploy( wallet: Wallet, artifact: ContractArtifact, args: any[], constructorName?: string ) ``` **Parameters:** * `wallet`: `Wallet` * The wallet for executing the deployment. * `artifact`: `ContractArtifact` * Build artifact of the contract to deploy * `args`: `any[]` * Arguments for the constructor. * `constructorName` (optional): `string` * The name of the constructor function to call. **Returns:** `DeployMethod` ##### deployWithPublicKeys[​](#deploywithpublickeys "Direct link to deployWithPublicKeys") Creates a tx to deploy (initialize and/or publish) a new instance of a contract using the specified public keys hash to derive the address. **Signature:** ``` public static deployWithPublicKeys( publicKeys: PublicKeys, wallet: Wallet, artifact: ContractArtifact, args: any[], constructorName?: string ) ``` **Parameters:** * `publicKeys`: `PublicKeys` * Hash of public keys to use for deriving the address. * `wallet`: `Wallet` * The wallet for executing the deployment. * `artifact`: `ContractArtifact` * Build artifact of the contract. * `args`: `any[]` * Arguments for the constructor. * `constructorName` (optional): `string` * The name of the constructor function to call. **Returns:** `DeployMethod` *** ### `contract/deploy_method.ts`[​](#contractdeploy_methodts "Direct link to contractdeploy_methodts") #### RequestDeployOptions[​](#requestdeployoptions "Direct link to RequestDeployOptions") **Type:** Type Alias Options for deploying a contract on the Aztec network. Allows specifying a contract address salt and different options to tweak contract publication and initialization **Signature:** ``` export type RequestDeployOptions = RequestInteractionOptions & { contractAddressSalt?: Fr; deployer?: AztecAddress; skipClassPublication?: boolean; skipInstancePublication?: boolean; skipInitialization?: boolean; skipRegistration?: boolean; }; ``` **Type Members:** ##### contractAddressSalt[​](#contractaddresssalt "Direct link to contractAddressSalt") An optional salt value used to deterministically calculate the contract address. **Type:** `Fr` ##### deployer[​](#deployer "Direct link to deployer") Deployer address that will be used for the deployed contract's address computation. If set to 0, the sender's address won't be mixed in **Type:** `AztecAddress` ##### skipClassPublication[​](#skipclasspublication "Direct link to skipClassPublication") Skip contract class publication. **Type:** `boolean` ##### skipInstancePublication[​](#skipinstancepublication "Direct link to skipInstancePublication") Skip publication, instead just privately initialize the contract. **Type:** `boolean` ##### skipInitialization[​](#skipinitialization "Direct link to skipInitialization") Skip contract initialization. **Type:** `boolean` ##### skipRegistration[​](#skipregistration "Direct link to skipRegistration") Skip contract registration in the wallet **Type:** `boolean` #### DeployOptions[​](#deployoptions "Direct link to DeployOptions") **Type:** Type Alias Extends the deployment options with the required parameters to send the transaction **Signature:** ``` export type DeployOptions = Omit & { universalDeploy?: boolean; } & Pick; ``` **Type Members:** ##### universalDeploy[​](#universaldeploy "Direct link to universalDeploy") Set to true to *not* include the sender in the address computation. This option is mutually exclusive with "deployer" **Type:** `boolean` #### SimulateDeployOptions[​](#simulatedeployoptions "Direct link to SimulateDeployOptions") **Type:** Type Alias Options for simulating the deployment of a contract Allows skipping certain validations and computing gas estimations **Signature:** ``` export type SimulateDeployOptions = Omit & { fee?: SimulationInteractionFeeOptions; skipTxValidation?: boolean; skipFeeEnforcement?: boolean; includeMetadata?: boolean; }; ``` **Type Members:** ##### fee[​](#fee "Direct link to fee") The fee options for the transaction. **Type:** `SimulationInteractionFeeOptions` ##### skipTxValidation[​](#skiptxvalidation "Direct link to skipTxValidation") Simulate without checking for the validity of the resulting transaction, e.g. whether it emits any existing nullifiers. **Type:** `boolean` ##### skipFeeEnforcement[​](#skipfeeenforcement "Direct link to skipFeeEnforcement") Whether to ensure the fee payer is not empty and has enough balance to pay for the fee. **Type:** `boolean` ##### includeMetadata[​](#includemetadata "Direct link to includeMetadata") Whether to include metadata such as offchain effects and performance statistics (e.g. timing information of the different circuits and oracles) in the simulation result, instead of just the return value of the function **Type:** `boolean` #### DeployMethod[​](#deploymethod "Direct link to DeployMethod") **Type:** Class Contract interaction for deployment. Handles class publication, instance publication, and initialization of the contract. Note that for some contracts, a tx is not required as part of its "creation": If there are no public functions, and if there are no initialization functions, then technically the contract has already been "created", and all of the contract's functions (private and utility) can be interacted-with immediately, without any "deployment tx". Extends the BaseContractInteraction class. **Extends:** `BaseContractInteraction` #### Constructor[​](#constructor-8 "Direct link to Constructor") **Signature:** ``` constructor( private publicKeys: PublicKeys, wallet: Wallet, protected artifact: ContractArtifact, protected postDeployCtor: (instance: ContractInstanceWithAddress, wallet: Wallet) => TContract, private args: any[] = [], constructorNameOrArtifact?: string | FunctionArtifact, authWitnesses: AuthWitness[] = [], capsules: Capsule[] = [] ) ``` **Parameters:** * `publicKeys`: `PublicKeys` * `wallet`: `Wallet` * `artifact`: `ContractArtifact` * `postDeployCtor`: `(instance: ContractInstanceWithAddress, wallet: Wallet) => TContract` * `args` (optional): `any[]` * `constructorNameOrArtifact` (optional): `string | FunctionArtifact` * `authWitnesses` (optional): `AuthWitness[]` * `capsules` (optional): `Capsule[]` #### Methods[​](#methods-12 "Direct link to Methods") ##### request[​](#request-3 "Direct link to request") Returns the execution payload that allows this operation to happen on chain. **Signature:** ``` public async request(options?: RequestDeployOptions): Promise ``` **Parameters:** * `options` (optional): `RequestDeployOptions` * Configuration options. **Returns:** `Promise` - The execution payload for this operation ##### convertDeployOptionsToRequestOptions[​](#convertdeployoptionstorequestoptions "Direct link to convertDeployOptionsToRequestOptions") **Signature:** ``` convertDeployOptionsToRequestOptions(options: DeployOptions): RequestDeployOptions ``` **Parameters:** * `options`: `DeployOptions` **Returns:** `RequestDeployOptions` ##### register[​](#register "Direct link to register") Adds this contract to the wallet and returns the Contract object. **Signature:** ``` public async register(options?: RequestDeployOptions): Promise ``` **Parameters:** * `options` (optional): `RequestDeployOptions` * Deployment options. **Returns:** `Promise` ##### getPublicationExecutionPayload[​](#getpublicationexecutionpayload "Direct link to getPublicationExecutionPayload") Returns an execution payload for: - publication of the contract class and - publication of the contract instance to enable public execution depending on the provided options. **Signature:** ``` protected async getPublicationExecutionPayload(options?: RequestDeployOptions): Promise ``` **Parameters:** * `options` (optional): `RequestDeployOptions` * Contract creation options. **Returns:** `Promise` - An execution payload with potentially calls (and bytecode capsule) to the class registry and instance registry. ##### getInitializationExecutionPayload[​](#getinitializationexecutionpayload "Direct link to getInitializationExecutionPayload") Returns the calls necessary to initialize the contract. **Signature:** ``` protected async getInitializationExecutionPayload(options?: RequestDeployOptions): Promise ``` **Parameters:** * `options` (optional): `RequestDeployOptions` * Deployment options. **Returns:** `Promise` - An array of function calls. ##### send[​](#send-1 "Direct link to send") Send a contract deployment transaction (initialize and/or publish) using the provided options. This function extends the 'send' method from the ContractFunctionInteraction class, allowing us to send a transaction specifically for contract deployment. **Signature:** ``` public override send(options: DeployOptions): DeploySentTx ``` **Parameters:** * `options`: `DeployOptions` * An object containing various deployment options such as contractAddressSalt and from. **Returns:** `DeploySentTx` - A SentTx object that returns the receipt and the deployed contract instance. ##### getInstance[​](#getinstance "Direct link to getInstance") Builds the contract instance and returns it. **Signature:** ``` public async getInstance(options?: RequestDeployOptions): Promise ``` **Parameters:** * `options` (optional): `RequestDeployOptions` * An object containing various initialization and publication options. **Returns:** `Promise` - An instance object. ##### simulate[​](#simulate-4 "Direct link to simulate") Simulate the deployment **Signature:** ``` public async simulate(options: SimulateDeployOptions): Promise> ``` **Parameters:** * `options`: `SimulateDeployOptions` * An optional object containing additional configuration for the simulation. **Returns:** `Promise>` - A simulation result object containing metadata of the execution, including gas estimations (if requested via options), execution statistics and emitted offchain effects ##### profile[​](#profile-1 "Direct link to profile") Simulate a deployment and profile the gate count for each function in the transaction. **Signature:** ``` public async profile(options: DeployOptions & ProfileInteractionOptions): Promise ``` **Parameters:** * `options`: `DeployOptions & ProfileInteractionOptions` * Same options as `send`, plus extra profiling options. **Returns:** `Promise` - An object containing the function return value and profile result. ##### with[​](#with-1 "Direct link to with") Augments this DeployMethod with additional metadata, such as authWitnesses and capsules. **Signature:** ``` public with({ authWitnesses = [], capsules = [], }: { authWitnesses?: AuthWitness[]; capsules?: Capsule[]; }): DeployMethod ``` **Parameters:** * `{ authWitnesses = [], capsules = [], }`: `{ /** The authWitnesses to add to the deployment */ authWitnesses?: AuthWitness[]; /** The capsules to add to the deployment */ capsules?: Capsule[]; }` **Returns:** `DeployMethod` - A new DeployMethod with the added metadata, but calling the same original function in the same manner #### Getters[​](#getters "Direct link to Getters") ##### address (getter)[​](#address-getter "Direct link to address (getter)") Return this deployment address. **Signature:** ``` public get address() { ``` **Returns:** `any` ##### partialAddress (getter)[​](#partialaddress-getter "Direct link to partialAddress (getter)") Returns the partial address for this deployment. **Signature:** ``` public get partialAddress() { ``` **Returns:** `any` *** ### `contract/deploy_sent_tx.ts`[​](#contractdeploy_sent_txts "Direct link to contractdeploy_sent_txts") #### DeployedWaitOpts[​](#deployedwaitopts "Direct link to DeployedWaitOpts") **Type:** Type Alias Options related to waiting for a deployment tx. **Signature:** ``` export type DeployedWaitOpts = WaitOpts & { wallet?: Wallet; }; ``` **Type Members:** ##### wallet[​](#wallet "Direct link to wallet") Wallet to use for creating a contract instance. Uses the one set in the deployer constructor if not set. **Type:** `Wallet` #### DeployTxReceipt[​](#deploytxreceipt "Direct link to DeployTxReceipt") **Type:** Type Alias Extends a transaction receipt with a contract instance that represents the newly deployed contract. **Signature:** ``` export type DeployTxReceipt = FieldsOf & { contract: TContract; instance: ContractInstanceWithAddress; }; ``` **Type Members:** ##### contract[​](#contract-2 "Direct link to contract") Instance of the newly deployed contract. **Type:** `TContract` ##### instance[​](#instance "Direct link to instance") The deployed contract instance with address and metadata. **Type:** `ContractInstanceWithAddress` #### DeploySentTx[​](#deploysenttx "Direct link to DeploySentTx") **Type:** Class A contract deployment transaction sent to the network, extending SentTx with methods to publish a contract instance. **Extends:** `SentTx` #### Constructor[​](#constructor-9 "Direct link to Constructor") **Signature:** ``` constructor( wallet: Wallet, sendTx: () => Promise, private postDeployCtor: (instance: ContractInstanceWithAddress, wallet: Wallet) => TContract, private instanceGetter: () => Promise ) ``` **Parameters:** * `wallet`: `Wallet` * `sendTx`: `() => Promise` * `postDeployCtor`: `(instance: ContractInstanceWithAddress, wallet: Wallet) => TContract` * `instanceGetter`: `() => Promise` * A getter for the deployed contract instance #### Methods[​](#methods-13 "Direct link to Methods") ##### getInstance[​](#getinstance-1 "Direct link to getInstance") Returns the contract instance for this deployment. **Signature:** ``` public async getInstance(): Promise ``` **Returns:** `Promise` - The deployed contract instance with address and metadata. ##### deployed[​](#deployed "Direct link to deployed") Awaits for the tx to be mined and returns the contract instance. Throws if tx is not mined. **Signature:** ``` public async deployed(opts?: DeployedWaitOpts): Promise ``` **Parameters:** * `opts` (optional): `DeployedWaitOpts` * Options for configuring the waiting for the tx to be mined. **Returns:** `Promise` - The deployed contract instance. ##### wait[​](#wait "Direct link to wait") Awaits for the tx to be mined and returns the receipt along with a contract instance. Throws if tx is not mined. **Signature:** ``` public override async wait(opts?: DeployedWaitOpts): Promise> ``` **Parameters:** * `opts` (optional): `DeployedWaitOpts` * Options for configuring the waiting for the tx to be mined. **Returns:** `Promise>` - The transaction receipt with the deployed contract instance. *** ### `contract/get_gas_limits.ts`[​](#contractget_gas_limitsts "Direct link to contractget_gas_limitsts") #### getGasLimits[​](#getgaslimits "Direct link to getGasLimits") **Type:** Function Returns suggested total and teardown gas limits for a simulated tx. **Signature:** ``` export getGasLimits( simulationResult: TxSimulationResult, pad = 0.1 ): { gasLimits: Gas; teardownGasLimits: Gas; } ``` **Parameters:** * `simulationResult`: `TxSimulationResult` * `pad` (optional): `any` * Percentage to pad the suggested gas limits by, (as decimal, e.g., 0.10 for 10%). **Returns:** ``` { /** * Gas limit for the tx, excluding teardown gas */ gasLimits: Gas; /** * Gas limit for the teardown phase */ teardownGasLimits: Gas; } ``` *** ### `contract/interaction_options.ts`[​](#contractinteraction_optionsts "Direct link to contractinteraction_optionsts") #### FeeEstimationOptions[​](#feeestimationoptions "Direct link to FeeEstimationOptions") **Type:** Type Alias Options used to tweak the simulation and add gas estimation capabilities **Signature:** ``` export type FeeEstimationOptions = { estimateGas?: boolean; estimatedGasPadding?: number; }; ``` **Type Members:** ##### estimateGas[​](#estimategas "Direct link to estimateGas") Whether to modify the fee settings of the simulation with high gas limit to figure out actual gas settings. **Type:** `boolean` ##### estimatedGasPadding[​](#estimatedgaspadding "Direct link to estimatedGasPadding") Percentage to pad the estimated gas limits by, if empty, defaults to 0.1. Only relevant if estimateGas is set. **Type:** `number` #### FeePaymentMethodOption[​](#feepaymentmethodoption "Direct link to FeePaymentMethodOption") **Type:** Type Alias Interactions allow configuring a custom fee payment method that gets bundled with the transaction before sending it to the wallet **Signature:** ``` export type FeePaymentMethodOption = { paymentMethod?: FeePaymentMethod; }; ``` **Type Members:** ##### paymentMethod[​](#paymentmethod "Direct link to paymentMethod") Fee payment method to embed in the interaction **Type:** `FeePaymentMethod` #### GasSettingsOption[​](#gassettingsoption "Direct link to GasSettingsOption") **Type:** Type Alias User-defined partial gas settings for the interaction. This type is completely optional since the wallet will fill in the missing options **Signature:** ``` export type GasSettingsOption = { gasSettings?: Partial>; }; ``` **Type Members:** ##### gasSettings[​](#gassettings "Direct link to gasSettings") The gas settings **Type:** `Partial>` #### InteractionFeeOptions[​](#interactionfeeoptions "Direct link to InteractionFeeOptions") **Type:** Type Alias Fee options as set by a user. **Signature:** ``` export type InteractionFeeOptions = GasSettingsOption & FeePaymentMethodOption; ``` #### SimulationInteractionFeeOptions[​](#simulationinteractionfeeoptions "Direct link to SimulationInteractionFeeOptions") **Type:** Type Alias Fee options that can be set for simulation *only* **Signature:** ``` export type SimulationInteractionFeeOptions = InteractionFeeOptions & FeeEstimationOptions; ``` #### RequestInteractionOptions[​](#requestinteractionoptions "Direct link to RequestInteractionOptions") **Type:** Type Alias Represents the options to configure a request from a contract interaction. Allows specifying additional auth witnesses and capsules to use during execution **Signature:** ``` export type RequestInteractionOptions = { authWitnesses?: AuthWitness[]; capsules?: Capsule[]; fee?: FeePaymentMethodOption; }; ``` **Type Members:** ##### authWitnesses[​](#authwitnesses "Direct link to authWitnesses") Extra authwits to use during execution **Type:** `AuthWitness[]` ##### capsules[​](#capsules "Direct link to capsules") Extra capsules to use during execution **Type:** `Capsule[]` ##### fee[​](#fee-1 "Direct link to fee") Fee payment method to embed in the interaction request **Type:** `FeePaymentMethodOption` #### SendInteractionOptions[​](#sendinteractionoptions "Direct link to SendInteractionOptions") **Type:** Type Alias Represents options for calling a (constrained) function in a contract. **Signature:** ``` export type SendInteractionOptions = RequestInteractionOptions & { from: AztecAddress; fee?: InteractionFeeOptions; }; ``` **Type Members:** ##### from[​](#from "Direct link to from") The sender's Aztec address. **Type:** `AztecAddress` ##### fee[​](#fee-2 "Direct link to fee") The fee options for the transaction. **Type:** `InteractionFeeOptions` #### SimulateInteractionOptions[​](#simulateinteractionoptions "Direct link to SimulateInteractionOptions") **Type:** Type Alias Represents the options for simulating a contract function interaction. Allows specifying the address from which the method should be called. Disregarded for simulation of public functions **Signature:** ``` export type SimulateInteractionOptions = Omit & { fee?: SimulationInteractionFeeOptions; skipTxValidation?: boolean; skipFeeEnforcement?: boolean; includeMetadata?: boolean; }; ``` **Type Members:** ##### fee[​](#fee-3 "Direct link to fee") The fee options for the transaction. **Type:** `SimulationInteractionFeeOptions` ##### skipTxValidation[​](#skiptxvalidation-1 "Direct link to skipTxValidation") Simulate without checking for the validity of the resulting transaction, e.g. whether it emits any existing nullifiers. **Type:** `boolean` ##### skipFeeEnforcement[​](#skipfeeenforcement-1 "Direct link to skipFeeEnforcement") Whether to ensure the fee payer is not empty and has enough balance to pay for the fee. **Type:** `boolean` ##### includeMetadata[​](#includemetadata-1 "Direct link to includeMetadata") Whether to include metadata such as offchain effects and performance statistics (e.g. timing information of the different circuits and oracles) in the simulation result, instead of just the return value of the function **Type:** `boolean` #### ProfileInteractionOptions[​](#profileinteractionoptions "Direct link to ProfileInteractionOptions") **Type:** Type Alias Represents the options for profiling an interaction. **Signature:** ``` export type ProfileInteractionOptions = SimulateInteractionOptions & { profileMode: 'gates' | 'execution-steps' | 'full'; skipProofGeneration?: boolean; }; ``` **Type Members:** ##### profileMode[​](#profilemode "Direct link to profileMode") Whether to return gates information or the bytecode/witnesses. **Type:** `'gates' | 'execution-steps' | 'full'` ##### skipProofGeneration[​](#skipproofgeneration "Direct link to skipProofGeneration") Whether to generate a Chonk proof or not **Type:** `boolean` #### SimulationReturn[​](#simulationreturn "Direct link to SimulationReturn") **Type:** Type Alias Represents the result type of a simulation. By default, it will just be the return value of the simulated function If `includeMetadata` is set to true in `SimulateInteractionOptions` on the input of `simulate(...)`, it will provide extra information. **Signature:** ``` export type SimulationReturn = T extends true ? { stats: SimulationStats; offchainEffects: OffchainEffect[]; result: any; estimatedGas: Pick; } : any; ``` #### toSendOptions[​](#tosendoptions "Direct link to toSendOptions") **Type:** Function Transforms and cleans up the higher level SendInteractionOptions defined by the interaction into SendOptions, which are the ones that can be serialized and forwarded to the wallet **Signature:** ``` export toSendOptions(options: SendInteractionOptions): SendOptions ``` **Parameters:** * `options`: `SendInteractionOptions` **Returns:** `SendOptions` #### toSimulateOptions[​](#tosimulateoptions "Direct link to toSimulateOptions") **Type:** Function Transforms and cleans up the higher level SimulateInteractionOptions defined by the interaction into SimulateOptions, which are the ones that can be serialized and forwarded to the wallet **Signature:** ``` export toSimulateOptions(options: SimulateInteractionOptions): SimulateOptions ``` **Parameters:** * `options`: `SimulateInteractionOptions` **Returns:** `SimulateOptions` #### toProfileOptions[​](#toprofileoptions "Direct link to toProfileOptions") **Type:** Function Transforms and cleans up the higher level ProfileInteractionOptions defined by the interaction into ProfileOptions, which are the ones that can be serialized and forwarded to the wallet **Signature:** ``` export toProfileOptions(options: ProfileInteractionOptions): ProfileOptions ``` **Parameters:** * `options`: `ProfileInteractionOptions` **Returns:** `ProfileOptions` *** ### `contract/protocol_contracts.ts`[​](#contractprotocol_contractsts "Direct link to contractprotocol_contractsts") #### getClassRegistryContract[​](#getclassregistrycontract "Direct link to getClassRegistryContract") **Type:** Function Returns a Contract wrapper for the contract class registry. **Signature:** ``` export async getClassRegistryContract(wallet: Wallet) ``` **Parameters:** * `wallet`: `Wallet` **Returns:** `Promise` #### getInstanceRegistryContract[​](#getinstanceregistrycontract "Direct link to getInstanceRegistryContract") **Type:** Function Returns a Contract wrapper for the contract instance registry. **Signature:** ``` export async getInstanceRegistryContract(wallet: Wallet) ``` **Parameters:** * `wallet`: `Wallet` **Returns:** `Promise` #### getFeeJuice[​](#getfeejuice "Direct link to getFeeJuice") **Type:** Function Returns a Contract wrapper for the fee juice contract **Signature:** ``` export async getFeeJuice(wallet: Wallet) ``` **Parameters:** * `wallet`: `Wallet` **Returns:** `Promise` *** ### `contract/sent_tx.ts`[​](#contractsent_txts "Direct link to contractsent_txts") #### WaitOpts[​](#waitopts "Direct link to WaitOpts") **Type:** Type Alias Options related to waiting for a tx. **Signature:** ``` export type WaitOpts = { ignoreDroppedReceiptsFor?: number; timeout?: number; interval?: number; dontThrowOnRevert?: boolean; }; ``` **Type Members:** ##### ignoreDroppedReceiptsFor[​](#ignoredroppedreceiptsfor "Direct link to ignoreDroppedReceiptsFor") The amount of time to ignore TxStatus.DROPPED receipts (in seconds) due to the presumption that it is being propagated by the p2p network. Defaults to 5. **Type:** `number` ##### timeout[​](#timeout "Direct link to timeout") The maximum time (in seconds) to wait for the transaction to be mined. Defaults to 60. **Type:** `number` ##### interval[​](#interval "Direct link to interval") The time interval (in seconds) between retries to fetch the transaction receipt. Defaults to 1. **Type:** `number` ##### dontThrowOnRevert[​](#dontthrowonrevert "Direct link to dontThrowOnRevert") Whether to accept a revert as a status code for the tx when waiting for it. If false, will throw if the tx reverts. **Type:** `boolean` #### DefaultWaitOpts[​](#defaultwaitopts "Direct link to DefaultWaitOpts") **Type:** Constant **Value Type:** `WaitOpts` #### SentTx[​](#senttx "Direct link to SentTx") **Type:** Class The SentTx class represents a sent transaction through the PXE (or directly to a node) providing methods to fetch its hash, receipt, and mining status. #### Constructor[​](#constructor-10 "Direct link to Constructor") **Signature:** ``` constructor( protected walletOrNode: Wallet | AztecNode, sendTx: () => Promise ) ``` **Parameters:** * `walletOrNode`: `Wallet | AztecNode` * `sendTx`: `() => Promise` #### Properties[​](#properties-2 "Direct link to Properties") ##### sendTxPromise[​](#sendtxpromise "Direct link to sendTxPromise") **Type:** `Promise` ##### sendTxError[​](#sendtxerror "Direct link to sendTxError") **Type:** `Error` ##### txHash[​](#txhash "Direct link to txHash") **Type:** `TxHash` #### Methods[​](#methods-14 "Direct link to Methods") ##### getTxHash[​](#gettxhash "Direct link to getTxHash") Retrieves the transaction hash of the SentTx instance. The function internally awaits for the 'txHashPromise' to resolve, and then returns the resolved transaction hash. **Signature:** ``` public async getTxHash(): Promise ``` **Returns:** `Promise` - A promise that resolves to the transaction hash of the SentTx instance. TODO(#7717): Don't throw here. ##### getReceipt[​](#getreceipt "Direct link to getReceipt") Retrieve the transaction receipt associated with the current SentTx instance. The function fetches the transaction hash using 'getTxHash' and then queries the PXE to get the corresponding transaction receipt. **Signature:** ``` public async getReceipt(): Promise ``` **Returns:** `Promise` - A promise that resolves to a TxReceipt object representing the fetched transaction receipt. ##### wait[​](#wait-1 "Direct link to wait") Awaits for a tx to be mined and returns the receipt. Throws if tx is not mined. **Signature:** ``` public async wait(opts?: WaitOpts): Promise> ``` **Parameters:** * `opts` (optional): `WaitOpts` * Options for configuring the waiting for the tx to be mined. **Returns:** `Promise>` - The transaction receipt. ##### waitForReceipt[​](#waitforreceipt "Direct link to waitForReceipt") **Signature:** ``` protected async waitForReceipt(opts?: WaitOpts): Promise ``` **Parameters:** * `opts` (optional): `WaitOpts` **Returns:** `Promise` *** ### `contract/unsafe_contract.ts`[​](#contractunsafe_contractts "Direct link to contractunsafe_contractts") #### UnsafeContract[​](#unsafecontract "Direct link to UnsafeContract") **Type:** Class Unsafe constructor for ContractBase that bypasses the check that the instance is registered in the wallet. **Extends:** `ContractBase` #### Constructor[​](#constructor-11 "Direct link to Constructor") **Signature:** ``` constructor( instance: ContractInstanceWithAddress, artifact: ContractArtifact, wallet: Wallet ) ``` **Parameters:** * `instance`: `ContractInstanceWithAddress` * The deployed contract instance definition. * `artifact`: `ContractArtifact` * The Application Binary Interface for the contract. * `wallet`: `Wallet` * The wallet used for interacting with this contract. *** ### `contract/wait_for_proven.ts`[​](#contractwait_for_provents "Direct link to contractwait_for_provents") #### WaitForProvenOpts[​](#waitforprovenopts "Direct link to WaitForProvenOpts") **Type:** Type Alias Options for waiting for a transaction to be proven. **Signature:** ``` export type WaitForProvenOpts = { provenTimeout?: number; interval?: number; }; ``` **Type Members:** ##### provenTimeout[​](#proventimeout "Direct link to provenTimeout") Time to wait for the tx to be proven before timing out **Type:** `number` ##### interval[​](#interval-1 "Direct link to interval") Elapsed time between polls to the node **Type:** `number` #### DefaultWaitForProvenOpts[​](#defaultwaitforprovenopts "Direct link to DefaultWaitForProvenOpts") **Type:** Constant **Value Type:** `WaitForProvenOpts` #### waitForProven[​](#waitforproven "Direct link to waitForProven") **Type:** Function Wait for a transaction to be proven by polling the node **Signature:** ``` export async waitForProven( node: AztecNode, receipt: TxReceipt, opts?: WaitForProvenOpts ) ``` **Parameters:** * `node`: `AztecNode` * `receipt`: `TxReceipt` * `opts` (optional): `WaitForProvenOpts` **Returns:** `Promise` ## Deployment[​](#deployment "Direct link to Deployment") *** ### `deployment/broadcast_function.ts`[​](#deploymentbroadcast_functionts "Direct link to deploymentbroadcast_functionts") #### broadcastPrivateFunction[​](#broadcastprivatefunction "Direct link to broadcastPrivateFunction") **Type:** Function Sets up a call to broadcast a private function's bytecode via the ClassRegistry contract. Note that this is not required for users to call the function, but is rather a convenience to make this code publicly available so dapps or wallets do not need to redistribute it. **Signature:** ``` export async broadcastPrivateFunction( wallet: Wallet, artifact: ContractArtifact, selector: FunctionSelector ): Promise ``` **Parameters:** * `wallet`: `Wallet` * Wallet to send the transaction. * `artifact`: `ContractArtifact` * Contract artifact that contains the function to be broadcast. * `selector`: `FunctionSelector` * Selector of the function to be broadcast. **Returns:** `Promise` - A ContractFunctionInteraction object that can be used to send the transaction. #### broadcastUtilityFunction[​](#broadcastutilityfunction "Direct link to broadcastUtilityFunction") **Type:** Function Sets up a call to broadcast a utility function's bytecode via the ClassRegistry contract. Note that this is not required for users to call the function, but is rather a convenience to make this code publicly available so dapps or wallets do not need to redistribute it. **Signature:** ``` export async broadcastUtilityFunction( wallet: Wallet, artifact: ContractArtifact, selector: FunctionSelector ): Promise ``` **Parameters:** * `wallet`: `Wallet` * Wallet to send the transaction. * `artifact`: `ContractArtifact` * Contract artifact that contains the function to be broadcast. * `selector`: `FunctionSelector` * Selector of the function to be broadcast. **Returns:** `Promise` - A ContractFunctionInteraction object that can be used to send the transaction. *** ### `deployment/contract_deployer.ts`[​](#deploymentcontract_deployerts "Direct link to deploymentcontract_deployerts") #### ContractDeployer[​](#contractdeployer "Direct link to ContractDeployer") **Type:** Class A class for deploying contract. #### Constructor[​](#constructor-12 "Direct link to Constructor") **Signature:** ``` constructor( private artifact: ContractArtifact, private wallet: Wallet, private publicKeys?: PublicKeys, private constructorName?: string ) ``` **Parameters:** * `artifact`: `ContractArtifact` * `wallet`: `Wallet` * `publicKeys` (optional): `PublicKeys` * `constructorName` (optional): `string` #### Methods[​](#methods-15 "Direct link to Methods") ##### deploy[​](#deploy-1 "Direct link to deploy") Deploy a contract using the provided ABI and constructor arguments. This function creates a new DeployMethod instance that can be used to send deployment transactions and query deployment status. The method accepts any number of constructor arguments, which will be passed to the contract's constructor during deployment. **Signature:** ``` public deploy(...args: any[]) ``` **Parameters:** * `args`: `any[]` * The constructor arguments for the contract being deployed. **Returns:** `DeployMethod` - A DeployMethod instance configured with the ABI, PXE, and constructor arguments. *** ### `deployment/publish_class.ts`[​](#deploymentpublish_classts "Direct link to deploymentpublish_classts") #### publishContractClass[​](#publishcontractclass "Direct link to publishContractClass") **Type:** Function Sets up a call to publish a contract class given its artifact. **Signature:** ``` export async publishContractClass( wallet: Wallet, artifact: ContractArtifact ): Promise ``` **Parameters:** * `wallet`: `Wallet` * `artifact`: `ContractArtifact` **Returns:** `Promise` *** ### `deployment/publish_instance.ts`[​](#deploymentpublish_instancets "Direct link to deploymentpublish_instancets") #### publishInstance[​](#publishinstance "Direct link to publishInstance") **Type:** Function Sets up a call to the canonical contract instance registry to publish a contract instance. **Signature:** ``` export async publishInstance( wallet: Wallet, instance: ContractInstanceWithAddress ): Promise ``` **Parameters:** * `wallet`: `Wallet` * The wallet to use for the publication (setup) tx. * `instance`: `ContractInstanceWithAddress` * The instance to publish. **Returns:** `Promise` ## Ethereum[​](#ethereum "Direct link to Ethereum") *** ### `ethereum/portal_manager.ts`[​](#ethereumportal_managerts "Direct link to ethereumportal_managerts") #### L2Claim[​](#l2claim "Direct link to L2Claim") **Type:** Type Alias L1 to L2 message info to claim it on L2. **Signature:** ``` export type L2Claim = { claimSecret: Fr; claimSecretHash: Fr; messageHash: Hex; messageLeafIndex: bigint; }; ``` **Type Members:** ##### claimSecret[​](#claimsecret "Direct link to claimSecret") Secret for claiming. **Type:** `Fr` ##### claimSecretHash[​](#claimsecrethash "Direct link to claimSecretHash") Hash of the secret for claiming. **Type:** `Fr` ##### messageHash[​](#messagehash "Direct link to messageHash") Hash of the message. **Type:** `Hex` ##### messageLeafIndex[​](#messageleafindex "Direct link to messageLeafIndex") Leaf index in the L1 to L2 message tree. **Type:** `bigint` #### L2AmountClaim[​](#l2amountclaim "Direct link to L2AmountClaim") **Type:** Type Alias L1 to L2 message info that corresponds to an amount to claim. **Signature:** ``` export type L2AmountClaim = L2Claim & { claimAmount: bigint }; ``` **Type Members:** ##### claimAmount[​](#claimamount "Direct link to claimAmount") **Type:** `bigint` #### L2AmountClaimWithRecipient[​](#l2amountclaimwithrecipient "Direct link to L2AmountClaimWithRecipient") **Type:** Type Alias L1 to L2 message info that corresponds to an amount to claim with associated recipient. **Signature:** ``` export type L2AmountClaimWithRecipient = L2AmountClaim & { recipient: AztecAddress; }; ``` **Type Members:** ##### recipient[​](#recipient "Direct link to recipient") Address that will receive the newly minted notes. **Type:** `AztecAddress` #### generateClaimSecret[​](#generateclaimsecret "Direct link to generateClaimSecret") **Type:** Function Generates a pair secret and secret hash **Signature:** ``` export async generateClaimSecret(logger?: Logger): Promise<[ Fr, Fr ]> ``` **Parameters:** * `logger` (optional): `Logger` **Returns:** `Promise<[Fr, Fr]>` #### L1TokenManager[​](#l1tokenmanager "Direct link to L1TokenManager") **Type:** Class Helper for managing an ERC20 on L1. #### Constructor[​](#constructor-13 "Direct link to Constructor") **Signature:** ``` public constructor( public readonly tokenAddress: EthAddress, public readonly handlerAddress: EthAddress | undefined, private readonly extendedClient: ExtendedViemWalletClient, private logger: Logger ) ``` **Parameters:** * `tokenAddress`: `EthAddress` * Address of the ERC20 contract. * `handlerAddress`: `EthAddress | undefined` * Address of the handler/faucet contract. * `extendedClient`: `ExtendedViemWalletClient` * `logger`: `Logger` #### Methods[​](#methods-16 "Direct link to Methods") ##### getMintAmount[​](#getmintamount "Direct link to getMintAmount") Returns the amount of tokens available to mint via the handler. **Signature:** ``` public async getMintAmount() ``` **Returns:** `Promise` ##### getL1TokenBalance[​](#getl1tokenbalance "Direct link to getL1TokenBalance") Returns the balance of the given address. **Signature:** ``` public async getL1TokenBalance(address: Hex) ``` **Parameters:** * `address`: `Hex` * Address to get the balance of. **Returns:** `Promise` ##### mint[​](#mint "Direct link to mint") Mints a fixed amount of tokens for the given address. Returns once the tx has been mined. **Signature:** ``` public async mint( address: Hex, addressName?: string ) ``` **Parameters:** * `address`: `Hex` * Address to mint the tokens for. * `addressName` (optional): `string` * Optional name of the address for logging. **Returns:** `Promise` ##### approve[​](#approve "Direct link to approve") Approves tokens for the given address. Returns once the tx has been mined. **Signature:** ``` public async approve( amount: bigint, address: Hex, addressName = '' ) ``` **Parameters:** * `amount`: `bigint` * Amount to approve. * `address`: `Hex` * Address to approve the tokens for. * `addressName` (optional): `any` * Optional name of the address for logging. **Returns:** `Promise` #### L1FeeJuicePortalManager[​](#l1feejuiceportalmanager "Direct link to L1FeeJuicePortalManager") **Type:** Class Helper for interacting with the FeeJuicePortal on L1. #### Constructor[​](#constructor-14 "Direct link to Constructor") **Signature:** ``` constructor( portalAddress: EthAddress, tokenAddress: EthAddress, handlerAddress: EthAddress, private readonly extendedClient: ExtendedViemWalletClient, private readonly logger: Logger ) ``` **Parameters:** * `portalAddress`: `EthAddress` * `tokenAddress`: `EthAddress` * `handlerAddress`: `EthAddress` * `extendedClient`: `ExtendedViemWalletClient` * `logger`: `Logger` #### Methods[​](#methods-17 "Direct link to Methods") ##### getTokenManager[​](#gettokenmanager "Direct link to getTokenManager") Returns the associated token manager for the L1 ERC20. **Signature:** ``` public getTokenManager() ``` **Returns:** `L1TokenManager` ##### bridgeTokensPublic[​](#bridgetokenspublic "Direct link to bridgeTokensPublic") Bridges fee juice from L1 to L2 publicly. Handles L1 ERC20 approvals. Returns once the tx has been mined. **Signature:** ``` public async bridgeTokensPublic( to: AztecAddress, amount: bigint | undefined, mint = false ): Promise ``` **Parameters:** * `to`: `AztecAddress` * Address to send the tokens to on L2. * `amount`: `bigint | undefined` * Amount of tokens to send. * `mint` (optional): `any` * Whether to mint the tokens before sending (only during testing). **Returns:** `Promise` ##### new[​](#new "Direct link to new") Creates a new instance **Signature:** ``` public static async new( node: AztecNode, extendedClient: ExtendedViemWalletClient, logger: Logger ): Promise ``` **Parameters:** * `node`: `AztecNode` * Aztec node client used for retrieving the L1 contract addresses. * `extendedClient`: `ExtendedViemWalletClient` * Wallet client, extended with public actions. * `logger`: `Logger` * Logger. **Returns:** `Promise` #### L1ToL2TokenPortalManager[​](#l1tol2tokenportalmanager "Direct link to L1ToL2TokenPortalManager") **Type:** Class Helper for interacting with a test TokenPortal on L1 for sending tokens to L2. #### Constructor[​](#constructor-15 "Direct link to Constructor") **Signature:** ``` constructor( portalAddress: EthAddress, tokenAddress: EthAddress, handlerAddress: EthAddress | undefined, protected extendedClient: ExtendedViemWalletClient, protected logger: Logger ) ``` **Parameters:** * `portalAddress`: `EthAddress` * `tokenAddress`: `EthAddress` * `handlerAddress`: `EthAddress | undefined` * `extendedClient`: `ExtendedViemWalletClient` * `logger`: `Logger` #### Properties[​](#properties-3 "Direct link to Properties") ##### portal[​](#portal "Direct link to portal") **Type:** `ViemContract` ##### tokenManager[​](#tokenmanager "Direct link to tokenManager") **Type:** `L1TokenManager` #### Methods[​](#methods-18 "Direct link to Methods") ##### getTokenManager[​](#gettokenmanager-1 "Direct link to getTokenManager") Returns the token manager for the underlying L1 token. **Signature:** ``` public getTokenManager() ``` **Returns:** `L1TokenManager` ##### bridgeTokensPublic[​](#bridgetokenspublic-1 "Direct link to bridgeTokensPublic") Bridges tokens from L1 to L2. Handles token approvals. Returns once the tx has been mined. **Signature:** ``` public async bridgeTokensPublic( to: AztecAddress, amount: bigint, mint = false ): Promise ``` **Parameters:** * `to`: `AztecAddress` * Address to send the tokens to on L2. * `amount`: `bigint` * Amount of tokens to send. * `mint` (optional): `any` * Whether to mint the tokens before sending (only during testing). **Returns:** `Promise` ##### bridgeTokensPrivate[​](#bridgetokensprivate "Direct link to bridgeTokensPrivate") Bridges tokens from L1 to L2 privately. Handles token approvals. Returns once the tx has been mined. **Signature:** ``` public async bridgeTokensPrivate( to: AztecAddress, amount: bigint, mint = false ): Promise ``` **Parameters:** * `to`: `AztecAddress` * Address to send the tokens to on L2. * `amount`: `bigint` * Amount of tokens to send. * `mint` (optional): `any` * Whether to mint the tokens before sending (only during testing). **Returns:** `Promise` #### L1TokenPortalManager[​](#l1tokenportalmanager "Direct link to L1TokenPortalManager") **Type:** Class Helper for interacting with a test TokenPortal on L1 for both withdrawing from and bridging to L2. **Extends:** `L1ToL2TokenPortalManager` #### Constructor[​](#constructor-16 "Direct link to Constructor") **Signature:** ``` constructor( portalAddress: EthAddress, tokenAddress: EthAddress, handlerAddress: EthAddress | undefined, outboxAddress: EthAddress, extendedClient: ExtendedViemWalletClient, logger: Logger ) ``` **Parameters:** * `portalAddress`: `EthAddress` * `tokenAddress`: `EthAddress` * `handlerAddress`: `EthAddress | undefined` * `outboxAddress`: `EthAddress` * `extendedClient`: `ExtendedViemWalletClient` * `logger`: `Logger` #### Methods[​](#methods-19 "Direct link to Methods") ##### withdrawFunds[​](#withdrawfunds "Direct link to withdrawFunds") Withdraws funds from the portal by consuming an L2 to L1 message. Returns once the tx is mined on L1. **Signature:** ``` public async withdrawFunds( amount: bigint, recipient: EthAddress, blockNumber: bigint, messageIndex: bigint, siblingPath: SiblingPath ) ``` **Parameters:** * `amount`: `bigint` * Amount to withdraw. * `recipient`: `EthAddress` * Who will receive the funds. * `blockNumber`: `bigint` * L2 block number of the message. * `messageIndex`: `bigint` * Index of the message. * `siblingPath`: `SiblingPath` * Sibling path of the message. **Returns:** `Promise` ##### getL2ToL1MessageLeaf[​](#getl2tol1messageleaf "Direct link to getL2ToL1MessageLeaf") Computes the L2 to L1 message leaf for the given parameters. **Signature:** ``` public async getL2ToL1MessageLeaf( amount: bigint, recipient: EthAddress, l2Bridge: AztecAddress, callerOnL1: EthAddress = EthAddress.ZERO ): Promise ``` **Parameters:** * `amount`: `bigint` * Amount to bridge. * `recipient`: `EthAddress` * Recipient on L1. * `l2Bridge`: `AztecAddress` * Address of the L2 bridge. * `callerOnL1` (optional): `EthAddress` * Caller address on L1. **Returns:** `Promise` ## Fee[​](#fee-4 "Direct link to Fee") *** ### `fee/fee_juice_payment_method_with_claim.ts`[​](#feefee_juice_payment_method_with_claimts "Direct link to feefee_juice_payment_method_with_claimts") #### FeeJuicePaymentMethodWithClaim[​](#feejuicepaymentmethodwithclaim "Direct link to FeeJuicePaymentMethodWithClaim") **Type:** Class Pay fee directly with Fee Juice claimed in the same tx. Claiming consumes an L1 to L2 message that "contains" the fee juice bridged from L1. **Implements:** `FeePaymentMethod` #### Constructor[​](#constructor-17 "Direct link to Constructor") **Signature:** ``` constructor( private sender: AztecAddress, private claim: Pick ) ``` **Parameters:** * `sender`: `AztecAddress` * `claim`: `Pick` #### Methods[​](#methods-20 "Direct link to Methods") ##### getExecutionPayload[​](#getexecutionpayload "Direct link to getExecutionPayload") Creates an execution payload to pay the fee in Fee Juice. **Signature:** ``` async getExecutionPayload(): Promise ``` **Returns:** `Promise` - An execution payload that just contains the `claim_and_end_setup` function call. ##### getAsset[​](#getasset "Direct link to getAsset") **Signature:** ``` getAsset() ``` **Returns:** `Promise` ##### getFeePayer[​](#getfeepayer "Direct link to getFeePayer") **Signature:** ``` getFeePayer(): Promise ``` **Returns:** `Promise` ##### getGasSettings[​](#getgassettings "Direct link to getGasSettings") **Signature:** ``` getGasSettings(): GasSettings | undefined ``` **Returns:** `GasSettings | undefined` *** ### `fee/fee_payment_method.ts`[​](#feefee_payment_methodts "Direct link to feefee_payment_methodts") #### FeePaymentMethod[​](#feepaymentmethod "Direct link to FeePaymentMethod") **Type:** Interface Holds information about how the fee for a transaction is to be paid. #### Methods[​](#methods-21 "Direct link to Methods") ##### getAsset[​](#getasset-1 "Direct link to getAsset") The asset used to pay the fee. **Signature:** ``` getAsset(): Promise ``` **Returns:** `Promise` ##### getExecutionPayload[​](#getexecutionpayload-1 "Direct link to getExecutionPayload") Returns the data to be added to the final execution request to pay the fee in the given asset **Signature:** ``` getExecutionPayload(): Promise ``` **Returns:** `Promise` - The function calls to pay the fee. ##### getFeePayer[​](#getfeepayer-1 "Direct link to getFeePayer") The expected fee payer for this tx. **Signature:** ``` getFeePayer(): Promise ``` **Returns:** `Promise` ##### getGasSettings[​](#getgassettings-1 "Direct link to getGasSettings") The gas settings (if any) used to compute the execution payload of the payment method **Signature:** ``` getGasSettings(): GasSettings | undefined ``` **Returns:** `GasSettings | undefined` *** ### `fee/private_fee_payment_method.ts`[​](#feeprivate_fee_payment_methodts "Direct link to feeprivate_fee_payment_methodts") #### PrivateFeePaymentMethod[​](#privatefeepaymentmethod "Direct link to PrivateFeePaymentMethod") **Type:** Class Holds information about how the fee for a transaction is to be paid. **Implements:** `FeePaymentMethod` #### Constructor[​](#constructor-18 "Direct link to Constructor") **Signature:** ``` constructor( private paymentContract: AztecAddress, private sender: AztecAddress, private wallet: Wallet, protected gasSettings: GasSettings, private setMaxFeeToOne = false ) ``` **Parameters:** * `paymentContract`: `AztecAddress` * Address which will hold the fee payment. * `sender`: `AztecAddress` * Address of the account that will pay the fee * `wallet`: `Wallet` * A wallet to perform the simulation to get the accepted asset * `gasSettings`: `GasSettings` * Gas settings used to compute the maximum fee the user is willing to pay * `setMaxFeeToOne` (optional): `any` * If true, the max fee will be set to 1. TODO(#7694): Remove this param once the lacking feature in TXE is implemented. #### Methods[​](#methods-22 "Direct link to Methods") ##### getAsset[​](#getasset-2 "Direct link to getAsset") The asset used to pay the fee. **Signature:** ``` async getAsset(): Promise ``` **Returns:** `Promise` - The asset used to pay the fee. ##### getFeePayer[​](#getfeepayer-2 "Direct link to getFeePayer") **Signature:** ``` getFeePayer(): Promise ``` **Returns:** `Promise` ##### getExecutionPayload[​](#getexecutionpayload-2 "Direct link to getExecutionPayload") Creates an execution payload to pay the fee using a private function through an FPC in the desired asset **Signature:** ``` async getExecutionPayload(): Promise ``` **Returns:** `Promise` - An execution payload that contains the required function calls and auth witnesses. ##### getGasSettings[​](#getgassettings-2 "Direct link to getGasSettings") **Signature:** ``` getGasSettings(): GasSettings | undefined ``` **Returns:** `GasSettings | undefined` *** ### `fee/public_fee_payment_method.ts`[​](#feepublic_fee_payment_methodts "Direct link to feepublic_fee_payment_methodts") #### PublicFeePaymentMethod[​](#publicfeepaymentmethod "Direct link to PublicFeePaymentMethod") **Type:** Class Holds information about how the fee for a transaction is to be paid. **Implements:** `FeePaymentMethod` #### Constructor[​](#constructor-19 "Direct link to Constructor") **Signature:** ``` constructor( protected paymentContract: AztecAddress, protected sender: AztecAddress, protected wallet: Wallet, protected gasSettings: GasSettings ) ``` **Parameters:** * `paymentContract`: `AztecAddress` * Address which will hold the fee payment. * `sender`: `AztecAddress` * An auth witness provider to authorize fee payments * `wallet`: `Wallet` * A wallet to perform the simulation to get the accepted asset * `gasSettings`: `GasSettings` * Gas settings used to compute the maximum fee the user is willing to pay #### Methods[​](#methods-23 "Direct link to Methods") ##### getAsset[​](#getasset-3 "Direct link to getAsset") The asset used to pay the fee. **Signature:** ``` async getAsset(): Promise ``` **Returns:** `Promise` - The asset used to pay the fee. ##### getFeePayer[​](#getfeepayer-3 "Direct link to getFeePayer") **Signature:** ``` getFeePayer(): Promise ``` **Returns:** `Promise` ##### getExecutionPayload[​](#getexecutionpayload-3 "Direct link to getExecutionPayload") Creates an execution payload to pay the fee using a public function through an FPC in the desired asset **Signature:** ``` async getExecutionPayload(): Promise ``` **Returns:** `Promise` - An execution payload that contains the required function calls. ##### getGasSettings[​](#getgassettings-3 "Direct link to getGasSettings") **Signature:** ``` getGasSettings(): GasSettings | undefined ``` **Returns:** `GasSettings | undefined` *** ### `fee/sponsored_fee_payment.ts`[​](#feesponsored_fee_paymentts "Direct link to feesponsored_fee_paymentts") #### SponsoredFeePaymentMethod[​](#sponsoredfeepaymentmethod "Direct link to SponsoredFeePaymentMethod") **Type:** Class A fee payment method that uses a contract that blindly sponsors transactions. This contract is expected to be prefunded in testing environments. **Implements:** `FeePaymentMethod` #### Constructor[​](#constructor-20 "Direct link to Constructor") **Signature:** ``` constructor(private paymentContract: AztecAddress) ``` **Parameters:** * `paymentContract`: `AztecAddress` #### Methods[​](#methods-24 "Direct link to Methods") ##### getAsset[​](#getasset-4 "Direct link to getAsset") **Signature:** ``` getAsset(): Promise ``` **Returns:** `Promise` ##### getFeePayer[​](#getfeepayer-4 "Direct link to getFeePayer") **Signature:** ``` getFeePayer() ``` **Returns:** `Promise` ##### getExecutionPayload[​](#getexecutionpayload-4 "Direct link to getExecutionPayload") **Signature:** ``` async getExecutionPayload(): Promise ``` **Returns:** `Promise` ##### getGasSettings[​](#getgassettings-4 "Direct link to getGasSettings") **Signature:** ``` getGasSettings(): GasSettings | undefined ``` **Returns:** `GasSettings | undefined` ## Utils[​](#utils "Direct link to Utils") *** ### `utils/abi_types.ts`[​](#utilsabi_typests "Direct link to utilsabi_typests") #### FieldLike[​](#fieldlike "Direct link to FieldLike") **Type:** Type Alias Any type that can be converted into a field for a contract call. **Signature:** ``` export type FieldLike = Fr | Buffer | bigint | number | { toField: () => Fr }; ``` #### EthAddressLike[​](#ethaddresslike "Direct link to EthAddressLike") **Type:** Type Alias Any type that can be converted into an EthAddress Aztec.nr struct. **Signature:** ``` export type EthAddressLike = { address: FieldLike } | EthAddress; ``` #### AztecAddressLike[​](#aztecaddresslike "Direct link to AztecAddressLike") **Type:** Type Alias Any type that can be converted into an AztecAddress Aztec.nr struct. **Signature:** ``` export type AztecAddressLike = { address: FieldLike } | AztecAddress; ``` #### FunctionSelectorLike[​](#functionselectorlike "Direct link to FunctionSelectorLike") **Type:** Type Alias Any type that can be converted into a FunctionSelector Aztec.nr struct. **Signature:** ``` export type FunctionSelectorLike = FieldLike | FunctionSelector; ``` #### EventSelectorLike[​](#eventselectorlike "Direct link to EventSelectorLike") **Type:** Type Alias Any type that can be converted into an EventSelector Aztec.nr struct. **Signature:** ``` export type EventSelectorLike = FieldLike | EventSelector; ``` #### U128Like[​](#u128like "Direct link to U128Like") **Type:** Type Alias Any type that can be converted into a U128. **Signature:** ``` export type U128Like = bigint | number; ``` #### WrappedFieldLike[​](#wrappedfieldlike "Direct link to WrappedFieldLike") **Type:** Type Alias Any type that can be converted into a struct with a single `inner` field. **Signature:** ``` export type WrappedFieldLike = { inner: FieldLike } | FieldLike; ``` *** ### `utils/authwit.ts`[​](#utilsauthwitts "Direct link to utilsauthwitts") #### IntentInnerHash[​](#intentinnerhash "Direct link to IntentInnerHash") **Type:** Type Alias Intent with an inner hash **Signature:** ``` export type IntentInnerHash = { consumer: AztecAddress; innerHash: Fr; }; ``` **Type Members:** ##### consumer[​](#consumer "Direct link to consumer") The consumer **Type:** `AztecAddress` ##### innerHash[​](#innerhash "Direct link to innerHash") The action to approve **Type:** `Fr` #### CallIntent[​](#callintent "Direct link to CallIntent") **Type:** Type Alias Intent with a call **Signature:** ``` export type CallIntent = { caller: AztecAddress; call: FunctionCall; }; ``` **Type Members:** ##### caller[​](#caller "Direct link to caller") The caller to approve **Type:** `AztecAddress` ##### call[​](#call "Direct link to call") The call to approve **Type:** `FunctionCall` #### ContractFunctionInteractionCallIntent[​](#contractfunctioninteractioncallintent "Direct link to ContractFunctionInteractionCallIntent") **Type:** Type Alias Intent with a ContractFunctionInteraction **Signature:** ``` export type ContractFunctionInteractionCallIntent = { caller: AztecAddress; action: ContractFunctionInteraction; }; ``` **Type Members:** ##### caller[​](#caller-1 "Direct link to caller") The caller to approve **Type:** `AztecAddress` ##### action[​](#action "Direct link to action") The action to approve **Type:** `ContractFunctionInteraction` #### computeAuthWitMessageHash[​](#computeauthwitmessagehash "Direct link to computeAuthWitMessageHash") **Type:** Constant Compute an authentication witness message hash from an intent and metadata If using the `IntentInnerHash`, the consumer is the address that can "consume" the authwit, for token approvals it is the token contract itself. The `innerHash` itself will be the message that a contract is allowed to execute. At the point of "approval checking", the validating contract (account for private and registry for public) will be computing the message hash (`H(consumer, chainid, version, inner_hash)`) where the all but the `inner_hash` is injected from the context (consumer = msg\_sender), and use it for the authentication check. Therefore, any allowed `innerHash` will therefore also have information around where it can be spent (version, chainId) and who can spend it (consumer). If using the `CallIntent`, the caller is the address that is making the call, for a token approval from Alice to Bob, this would be Bob. The action is then used along with the `caller` to compute the `innerHash` and the consumer. **Value Type:** `any` #### getMessageHashFromIntent[​](#getmessagehashfromintent "Direct link to getMessageHashFromIntent") **Type:** Function Compute an authentication witness message hash from an intent and metadata. This is just a wrapper around computeAuthwitMessageHash that allows receiving an already computed messageHash as input **Signature:** ``` export async getMessageHashFromIntent( messageHashOrIntent: Fr | IntentInnerHash | CallIntent | ContractFunctionInteractionCallIntent, chainInfo: ChainInfo ) ``` **Parameters:** * `messageHashOrIntent`: `Fr | IntentInnerHash | CallIntent | ContractFunctionInteractionCallIntent` * The precomputed messageHash or intent to approve (consumer and innerHash or caller and call/action) * `chainInfo`: `ChainInfo` **Returns:** `Promise` - The message hash for the intent #### computeInnerAuthWitHashFromAction[​](#computeinnerauthwithashfromaction "Direct link to computeInnerAuthWitHashFromAction") **Type:** Constant Computes the inner authwitness hash for either a function call or an action, for it to later be combined with the metadata required for the outer hash and eventually the full AuthWitness. **Value Type:** `any` #### lookupValidity[​](#lookupvalidity "Direct link to lookupValidity") **Type:** Function Lookup the validity of an authwit in private and public contexts. Uses the chain id and version of the wallet. **Signature:** ``` export async lookupValidity( wallet: Wallet, onBehalfOf: AztecAddress, intent: IntentInnerHash | CallIntent | ContractFunctionInteractionCallIntent, witness: AuthWitness ): Promise<{ isValidInPrivate: boolean; isValidInPublic: boolean; }> ``` **Parameters:** * `wallet`: `Wallet` * The wallet use to simulate and read the public data * `onBehalfOf`: `AztecAddress` * The address of the "approver" * `intent`: `IntentInnerHash | CallIntent | ContractFunctionInteractionCallIntent` * The consumer and inner hash or the caller and action to lookup * `witness`: `AuthWitness` * The computed authentication witness to check **Returns:** ``` Promise<{ /** boolean flag indicating if the authwit is valid in private context */ isValidInPrivate: boolean; /** boolean flag indicating if the authwit is valid in public context */ isValidInPublic: boolean; }> ``` A struct containing the validity of the authwit in private and public contexts. #### SetPublicAuthwitContractInteraction[​](#setpublicauthwitcontractinteraction "Direct link to SetPublicAuthwitContractInteraction") **Type:** Class Convenience class designed to wrap the very common interaction of setting a public authwit in the AuthRegistry contract **Extends:** `ContractFunctionInteraction` #### Constructor[​](#constructor-21 "Direct link to Constructor") **Signature:** ``` private constructor( wallet: Wallet, private from: AztecAddress, messageHash: Fr, authorized: boolean ) ``` **Parameters:** * `wallet`: `Wallet` * `from`: `AztecAddress` * `messageHash`: `Fr` * `authorized`: `boolean` #### Methods[​](#methods-25 "Direct link to Methods") ##### create[​](#create "Direct link to create") **Signature:** ``` static async create( wallet: Wallet, from: AztecAddress, messageHashOrIntent: Fr | IntentInnerHash | CallIntent | ContractFunctionInteractionCallIntent, authorized: boolean ) ``` **Parameters:** * `wallet`: `Wallet` * `from`: `AztecAddress` * `messageHashOrIntent`: `Fr | IntentInnerHash | CallIntent | ContractFunctionInteractionCallIntent` * `authorized`: `boolean` **Returns:** `Promise` ##### simulate[​](#simulate-5 "Direct link to simulate") Overrides the simulate method, adding the sender of the authwit (authorizer) as from and preventing misuse **Signature:** ``` public override simulate(options: Omit): Promise> ``` **Parameters:** * `options`: `Omit` * An optional object containing additional configuration for the transaction. **Returns:** `Promise>` - The result of the transaction as returned by the contract function. ##### simulate[​](#simulate-6 "Direct link to simulate") **Signature:** ``` public override simulate(options: Omit = {}): Promise> ``` **Parameters:** * `options` (optional): `Omit` **Returns:** `Promise>` ##### profile[​](#profile-2 "Direct link to profile") Overrides the profile method, adding the sender of the authwit (authorizer) as from and preventing misuse **Signature:** ``` public override profile(options: Omit = { profileMode: 'gates' }): Promise ``` **Parameters:** * `options` (optional): `Omit` * Same options as `simulate`, plus profiling method **Returns:** `Promise` - An object containing the function return value and profile result. ##### send[​](#send-2 "Direct link to send") Overrides the send method, adding the sender of the authwit (authorizer) as from and preventing misuse **Signature:** ``` public override send(options: Omit = {}): SentTx ``` **Parameters:** * `options` (optional): `Omit` * An optional object containing 'fee' options information **Returns:** `SentTx` - A SentTx instance for tracking the transaction status and information. *** ### `utils/cross_chain.ts`[​](#utilscross_chaints "Direct link to utilscross_chaints") #### waitForL1ToL2MessageReady[​](#waitforl1tol2messageready "Direct link to waitForL1ToL2MessageReady") **Type:** Function Waits for the L1 to L2 message to be ready to be consumed. **Signature:** ``` export async waitForL1ToL2MessageReady( node: Pick, l1ToL2MessageHash: Fr, opts: { timeoutSeconds: number; forPublicConsumption: boolean; } ) ``` **Parameters:** * `node`: `Pick` * Aztec node instance used to obtain the information about the message * `l1ToL2MessageHash`: `Fr` * Hash of the L1 to L2 message * `opts`: `{ /** Timeout for the operation in seconds */ timeoutSeconds: number; /** True if the message is meant to be consumed from a public function */ forPublicConsumption: boolean; }` * Options **Returns:** `Promise` #### isL1ToL2MessageReady[​](#isl1tol2messageready "Direct link to isL1ToL2MessageReady") **Type:** Function Returns whether the L1 to L2 message is ready to be consumed. **Signature:** ``` export async isL1ToL2MessageReady( node: Pick, l1ToL2MessageHash: Fr, opts: { forPublicConsumption: boolean; messageBlockNumber?: number; } ): Promise ``` **Parameters:** * `node`: `Pick` * Aztec node instance used to obtain the information about the message * `l1ToL2MessageHash`: `Fr` * Hash of the L1 to L2 message * `opts`: `{ /** True if the message is meant to be consumed from a public function */ forPublicConsumption: boolean; /** Cached synced block number for the message (will be fetched from PXE otherwise) */ messageBlockNumber?: number; }` * Options **Returns:** `Promise` - True if the message is ready to be consumed, false otherwise *** ### `utils/fee_juice.ts`[​](#utilsfee_juicets "Direct link to utilsfee_juicets") #### getFeeJuiceBalance[​](#getfeejuicebalance "Direct link to getFeeJuiceBalance") **Type:** Function Returns the owner's fee juice balance. Note: This is used only e2e\_local\_network\_example test. TODO: Consider nuking. **Signature:** ``` export async getFeeJuiceBalance( owner: AztecAddress, node: AztecNode ): Promise ``` **Parameters:** * `owner`: `AztecAddress` * `node`: `AztecNode` **Returns:** `Promise` *** ### `utils/field_compressed_string.ts`[​](#utilsfield_compressed_stringts "Direct link to utilsfield_compressed_stringts") #### readFieldCompressedString[​](#readfieldcompressedstring "Direct link to readFieldCompressedString") **Type:** Constant This turns **Value Type:** `any` *** ### `utils/node.ts`[​](#utilsnodets "Direct link to utilsnodets") #### waitForNode[​](#waitfornode "Direct link to waitForNode") **Type:** Constant **Value Type:** `any` #### createAztecNodeClient[​](#createaztecnodeclient "Direct link to createAztecNodeClient") **Type:** Constant This is re-exported from `@aztec/stdlib/interfaces/client`. See the source module for full documentation. **Value Type:** `Re-export` #### AztecNode[​](#aztecnode "Direct link to AztecNode") **Type:** Type Alias This is a type re-exported from `@aztec/stdlib/interfaces/client`. See the source module for full type definition and documentation. **Signature:** ``` export type { AztecNode } from '@aztec/stdlib/interfaces/client' ``` *** ### `utils/pub_key.ts`[​](#utilspub_keyts "Direct link to utilspub_keyts") #### generatePublicKey[​](#generatepublickey "Direct link to generatePublicKey") **Type:** Function Method for generating a public grumpkin key from a private key. **Signature:** ``` export generatePublicKey(privateKey: GrumpkinScalar): Promise ``` **Parameters:** * `privateKey`: `GrumpkinScalar` * The private key. **Returns:** `Promise` - The generated public key. ## Wallet[​](#wallet-1 "Direct link to Wallet") *** ### `wallet/account_entrypoint_meta_payment_method.ts`[​](#walletaccount_entrypoint_meta_payment_methodts "Direct link to walletaccount_entrypoint_meta_payment_methodts") #### AccountEntrypointMetaPaymentMethod[​](#accountentrypointmetapaymentmethod "Direct link to AccountEntrypointMetaPaymentMethod") **Type:** Class Fee payment method that allows an account contract to pay for its own deployment It works by rerouting the provided fee payment method through the account's entrypoint, which sets itself as fee payer. If no payment method is provided, it is assumed the account will pay with its own fee juice balance. Usually, in order to pay fees it is necessary to obtain an ExecutionPayload that encodes the necessary information that is sent to the user's account entrypoint, that has plumbing to handle it. If there's no account contract yet (it's being deployed) a MultiCallContract is used, which doesn't have a concept of fees or how to handle this payload. HOWEVER, the account contract's entrypoint does, so this method reshapes that fee payload into a call to the account contract entrypoint being deployed with the original fee payload. This class can be seen in action in DeployAccountMethod.ts#getSelfPaymentMethod **Implements:** `FeePaymentMethod` #### Constructor[​](#constructor-22 "Direct link to Constructor") **Signature:** ``` constructor( private wallet: Wallet, private artifact: ContractArtifact, private feePaymentNameOrArtifact: string | FunctionArtifact, private accountAddress: AztecAddress, private paymentMethod?: FeePaymentMethod ) ``` **Parameters:** * `wallet`: `Wallet` * `artifact`: `ContractArtifact` * `feePaymentNameOrArtifact`: `string | FunctionArtifact` * `accountAddress`: `AztecAddress` * `paymentMethod` (optional): `FeePaymentMethod` #### Methods[​](#methods-26 "Direct link to Methods") ##### getAsset[​](#getasset-5 "Direct link to getAsset") **Signature:** ``` getAsset(): Promise ``` **Returns:** `Promise` ##### getExecutionPayload[​](#getexecutionpayload-5 "Direct link to getExecutionPayload") **Signature:** ``` async getExecutionPayload(): Promise ``` **Returns:** `Promise` ##### getFeePayer[​](#getfeepayer-5 "Direct link to getFeePayer") **Signature:** ``` getFeePayer(): Promise ``` **Returns:** `Promise` ##### getGasSettings[​](#getgassettings-5 "Direct link to getGasSettings") **Signature:** ``` getGasSettings(): GasSettings | undefined ``` **Returns:** `GasSettings | undefined` *** ### `wallet/account_manager.ts`[​](#walletaccount_managerts "Direct link to walletaccount_managerts") #### AccountManager[​](#accountmanager "Direct link to AccountManager") **Type:** Class Manages a user account. Provides methods for calculating the account's address and other related data, plus a helper to return a preconfigured deploy method. #### Constructor[​](#constructor-23 "Direct link to Constructor") **Signature:** ``` private constructor( private wallet: Wallet, private secretKey: Fr, private accountContract: AccountContract, private instance: ContractInstanceWithAddress, public readonly salt: Salt ) ``` **Parameters:** * `wallet`: `Wallet` * `secretKey`: `Fr` * `accountContract`: `AccountContract` * `instance`: `ContractInstanceWithAddress` * `salt`: `Salt` * Contract instantiation salt for the account contract #### Methods[​](#methods-27 "Direct link to Methods") ##### create[​](#create-1 "Direct link to create") **Signature:** ``` static async create( wallet: Wallet, secretKey: Fr, accountContract: AccountContract, salt?: Salt ) ``` **Parameters:** * `wallet`: `Wallet` * `secretKey`: `Fr` * `accountContract`: `AccountContract` * `salt` (optional): `Salt` **Returns:** `Promise` ##### getPublicKeys[​](#getpublickeys "Direct link to getPublicKeys") **Signature:** ``` protected getPublicKeys() ``` **Returns:** `any` ##### getPublicKeysHash[​](#getpublickeyshash "Direct link to getPublicKeysHash") **Signature:** ``` protected getPublicKeysHash() ``` **Returns:** `any` ##### getAccountInterface[​](#getaccountinterface "Direct link to getAccountInterface") Returns the entrypoint for this account as defined by its account contract. **Signature:** ``` public async getAccountInterface(): Promise ``` **Returns:** `Promise` - An entrypoint. ##### getCompleteAddress[​](#getcompleteaddress-3 "Direct link to getCompleteAddress") Gets the calculated complete address associated with this account. Does not require the account to have been published for public execution. **Signature:** ``` public getCompleteAddress(): Promise ``` **Returns:** `Promise` - The address, partial address, and encryption public key. ##### getSecretKey[​](#getsecretkey-1 "Direct link to getSecretKey") Returns the secret key used to derive the rest of the privacy keys for this contract **Signature:** ``` public getSecretKey() ``` **Returns:** `Fr` ##### getInstance[​](#getinstance-2 "Direct link to getInstance") Returns the contract instance definition associated with this account. Does not require the account to have been published for public execution. **Signature:** ``` public getInstance(): ContractInstanceWithAddress ``` **Returns:** `ContractInstanceWithAddress` - ContractInstance instance. ##### getAccount[​](#getaccount "Direct link to getAccount") Returns a Wallet instance associated with this account. Use it to create Contract instances to be interacted with from this account. **Signature:** ``` public async getAccount(): Promise ``` **Returns:** `Promise` - A Wallet instance. ##### getAccountContract[​](#getaccountcontract "Direct link to getAccountContract") Returns the account contract that backs this account. **Signature:** ``` getAccountContract(): AccountContract ``` **Returns:** `AccountContract` - The account contract ##### getDeployMethod[​](#getdeploymethod "Direct link to getDeployMethod") Returns a preconfigured deploy method that contains all the necessary function calls to deploy the account contract. **Signature:** ``` public async getDeployMethod(): Promise ``` **Returns:** `Promise` ##### hasInitializer[​](#hasinitializer "Direct link to hasInitializer") Returns whether this account contract has an initializer function. **Signature:** ``` public async hasInitializer() ``` **Returns:** `Promise` #### Getters[​](#getters-1 "Direct link to Getters") ##### address (getter)[​](#address-getter-1 "Direct link to address (getter)") **Signature:** ``` get address() { ``` **Returns:** `any` *** ### `wallet/deploy_account_method.ts`[​](#walletdeploy_account_methodts "Direct link to walletdeploy_account_methodts") #### RequestDeployAccountOptions[​](#requestdeployaccountoptions "Direct link to RequestDeployAccountOptions") **Type:** Type Alias The configuration options for the request method. Omits the contractAddressSalt, since for account contracts that is fixed in the constructor **Signature:** ``` export type RequestDeployAccountOptions = Omit; ``` #### DeployAccountOptions[​](#deployaccountoptions "Direct link to DeployAccountOptions") **Type:** Type Alias The configuration options for the send/prove methods. Omits: - The contractAddressSalt, since for account contracts that is fixed in the constructor. - UniversalDeployment flag, since account contracts are always deployed with it set to true **Signature:** ``` export type DeployAccountOptions = Omit; ``` #### SimulateDeployAccountOptions[​](#simulatedeployaccountoptions "Direct link to SimulateDeployAccountOptions") **Type:** Type Alias The configuration options for the simulate method. Omits the contractAddressSalt, since for account contracts that is fixed in the constructor **Signature:** ``` export type SimulateDeployAccountOptions = Omit; ``` #### DeployAccountMethod[​](#deployaccountmethod "Direct link to DeployAccountMethod") **Type:** Class Modified version of the DeployMethod used to deploy account contracts. Supports deploying contracts that can pay for their own fee, plus some preconfigured options to avoid errors. **Extends:** `DeployMethod` #### Constructor[​](#constructor-24 "Direct link to Constructor") **Signature:** ``` constructor( publicKeys: PublicKeys, wallet: Wallet, artifact: ContractArtifact, postDeployCtor: (instance: ContractInstanceWithAddress, wallet: Wallet) => TContract, private salt: Fr, args: any[] = [], constructorNameOrArtifact?: string | FunctionArtifact ) ``` **Parameters:** * `publicKeys`: `PublicKeys` * `wallet`: `Wallet` * `artifact`: `ContractArtifact` * `postDeployCtor`: `(instance: ContractInstanceWithAddress, wallet: Wallet) => TContract` * `salt`: `Fr` * `args` (optional): `any[]` * `constructorNameOrArtifact` (optional): `string | FunctionArtifact` #### Methods[​](#methods-28 "Direct link to Methods") ##### request[​](#request-4 "Direct link to request") Returns the execution payload that allows this operation to happen on chain. **Signature:** ``` public override async request(opts?: RequestDeployAccountOptions): Promise ``` **Parameters:** * `opts` (optional): `RequestDeployAccountOptions` * Configuration options. **Returns:** `Promise` - The execution payload for this operation ##### convertDeployOptionsToRequestOptions[​](#convertdeployoptionstorequestoptions-1 "Direct link to convertDeployOptionsToRequestOptions") **Signature:** ``` override convertDeployOptionsToRequestOptions(options: DeployOptions): RequestDeployOptions ``` **Parameters:** * `options`: `DeployOptions` **Returns:** `RequestDeployOptions` *** ### `wallet/wallet.ts`[​](#walletwalletts "Direct link to walletwalletts") #### Aliased[​](#aliased "Direct link to Aliased") **Type:** Type Alias A wrapper type that allows any item to be associated with an alias. **Signature:** ``` export type Aliased = { alias: string; item: T; }; ``` **Type Members:** ##### alias[​](#alias "Direct link to alias") The alias **Type:** `string` ##### item[​](#item "Direct link to item") The item being aliased. **Type:** `T` #### SimulateOptions[​](#simulateoptions "Direct link to SimulateOptions") **Type:** Type Alias Options for simulating interactions with the wallet. Overrides the fee settings of an interaction with a simplified version that only hints at the wallet wether the interaction contains a fee payment method or not **Signature:** ``` export type SimulateOptions = Omit & { fee?: GasSettingsOption & FeeEstimationOptions; }; ``` **Type Members:** ##### fee[​](#fee-5 "Direct link to fee") The fee options **Type:** `GasSettingsOption & FeeEstimationOptions` #### ProfileOptions[​](#profileoptions "Direct link to ProfileOptions") **Type:** Type Alias Options for profiling interactions with the wallet. Overrides the fee settings of an interaction with a simplified version that only hints at the wallet wether the interaction contains a fee payment method or not **Signature:** ``` export type ProfileOptions = Omit & { fee?: GasSettingsOption; }; ``` **Type Members:** ##### fee[​](#fee-6 "Direct link to fee") The fee options **Type:** `GasSettingsOption` #### SendOptions[​](#sendoptions "Direct link to SendOptions") **Type:** Type Alias Options for sending/proving interactions with the wallet. Overrides the fee settings of an interaction with a simplified version that only hints at the wallet wether the interaction contains a fee payment method or not **Signature:** ``` export type SendOptions = Omit & { fee?: GasSettingsOption; }; ``` **Type Members:** ##### fee[​](#fee-7 "Direct link to fee") The fee options **Type:** `GasSettingsOption` #### BatchableMethods[​](#batchablemethods "Direct link to BatchableMethods") **Type:** Type Alias Helper type that represents all methods that can be batched. **Signature:** ``` export type BatchableMethods = Pick< Wallet, 'registerContract' | 'sendTx' | 'registerSender' | 'simulateUtility' | 'simulateTx' >; ``` #### BatchedMethod[​](#batchedmethod "Direct link to BatchedMethod") **Type:** Type Alias From the batchable methods, we create a type that represents a method call with its name and arguments. This is what the wallet will accept as arguments to the `batch` method. **Signature:** ``` export type BatchedMethod = { name: T; args: Parameters; }; ``` **Type Members:** ##### name[​](#name "Direct link to name") The method name **Type:** `T` ##### args[​](#args "Direct link to args") The method arguments **Type:** `Parameters` #### BatchedMethodResult[​](#batchedmethodresult "Direct link to BatchedMethodResult") **Type:** Type Alias Helper type to extract the return type of a batched method **Signature:** ``` export type BatchedMethodResult = T extends BatchedMethod ? Awaited> : never; ``` #### BatchedMethodResultWrapper[​](#batchedmethodresultwrapper "Direct link to BatchedMethodResultWrapper") **Type:** Type Alias Wrapper type for batch results that includes the method name for discriminated union deserialization. Each result is wrapped as { name: 'methodName', result: ActualResult } to allow proper deserialization when AztecAddress and TxHash would otherwise be ambiguous (both are hex strings). **Signature:** ``` export type BatchedMethodResultWrapper> = { name: T['name']; result: BatchedMethodResult; }; ``` **Type Members:** ##### name[​](#name-1 "Direct link to name") The method name **Type:** `T['name']` ##### result[​](#result "Direct link to result") The method result **Type:** `BatchedMethodResult` #### BatchResults[​](#batchresults "Direct link to BatchResults") **Type:** Type Alias Maps a tuple of BatchedMethod to a tuple of their wrapped return types **Signature:** ``` export type BatchResults[]> = { [K in keyof T]: BatchedMethodResultWrapper; }; ``` **Type Members:** ##### \[K in keyof T][​](#k-in-keyof-t "Direct link to \[K in keyof T]") **Signature:** `[K in keyof T]: BatchedMethodResultWrapper` **Key Type:** `keyof T` **Value Type:** `BatchedMethodResultWrapper` #### PrivateEventFilter[​](#privateeventfilter "Direct link to PrivateEventFilter") **Type:** Type Alias Filter options when querying private events. **Signature:** ``` export type PrivateEventFilter = { contractAddress: AztecAddress; scopes: AztecAddress[]; txHash?: TxHash; fromBlock?: BlockNumber; toBlock?: BlockNumber; }; ``` **Type Members:** ##### contractAddress[​](#contractaddress "Direct link to contractAddress") The address of the contract that emitted the events. **Type:** `AztecAddress` ##### scopes[​](#scopes "Direct link to scopes") Addresses of accounts that are in scope for this filter. **Type:** `AztecAddress[]` ##### txHash[​](#txhash-1 "Direct link to txHash") Transaction in which the events were emitted. **Type:** `TxHash` ##### fromBlock[​](#fromblock "Direct link to fromBlock") The block number from which to start fetching events (inclusive). Optional. If provided, it must be greater or equal than 1. Defaults to the initial L2 block number (INITIAL\_L2\_BLOCK\_NUM). **Type:** `BlockNumber` ##### toBlock[​](#toblock "Direct link to toBlock") The block number until which to fetch logs (not inclusive). Optional. If provided, it must be greater than fromBlock. Defaults to the latest known block to PXE + 1. **Type:** `BlockNumber` #### PrivateEvent[​](#privateevent "Direct link to PrivateEvent") **Type:** Type Alias An ABI decoded private event with associated metadata. **Signature:** ``` export type PrivateEvent = { event: T; metadata: InTx; }; ``` **Type Members:** ##### event[​](#event "Direct link to event") The ABI decoded event **Type:** `T` ##### metadata[​](#metadata "Direct link to metadata") Metadata describing event context information such as tx and block **Type:** `InTx` #### Wallet[​](#wallet-2 "Direct link to Wallet") **Type:** Type Alias The wallet interface. **Signature:** ``` export type Wallet = { getContractClassMetadata(id: Fr, includeArtifact?: boolean): Promise; getContractMetadata(address: AztecAddress): Promise; getPrivateEvents( eventMetadata: EventMetadataDefinition, eventFilter: PrivateEventFilter, ): Promise[]>; getChainInfo(): Promise; getTxReceipt(txHash: TxHash): Promise; registerSender(address: AztecAddress, alias?: string): Promise; getAddressBook(): Promise[]>; getAccounts(): Promise[]>; registerContract( instance: ContractInstanceWithAddress, artifact?: ContractArtifact, secretKey?: Fr, ): Promise; simulateTx(exec: ExecutionPayload, opts: SimulateOptions): Promise; simulateUtility(call: FunctionCall, authwits?: AuthWitness[]): Promise; profileTx(exec: ExecutionPayload, opts: ProfileOptions): Promise; sendTx(exec: ExecutionPayload, opts: SendOptions): Promise; createAuthWit(from: AztecAddress, messageHashOrIntent: Fr | IntentInnerHash | CallIntent): Promise; batch[]>(methods: T): Promise>; }; ``` **Type Members:** ##### getContractClassMetadata[​](#getcontractclassmetadata "Direct link to getContractClassMetadata") **Signature:** ``` getContractClassMetadata( id: Fr, includeArtifact?: boolean ): Promise ``` **Parameters:** * `id`: `Fr` * `includeArtifact` (optional): `boolean` **Returns:** `Promise` ##### getContractMetadata[​](#getcontractmetadata "Direct link to getContractMetadata") **Signature:** ``` getContractMetadata(address: AztecAddress): Promise ``` **Parameters:** * `address`: `AztecAddress` **Returns:** `Promise` ##### getPrivateEvents[​](#getprivateevents "Direct link to getPrivateEvents") **Signature:** ``` getPrivateEvents( eventMetadata: EventMetadataDefinition, eventFilter: PrivateEventFilter ): Promise[]> ``` **Parameters:** * `eventMetadata`: `EventMetadataDefinition` * `eventFilter`: `PrivateEventFilter` **Returns:** `Promise[]>` ##### getChainInfo[​](#getchaininfo "Direct link to getChainInfo") **Signature:** ``` getChainInfo(): Promise ``` **Returns:** `Promise` ##### getTxReceipt[​](#gettxreceipt "Direct link to getTxReceipt") **Signature:** ``` getTxReceipt(txHash: TxHash): Promise ``` **Parameters:** * `txHash`: `TxHash` **Returns:** `Promise` ##### registerSender[​](#registersender "Direct link to registerSender") **Signature:** ``` registerSender( address: AztecAddress, alias?: string ): Promise ``` **Parameters:** * `address`: `AztecAddress` * `alias` (optional): `string` **Returns:** `Promise` ##### getAddressBook[​](#getaddressbook "Direct link to getAddressBook") **Signature:** ``` getAddressBook(): Promise[]> ``` **Returns:** `Promise[]>` ##### getAccounts[​](#getaccounts "Direct link to getAccounts") **Signature:** ``` getAccounts(): Promise[]> ``` **Returns:** `Promise[]>` ##### registerContract[​](#registercontract "Direct link to registerContract") **Signature:** ``` registerContract( instance: ContractInstanceWithAddress, artifact?: ContractArtifact, secretKey?: Fr ): Promise ``` **Parameters:** * `instance`: `ContractInstanceWithAddress` * `artifact` (optional): `ContractArtifact` * `secretKey` (optional): `Fr` **Returns:** `Promise` ##### simulateTx[​](#simulatetx "Direct link to simulateTx") **Signature:** ``` simulateTx( exec: ExecutionPayload, opts: SimulateOptions ): Promise ``` **Parameters:** * `exec`: `ExecutionPayload` * `opts`: `SimulateOptions` **Returns:** `Promise` ##### simulateUtility[​](#simulateutility "Direct link to simulateUtility") **Signature:** ``` simulateUtility( call: FunctionCall, authwits?: AuthWitness[] ): Promise ``` **Parameters:** * `call`: `FunctionCall` * `authwits` (optional): `AuthWitness[]` **Returns:** `Promise` ##### profileTx[​](#profiletx "Direct link to profileTx") **Signature:** ``` profileTx( exec: ExecutionPayload, opts: ProfileOptions ): Promise ``` **Parameters:** * `exec`: `ExecutionPayload` * `opts`: `ProfileOptions` **Returns:** `Promise` ##### sendTx[​](#sendtx "Direct link to sendTx") **Signature:** ``` sendTx( exec: ExecutionPayload, opts: SendOptions ): Promise ``` **Parameters:** * `exec`: `ExecutionPayload` * `opts`: `SendOptions` **Returns:** `Promise` ##### createAuthWit[​](#createauthwit-2 "Direct link to createAuthWit") **Signature:** ``` createAuthWit( from: AztecAddress, messageHashOrIntent: Fr | IntentInnerHash | CallIntent ): Promise ``` **Parameters:** * `from`: `AztecAddress` * `messageHashOrIntent`: `Fr | IntentInnerHash | CallIntent` **Returns:** `Promise` ##### batch[​](#batch "Direct link to batch") **Signature:** ``` batch[]>(methods: T): Promise> ``` **Parameters:** * `methods`: `T` **Returns:** `Promise>` #### FunctionCallSchema[​](#functioncallschema "Direct link to FunctionCallSchema") **Type:** Constant **Value Type:** `any` #### ExecutionPayloadSchema[​](#executionpayloadschema "Direct link to ExecutionPayloadSchema") **Type:** Constant **Value Type:** `any` #### GasSettingsOptionSchema[​](#gassettingsoptionschema "Direct link to GasSettingsOptionSchema") **Type:** Constant **Value Type:** `any` #### WalletSimulationFeeOptionSchema[​](#walletsimulationfeeoptionschema "Direct link to WalletSimulationFeeOptionSchema") **Type:** Constant **Value Type:** `any` #### SendOptionsSchema[​](#sendoptionsschema "Direct link to SendOptionsSchema") **Type:** Constant **Value Type:** `any` #### SimulateOptionsSchema[​](#simulateoptionsschema "Direct link to SimulateOptionsSchema") **Type:** Constant **Value Type:** `any` #### ProfileOptionsSchema[​](#profileoptionsschema "Direct link to ProfileOptionsSchema") **Type:** Constant **Value Type:** `any` #### MessageHashOrIntentSchema[​](#messagehashorintentschema "Direct link to MessageHashOrIntentSchema") **Type:** Constant **Value Type:** `any` #### BatchedMethodSchema[​](#batchedmethodschema "Direct link to BatchedMethodSchema") **Type:** Constant **Value Type:** `any` #### ContractMetadataSchema[​](#contractmetadataschema "Direct link to ContractMetadataSchema") **Type:** Constant **Value Type:** `any` #### ContractClassMetadataSchema[​](#contractclassmetadataschema "Direct link to ContractClassMetadataSchema") **Type:** Constant **Value Type:** `any` #### EventMetadataDefinitionSchema[​](#eventmetadatadefinitionschema "Direct link to EventMetadataDefinitionSchema") **Type:** Constant **Value Type:** `any` #### PrivateEventSchema[​](#privateeventschema "Direct link to PrivateEventSchema") **Type:** Constant **Value Type:** `ZodFor>` #### PrivateEventFilterSchema[​](#privateeventfilterschema "Direct link to PrivateEventFilterSchema") **Type:** Constant **Value Type:** `any` #### WalletSchema[​](#walletschema "Direct link to WalletSchema") **Type:** Constant **Value Type:** `ApiSchemaFor` --- # Connect to Local Network This guide shows you how to connect your application to the Aztec local network and interact with the network. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Running Aztec local network (see [Quickstart](/developers/testnet/getting_started_on_local_network.md)) on port 8080 * Node.js installed * TypeScript project set up ## Install dependencies[​](#install-dependencies "Direct link to Install dependencies") ``` yarn add @aztec/aztec.js@5.0.0-rc.2 @aztec/wallets@5.0.0-rc.2 ``` ## Connect to the network[​](#connect-to-the-network "Direct link to Connect to the network") Create a node client and EmbeddedWallet to interact with the local network: connect\_to\_network ``` import { createAztecNodeClient, waitForNode } from "@aztec/aztec.js/node"; import { EmbeddedWallet } from "@aztec/wallets/embedded"; import { getInitialTestAccountsData } from "@aztec/accounts/testing"; const nodeUrl = process.env.AZTEC_NODE_URL ?? "http://localhost:8080"; const node = createAztecNodeClient(nodeUrl); // Wait for the network to be ready await waitForNode(node); // Create an EmbeddedWallet connected to the node const wallet = await EmbeddedWallet.create(node, { ephemeral: true }); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L1-L14](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_connection/index.ts#L1-L14) About EmbeddedWallet `EmbeddedWallet` is a simplified wallet for local development that implements the same `Wallet` interface used in production. It handles key management, transaction signing, and proof generation in-process without external dependencies. **Why use it for testing?** It starts instantly, requires no setup, and provides deterministic behavior—ideal for automated tests and rapid iteration. **Production wallets** (like browser extensions or mobile apps) implement the same interface but store keys securely, may require user confirmation for transactions, and typically run in a separate process. Code written against `EmbeddedWallet` works with any `Wallet` implementation, so your application logic transfers directly to production. ### Verify the connection[​](#verify-the-connection "Direct link to Verify the connection") Get node information to confirm your connection: verify\_connection ``` const nodeInfo = await node.getNodeInfo(); console.log("Connected to local network version:", nodeInfo.nodeVersion); console.log("Chain ID:", nodeInfo.l1ChainId); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L16-L20](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_connection/index.ts#L16-L20) ### Load pre-funded accounts[​](#load-pre-funded-accounts "Direct link to Load pre-funded accounts") The local network has accounts pre-funded with fee juice to pay for gas. Register them in your wallet: load\_accounts ``` const testAccounts = await getInitialTestAccountsData(); const [aliceAddress, bobAddress] = await Promise.all( testAccounts.slice(0, 2).map(async (account) => { return ( await wallet.createSchnorrInitializerlessAccount( account.secret, account.salt, account.signingKey, ) ).address; }), ); console.log(`Alice's address: ${aliceAddress.toString()}`); console.log(`Bob's address: ${bobAddress.toString()}`); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L22-L38](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_connection/index.ts#L22-L38) These accounts are pre-funded with fee juice (the native gas token) at genesis, so you can immediately send transactions without needing to bridge funds from L1. ### Check fee juice balance[​](#check-fee-juice-balance "Direct link to Check fee juice balance") Verify that an account has fee juice for transactions: check\_fee\_juice ``` import { getFeeJuiceBalance } from "@aztec/aztec.js/utils"; const aliceBalance = await getFeeJuiceBalance(aliceAddress, node); console.log(`Alice's fee juice balance: ${aliceBalance}`); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L40-L45](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_connection/index.ts#L40-L45) ## Next steps[​](#next-steps "Direct link to Next steps") * [Create an account](/developers/testnet/docs/aztec-js/how_to_create_account.md) - Deploy new accounts on the network * [Deploy a contract](/developers/testnet/docs/aztec-js/how_to_deploy_contract.md) - Deploy your smart contracts * [Send transactions](/developers/testnet/docs/aztec-js/how_to_send_transaction.md) - Execute contract functions --- # Creating Accounts This guide shows you how to create and deploy a new account on Aztec. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * [Connected to a network](/developers/testnet/docs/aztec-js/how_to_connect_to_local_network.md) with a `EmbeddedWallet` instance * Understanding of [account concepts](/developers/testnet/docs/foundational-topics/accounts.md) ## Install dependencies[​](#install-dependencies "Direct link to Install dependencies") ``` yarn add @aztec/aztec.js@5.0.0-rc.2 @aztec/wallets@5.0.0-rc.2 @aztec/noir-contracts.js@5.0.0-rc.2 ``` ## Create a new account[​](#create-a-new-account "Direct link to Create a new account") Using the [`wallet` from the connection guide](/developers/testnet/docs/aztec-js/how_to_connect_to_local_network.md), call `createSchnorrAccount` to create a new account with a random secret and salt: create\_account ``` import { Fr } from "@aztec/aztec.js/fields"; const secret = Fr.random(); const salt = Fr.random(); const newAccount = await wallet.createSchnorrAccount(secret, salt); console.log("New account address:", newAccount.address.toString()); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L47-L54](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_connection/index.ts#L47-L54) The secret is used to derive the account's encryption keys, and the salt ensures address uniqueness. The signing key is automatically derived from the secret. Store your secret and salt Save the `secret` and `salt` values securely. You need both to recover access to your account. If you lose them, you will permanently lose access to the account and any assets it holds. ## Deploy the account[​](#deploy-the-account "Direct link to Deploy the account") New accounts must be deployed before they can send transactions. Deployment requires paying fees. ### Using the Sponsored FPC[​](#using-the-sponsored-fpc "Direct link to Using the Sponsored FPC") If your account doesn't have Fee Juice, use the [Sponsored FPC](/developers/testnet/docs/aztec-js/how_to_pay_fees.md#sponsored-fpc): deploy\_account\_sponsored\_fpc ``` // Additional imports needed for account deployment examples import { NO_FROM } from "@aztec/aztec.js/account"; import { SponsoredFeePaymentMethod } from "@aztec/aztec.js/fee/testing"; import { SponsoredFPCContract } from "@aztec/noir-contracts.js/SponsoredFPC"; import { getContractInstanceFromInstantiationParams } from "@aztec/stdlib/contract"; // Set up the Sponsored FPC payment method (see fees guide for details) const sponsoredFPCInstance = await getContractInstanceFromInstantiationParams( SponsoredFPCContract.artifact, { salt: new Fr(0) }, ); await wallet.registerContract( sponsoredFPCInstance, SponsoredFPCContract.artifact, ); const sponsoredPaymentMethod = new SponsoredFeePaymentMethod( sponsoredFPCInstance.address, ); // newAccount is the account created in the previous section const deployMethod = await newAccount.getDeployMethod(); await deployMethod.send({ from: NO_FROM, fee: { paymentMethod: sponsoredPaymentMethod }, }); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L56-L82](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_connection/index.ts#L56-L82) info See the [guide on fees](/developers/testnet/docs/aztec-js/how_to_pay_fees.md#sponsored-fpc) for more details on the Sponsored FPC and what this snippet means. ### Using Fee Juice[​](#using-fee-juice "Direct link to Using Fee Juice") If your account has Fee Juice from a [bridge from L1](/developers/testnet/docs/aztec-js/how_to_pay_fees.md#bridge-fee-juice-from-l1), you can claim it and deploy in one step using `FeeJuicePaymentMethodWithClaim`. Create a new Schnorr account for this path: create\_fee\_juice\_account ``` // `feeJuiceAccount` is just another Schnorr account, the same kind as // `newAccount` above. It gets its own name here so both deploy paths // can coexist in one example; in your own code, pick whichever name fits. const feeJuiceSecret = Fr.random(); const feeJuiceSalt = Fr.random(); const feeJuiceAccount = await wallet.createSchnorrAccount( feeJuiceSecret, feeJuiceSalt, ); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L84-L94](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_connection/index.ts#L84-L94) Claim the bridged Fee Juice and deploy in one step: bridge\_fee\_juice\_claim ``` import { FeeJuicePaymentMethodWithClaim } from "@aztec/aztec.js/fee"; // claim is from the bridgeTokensPublic step above // Create a payment method that claims the bridged Fee Juice and uses it to pay const bridgePaymentMethod = new FeeJuicePaymentMethodWithClaim( feeJuiceAccount.address, claim, ); // Use it to pay for any transaction; here we deploy the account in one step const deployMethodBridged = await feeJuiceAccount.getDeployMethod(); await deployMethodBridged.send({ from: NO_FROM, fee: { paymentMethod: bridgePaymentMethod }, }); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L163-L179](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_connection/index.ts#L163-L179) If the account already has Fee Juice on L2 (for example, from a faucet or a previously claimed bridge), no special payment method is needed — just call `send({ from: NO_FROM })` and Fee Juice is used automatically. The `from: NO_FROM` signals that this transaction should be executed without account contract mediation. The wallet will directly execute it via a default entrypoint with no authorization. ## Verify deployment[​](#verify-deployment "Direct link to Verify deployment") Confirm the account was deployed successfully. Substitute the account variable for whichever path you used above (`newAccount` for the Sponsored FPC path, `feeJuiceAccount` for the Fee Juice path): verify\_account\_deployment ``` // `newAccount` refers to whichever account you just deployed, // either the Sponsored FPC account or `feeJuiceAccount` from the Fee Juice path. const metadata = await wallet.getContractMetadata(newAccount.address); console.log("Account deployed:", metadata.initializationStatus); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L181-L186](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_connection/index.ts#L181-L186) ## Next steps[​](#next-steps "Direct link to Next steps") * [Deploy contracts](/developers/testnet/docs/aztec-js/how_to_deploy_contract.md) with your new account * [Send transactions](/developers/testnet/docs/aztec-js/how_to_send_transaction.md) from an account * Learn about [account abstraction](/developers/testnet/docs/foundational-topics/accounts.md) * Implement [authentication witnesses](/developers/testnet/docs/aztec-js/how_to_use_authwit.md) --- # Deploying Contracts This guide shows you how to deploy compiled contracts to Aztec using the generated TypeScript interfaces. ## Overview[​](#overview "Direct link to Overview") Deploying a contract to Aztec involves publishing the contract class (the bytecode) and creating a contract instance at a specific address. The generated TypeScript classes handle this process through an API: you call `deploy()` with constructor arguments and `send()` with transaction options to deploy and get the contract instance. The contract address is deterministically computed from the contract class, constructor arguments, salt, and deployer address. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Compiled contract artifacts (see [How to Compile](/developers/testnet/docs/aztec-nr/compiling_contracts.md)) * [Connected to a network](how_to_connect_to_local_network) with an `EmbeddedWallet` instance and funded accounts * TypeScript project set up ## Generate TypeScript bindings[​](#generate-typescript-bindings "Direct link to Generate TypeScript bindings") ### Compile and generate code[​](#compile-and-generate-code "Direct link to Compile and generate code") ``` # Compile the contract aztec compile # Generate TypeScript interface aztec codegen ./target/my_contract-MyContract.json -o src/artifacts ``` info The codegen command creates a TypeScript class with typed methods for deployment and interaction. This provides type safety and autocompletion in your IDE. ## Deploy a contract[​](#deploy-a-contract "Direct link to Deploy a contract") ### Step 1: Import and connect[​](#step-1-import-and-connect "Direct link to Step 1: Import and connect") ``` import { MyContract } from "./artifacts/MyContract"; ``` About wallets and accounts In the examples below, `wallet` refers to a `Wallet` instance that manages keys and signs transactions. See [Creating Accounts](/developers/testnet/docs/aztec-js/how_to_create_account.md) for how to set up a wallet. The `from` option in `send()` specifies which account pays for the transaction. This account must be registered in the wallet and have sufficient fee juice. On a local network, test accounts are pre-funded; on testnet, you typically use sponsored fees. ### Step 2: Deploy the contract[​](#step-2-deploy-the-contract "Direct link to Step 2: Deploy the contract") How you deploy depends on how you pay for it. When paying using an account's fee juice (like a test account on the local network): deploy\_basic\_local ``` // wallet and aliceAddress are from the connection guide // Deploy with constructor arguments const { contract: token } = await TokenContract.deploy( wallet, aliceAddress, "TestToken", "TST", 18, ).send({ from: aliceAddress }); // alice has fee juice and is registered in the wallet ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L38-L48](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L38-L48) On testnet, your account likely won't have Fee Juice. Instead, pay fees using the [Sponsored Fee Payment Contract method](/developers/testnet/docs/aztec-js/how_to_pay_fees.md): deploy\_sponsored\_fpc\_contract ``` // Set up the Sponsored FPC (see fees guide for full setup) const sponsoredFPCInstance = await getContractInstanceFromInstantiationParams( SponsoredFPCContract.artifact, { salt: new Fr(0) }, ); await wallet.registerContract( sponsoredFPCInstance, SponsoredFPCContract.artifact, ); const sponsoredPaymentMethod = new SponsoredFeePaymentMethod( sponsoredFPCInstance.address, ); // wallet is from the connection guide; sponsoredPaymentMethod is from the fees guide const { contract: sponsoredContract } = await TokenContract.deploy( wallet, aliceAddress, "SponsoredToken", "SPT", 18, ).send({ from: aliceAddress, fee: { paymentMethod: sponsoredPaymentMethod } }); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L50-L72](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L50-L72) Here's a complete example from the test suite: deploy\_basic ``` const { contract } = await StatefulTestContract.deploy(wallet, owner, 42).send({ from: defaultAccountAddress }); ``` > [Source code: yarn-project/end-to-end/src/e2e\_deploy\_contract/deploy\_method.test.ts#L48-L50](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/yarn-project/end-to-end/src/e2e_deploy_contract/deploy_method.test.ts#L48-L50) ## Use deployment options[​](#use-deployment-options "Direct link to Use deployment options") ### Deploy with custom salt[​](#deploy-with-custom-salt "Direct link to Deploy with custom salt") By default, the deployment's salt is random, but you can specify it (for example, if you want to get a deterministic address): deploy\_custom\_salt ``` // wallet and aliceAddress are from the connection guide const customSalt = Fr.random(); const { contract: saltedContract } = await TokenContract.deploy( wallet, aliceAddress, "SaltedToken", "SALT", 18, { salt: customSalt }, ).send({ from: aliceAddress, }); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L74-L88](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L74-L88) ### Deploy universally[​](#deploy-universally "Direct link to Deploy universally") Deploy to the same address across networks by setting `universalDeploy: true`: deploy\_universal ``` const opts = { universalDeploy: true, from: defaultAccountAddress }; const { contract } = await StatefulTestContract.deploy(wallet, owner, 42).send(opts); ``` > [Source code: yarn-project/end-to-end/src/e2e\_deploy\_contract/deploy\_method.test.ts#L67-L70](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/yarn-project/end-to-end/src/e2e_deploy_contract/deploy_method.test.ts#L67-L70) info Universal deployment excludes the sender from address computation, allowing the same address on any network with the same salt. ### Skip initialization[​](#skip-initialization "Direct link to Skip initialization") Deploy without running the constructor: skip\_initialization ``` // Deploy without running the constructor using skipInitialization const { contract: delayedToken } = await TokenContract.deploy( wallet, aliceAddress, "DelayedToken", "DLY", 18, ).send({ from: aliceAddress, skipInitialization: true, }); console.log(`Contract deployed at: ${delayedToken.address}`); // Initialize later by calling the constructor manually await delayedToken.methods .constructor(aliceAddress, "DelayedToken", "DLY", 18) .send({ from: aliceAddress }); console.log("Contract initialized"); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L274-L295](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L274-L295) ### Deploy with a specific initializer[​](#deploy-with-a-specific-initializer "Direct link to Deploy with a specific initializer") Some contracts have multiple initializer functions (e.g., both a private `constructor` and a `public_constructor`). By default, the generated `deploy()` method uses the default initializer (typically named `constructor`). To deploy using a different initializer, use `deployWithOpts`: deploy\_with\_opts ``` const { contract } = await StatefulTestContract.deployWithOpts( { wallet, method: 'public_constructor' }, owner, 42, ).send({ from: defaultAccountAddress, }); ``` > [Source code: yarn-project/end-to-end/src/e2e\_deploy\_contract/deploy\_method.test.ts#L91-L99](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/yarn-project/end-to-end/src/e2e_deploy_contract/deploy_method.test.ts#L91-L99) The `deployWithOpts` method accepts an options object as its first argument: * `wallet`: The wallet to use for deployment (required) * `method`: The name of the initializer function to call (optional, defaults to `constructor`) * `publicKeys`: Custom public keys for the contract instance (optional) The remaining arguments are the parameters for the chosen initializer function. tip This is useful for contracts that support multiple initialization patterns, such as token standards that allow both private and public minting during deployment. ## Calculate deployment address[​](#calculate-deployment-address "Direct link to Calculate deployment address") ### Get address before deployment[​](#get-address-before-deployment "Direct link to Get address before deployment") calculate\_address\_before\_deploy ``` // Calculate address without deploying // wallet is from the connection guide (see prerequisites) const deploymentSalt = Fr.random(); const deployMethod = TokenContract.deploy( wallet, aliceAddress, "PredictedToken", "PRED", 18, { salt: deploymentSalt, deployer: aliceAddress }, ); const instance = await deployMethod.getInstance(); const predictedAddress = instance.address; console.log(`Contract will deploy at: ${predictedAddress}`); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L90-L106](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L90-L106) warning This is an advanced pattern. For most use cases, deploy the contract directly and get the address from the deployed instance. ## Monitor deployment progress[​](#monitor-deployment-progress "Direct link to Monitor deployment progress") ### Track deployment transaction[​](#track-deployment-transaction "Direct link to Track deployment transaction") Use `NO_WAIT` to get the transaction hash immediately and track deployment: no\_wait\_deploy ``` // Use NO_WAIT to get the transaction hash immediately and track deployment const { txHash } = await TokenContract.deploy( wallet, aliceAddress, "AnotherToken", "ATK", 18, ).send({ from: aliceAddress, wait: NO_WAIT, }); console.log(`Deployment tx: ${txHash}`); // Wait for the transaction to be mined using the node const receipt = await waitForTx(node, txHash); console.log(`Deployed in block ${receipt.blockNumber}`); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L147-L165](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L147-L165) For most use cases, simply await the deployment to get the contract directly: deploy\_contract ``` import { TokenContract } from "@aztec/noir-contracts.js/Token"; const { contract: token } = await TokenContract.deploy( wallet, aliceAddress, "TestToken", "TST", 18, ).send({ from: aliceAddress }); console.log(`Token deployed at: ${token.address.toString()}`); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L125-L137](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_connection/index.ts#L125-L137) ## Deploy multiple contracts[​](#deploy-multiple-contracts "Direct link to Deploy multiple contracts") ### Deploy a token contract[​](#deploy-a-token-contract "Direct link to Deploy a token contract") Here's an example deploying a `TokenContract` with constructor arguments for admin, name, symbol, and decimals: deploy\_token ``` const { contract: token } = await TokenContract.deploy(wallet, owner, 'TOKEN', 'TKN', 18).send({ from: defaultAccountAddress, }); ``` > [Source code: yarn-project/end-to-end/src/e2e\_deploy\_contract/deploy\_method.test.ts#L80-L84](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/yarn-project/end-to-end/src/e2e_deploy_contract/deploy_method.test.ts#L80-L84) ### Deploy contracts with dependencies[​](#deploy-contracts-with-dependencies "Direct link to Deploy contracts with dependencies") When one contract depends on another, deploy them sequentially and pass the first contract's address: deploy\_with\_dependencies ``` // Deploy contracts with dependencies - deploy sequentially and pass addresses const { contract: baseToken } = await TokenContract.deploy( wallet, aliceAddress, "BaseToken", "BASE", 18, ).send({ from: aliceAddress }); // A second contract could reference the first (example pattern) const { contract: derivedToken } = await TokenContract.deploy( wallet, baseToken.address, // Use first contract's address as admin "DerivedToken", "DERIV", 18, ).send({ from: aliceAddress }); console.log(`Base token at: ${baseToken.address.toString()}`); console.log(`Derived token at: ${derivedToken.address.toString()}`); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L226-L247](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L226-L247) ### Deploy contracts in parallel[​](#deploy-contracts-in-parallel "Direct link to Deploy contracts in parallel") parallel\_deploy ``` // Deploy contracts in parallel using Promise.all const contracts = await Promise.all([ TokenContract.deploy(wallet, aliceAddress, "Token1", "T1", 18) .send({ from: aliceAddress, }) .then(({ contract }) => contract), TokenContract.deploy(wallet, aliceAddress, "Token2", "T2", 18) .send({ from: aliceAddress, }) .then(({ contract }) => contract), TokenContract.deploy(wallet, aliceAddress, "Token3", "T3", 18) .send({ from: aliceAddress, }) .then(({ contract }) => contract), ]); console.log(`Contract 1 at: ${contracts[0].address}`); console.log(`Contract 2 at: ${contracts[1].address}`); console.log(`Contract 3 at: ${contracts[2].address}`); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L249-L272](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L249-L272) Parallel deployment considerations Parallel deployment is faster, but transactions from the same account share a nonce sequence. The wallet handles nonce assignment automatically, but if one deployment fails, subsequent deployments may also fail due to nonce gaps. For reliable parallel deployments: * Use separate accounts for each deployment, or * Handle failures gracefully and retry with fresh nonces * Consider using `BatchCall` to bundle multiple operations into a single transaction (see below) ### Deploy with BatchCall[​](#deploy-with-batchcall "Direct link to Deploy with BatchCall") Use `BatchCall` to bundle a deployment with other calls into a single transaction. This is useful when you need to deploy a contract and immediately call methods on it: deploy\_batch ``` // Create a contract instance and make the PXE aware of it const deployMethod = StatefulTestContract.deploy(wallet, owner, 42, { deployer: defaultAccountAddress }); const contract = await deployMethod.register(); // Batch deployment and a public call into the same transaction const publicCall = contract.methods.increment_public_value(owner, 84); await new BatchCall(wallet, [deployMethod, publicCall]).send({ from: defaultAccountAddress }); ``` > [Source code: yarn-project/end-to-end/src/e2e\_deploy\_contract/deploy\_method.test.ts#L171-L179](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/yarn-project/end-to-end/src/e2e_deploy_contract/deploy_method.test.ts#L171-L179) ## Verify deployment[​](#verify-deployment "Direct link to Verify deployment") ### Check contract state[​](#check-contract-state "Direct link to Check contract state") Use `wallet.getContractMetadata()` to check your contract's current state: ``` const metadata = await wallet.getContractMetadata(contractAddress); // Check each state: metadata.instance; // Contract registered in your wallet? metadata.isContractClassPubliclyRegistered; // Class registered on the network? metadata.isContractPublished; // Instance registered on the network? metadata.initializationStatus; // Constructor has been called? ``` For a complete overview of what these states mean and when functions become callable, see [Contract Readiness States](/developers/testnet/docs/aztec-nr/contract_readiness_states.md). Here's a complete example: verify\_deployment ``` const metadata = await wallet.getContractMetadata(contract.address); const classMetadata = await wallet.getContractClassMetadata(metadata.instance!.currentContractClassId); const isPublished = classMetadata.isContractClassPubliclyRegistered; ``` > [Source code: yarn-project/end-to-end/src/e2e\_deploy\_contract/deploy\_method.test.ts#L57-L61](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/yarn-project/end-to-end/src/e2e_deploy_contract/deploy_method.test.ts#L57-L61) ### What the PXE checks automatically[​](#what-the-pxe-checks-automatically "Direct link to What the PXE checks automatically") When you simulate or send a transaction, the PXE automatically verifies: * Contract instance is registered in your wallet * Contract artifact is available locally * Contract class ID matches the network state The PXE does **not** automatically check: * Whether the contract is published on the network * Whether the contract is initialized * Whether the contract class is registered on the network If you call a public function on an unpublished contract, the transaction will fail at the network level, not during local simulation. Use `getContractMetadata()` to check these states before sending transactions if you want to provide better error messages to users. ### Verify contract is callable[​](#verify-contract-is-callable "Direct link to Verify contract is callable") verify\_contract\_callable ``` // token is from the deployment step above; aliceAddress is from the connection guide try { // Try calling a view function const { result: balance } = await token.methods .balance_of_public(aliceAddress) .simulate({ from: aliceAddress }); console.log("Contract is callable, balance:", balance); } catch (error) { console.error("Contract not accessible:", (error as Error).message); } ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L108-L119](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L108-L119) ## Register deployed contracts[​](#register-deployed-contracts "Direct link to Register deployed contracts") ### Add existing contract to wallet[​](#add-existing-contract-to-wallet "Direct link to Add existing contract to wallet") If a contract was deployed by another account: register\_external\_contract ``` // wallet is from the connection guide; contractAddress is the address of the deployed contract const contractAddress = token.address; // Get the contract metadata from the node (includes the instance) const metadata = await wallet.getContractMetadata(contractAddress); // Register the contract with the wallet // The registerContract method takes positional parameters: // - instance: ContractInstanceWithAddress (required) // - artifact: ContractArtifact (optional) // - secretKey: Fr (optional) await wallet.registerContract(metadata.instance!, TokenContract.artifact); // Now you can interact with the contract const externalContract = await TokenContract.at(contractAddress, wallet); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L121-L137](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L121-L137) warning You need the exact deployment parameters (salt, initialization hash, etc.) to correctly register an externally deployed contract. If you don't have access to the contract instance, you can reconstruct it: reconstruct\_contract\_instance ``` // Reconstruct a contract instance from deployment parameters // Use this when you need to register a contract deployed by someone else const reconstructedInstance = await getContractInstanceFromInstantiationParams( TokenContract.artifact, { publicKeys: PublicKeys.default(), constructorArtifact: "constructor", constructorArgs: [aliceAddress, "ReconstructedToken", "RTK", 18], deployer: aliceAddress, salt: new Fr(12345), // The original deployment salt }, ); // Register the reconstructed contract with the wallet await wallet.registerContract(reconstructedInstance, TokenContract.artifact); console.log( `Reconstructed contract address: ${reconstructedInstance.address.toString()}`, ); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L191-L210](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L191-L210) ## Next steps[​](#next-steps "Direct link to Next steps") * [Contract Readiness States](/developers/testnet/docs/aztec-nr/contract_readiness_states.md) - Understand the different states a contract progresses through * [Send transactions](/developers/testnet/docs/aztec-js/how_to_send_transaction.md) to interact with your contract * [Read contract data](/developers/testnet/docs/aztec-js/how_to_read_data.md) including simulating functions and reading events * [Use authentication witnesses](/developers/testnet/docs/aztec-js/how_to_use_authwit.md) for delegated calls --- # Paying Fees This guide walks you through paying transaction fees on Aztec using various payment methods. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * [Connected to a network](how_to_connect_to_local_network) with an `EmbeddedWallet` instance and funded accounts * Understanding of [fee concepts](/developers/testnet/docs/foundational-topics/fees.md) info The fee asset is only transferrable within a block to the current sequencer, as it powers the fee abstraction mechanism on Aztec. The asset is not transferable beyond this to ensure credible neutrality between all third party developer made asset portals and to ensure local compliance rules can be followed. ## Payment methods overview[​](#payment-methods-overview "Direct link to Payment methods overview") | Method | Use Case | Privacy | Requirements | | ------------------- | -------------------------------------- | ------------- | ------------------------- | | Fee Juice (default) | Account already has Fee Juice | Public | Funded account | | Sponsored FPC | Testing, free transactions | Public | None | | Private FPC | Privacy-preserving fees | Private | Bridged Fee Juice via FPC | | Third-party FPC | Pay in other tokens on testnet/mainnet | Varies by FPC | FPC provider's SDK | | Bridge + Claim | Bootstrap from L1 | Public | L1 ETH for gas | ## Mana and Fee Juice[​](#mana-and-fee-juice "Direct link to Mana and Fee Juice") Mana is Aztec's unit of computational effort (like gas on Ethereum), and Fee Juice is the native fee token used to pay for transactions. For a detailed explanation of these concepts, see [Fee Concepts](/developers/testnet/docs/foundational-topics/fees.md). ## Estimate mana costs[​](#estimate-mana-costs "Direct link to Estimate mana costs") Automatic estimation with EmbeddedWallet When using `EmbeddedWallet`, gas is estimated automatically on every `send()` call. You only need to manually estimate if you want to preview costs before sending, or if you're using a custom wallet implementation. Before sending a transaction, you can read the mana it will consume by simulating with `includeMetadata: true` and reading `gasUsed` from the result: estimate\_mana ``` const { gasUsed } = await token.methods .transfer_in_public(aliceAddress, bobAddress, 1n, 0n) .simulate({ from: aliceAddress, includeMetadata: true }); // Pad the raw usage yourself to leave headroom for variance, e.g. 10%. const estimatedGasLimits = gasUsed!.totalGas.mul(1.1); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L378-L384](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L378-L384) The `gasUsed` object contains the raw gas the simulation consumed: * `totalGas.daGas` / `totalGas.l2Gas` - DA and L2 mana consumed across the whole transaction * `teardownGas.daGas` / `teardownGas.l2Gas` - DA and L2 mana consumed in the teardown phase It is up to you to derive the gas limits you declare from this raw usage (typically by padding it, as shown above). If you don't declare any gas limits, the wallet fills in the network's per-tx admission limits for you. ### Calculate expected fee from estimate[​](#calculate-expected-fee-from-estimate "Direct link to Calculate expected fee from estimate") To calculate the expected fee from the padded gas, use the `computeFee` method with current network fees: compute\_fee\_from\_estimate ``` const currentFees = await node.getCurrentMinFees(); const estimatedFee = estimatedGasLimits.computeFee(currentFees).toBigInt(); console.log("Estimated fee:", estimatedFee); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L386-L390](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L386-L390) tip Pad the raw `gasUsed` yourself to leave a safety margin. Multiplying by `1.1` adds 10%; use higher padding for transactions with variable gas costs. The wallet rejects any declared limit above the network's per-tx admission limit. ## Get transaction fee from receipt[​](#get-transaction-fee-from-receipt "Direct link to Get transaction fee from receipt") After a transaction is mined, you can retrieve the fee paid from the receipt: get\_fee\_from\_receipt ``` const { receipt: feeReceipt } = await token.methods .mint_to_public(aliceAddress, 1n) .send({ from: aliceAddress }); console.log("Transaction fee:", feeReceipt.transactionFee); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L392-L397](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L392-L397) The `transactionFee` field is a `bigint` representing the total fee paid in the fee token (Fee Juice). You can also check execution status: check\_receipt\_status ``` console.log("Succeeded:", feeReceipt.hasExecutionSucceeded()); console.log("Block:", feeReceipt.blockNumber); console.log("Fee paid:", feeReceipt.transactionFee); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L399-L403](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L399-L403) ## Pay with Fee Juice[​](#pay-with-fee-juice "Direct link to Pay with Fee Juice") Fee Juice is the native fee token on Aztec. If your account has Fee Juice (for example, from a faucet), is [deployed](/developers/testnet/docs/aztec-js/how_to_create_account.md), and is registered in your wallet, it will be used automatically to pay for the fee of the transaction: pay\_with\_fee\_juice ``` // contract is a deployed contract instance; aliceAddress is from the connection guide const { receipt: feeJuiceReceipt } = await token.methods .mint_to_public(aliceAddress, 1n) .send({ from: aliceAddress, // no fee payment method needed; Fee Juice is used automatically }); console.log("Transaction fee:", feeJuiceReceipt.transactionFee); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L405-L414](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L405-L414) ## Use Fee Payment Contracts[​](#use-fee-payment-contracts "Direct link to Use Fee Payment Contracts") Fee Payment Contracts (FPCs) pay Fee Juice on your behalf. An FPC holds its own Fee Juice balance to pay the protocol and can accept other tokens from users in exchange. Some FPCs operate privately by design, routing fee payments through private notes rather than public function calls. note The SDK includes `PrivateFeePaymentMethod` and `PublicFeePaymentMethod` classes for the built-in reference FPC, but these are **deprecated** and do not work on mainnet alpha. For custom-token fee payment, use a third-party FPC with its own SDK (see [below](#third-party-fpcs-on-testnet-and-mainnet)). ### Sponsored FPC[​](#sponsored-fpc "Direct link to Sponsored FPC") The Sponsored FPC pays fees unconditionally, enabling free transactions. It is available on testnet, devnet, and local network. You can derive the Sponsored FPC address from its deployment parameters, register it with your wallet, and use it to pay for transactions: deploy\_sponsored\_fpc\_contract ``` // Set up the Sponsored FPC (see fees guide for full setup) const sponsoredFPCInstance = await getContractInstanceFromInstantiationParams( SponsoredFPCContract.artifact, { salt: new Fr(0) }, ); await wallet.registerContract( sponsoredFPCInstance, SponsoredFPCContract.artifact, ); const sponsoredPaymentMethod = new SponsoredFeePaymentMethod( sponsoredFPCInstance.address, ); // wallet is from the connection guide; sponsoredPaymentMethod is from the fees guide const { contract: sponsoredContract } = await TokenContract.deploy( wallet, aliceAddress, "SponsoredToken", "SPT", 18, ).send({ from: aliceAddress, fee: { paymentMethod: sponsoredPaymentMethod } }); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L50-L72](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L50-L72) Here's a simpler example from the test suite: sponsored\_fpc\_simple ``` const bananasToSendToBob = 10n; const { receipt: tx } = await bananaCoin.methods .transfer_in_public(aliceAddress, bobAddress, bananasToSendToBob, 0) .send({ from: aliceAddress, fee: { gasSettings, paymentMethod: new SponsoredFeePaymentMethod(sponsoredFPC.address), }, }); ``` > [Source code: yarn-project/end-to-end/src/e2e\_fees/sponsored\_payments.test.ts#L71-L82](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/yarn-project/end-to-end/src/e2e_fees/sponsored_payments.test.ts#L71-L82) ### Private fee payment[​](#private-fee-payment "Direct link to Private fee payment") For transactions where the fee payment itself should be private, you can use a fully private FPC, one that holds Fee Juice claimed from L1 as an internal private balance, works on every network, and never needs an onchain deployment. See [Pay Fees Privately](/developers/testnet/docs/aztec-js/how_to_use_private_fee_juice.md) for how this pattern works and a walkthrough using a community-built example. Shared salt for privacy When multiple apps derive the same private FPC address (using the same artifact and salt), every private fee payment joins a single, larger privacy set. See [recommended salt](/developers/testnet/docs/aztec-js/how_to_use_private_fee_juice.md#recommended-salt-0) for details. ### Third-party FPCs on testnet and mainnet[​](#third-party-fpcs-on-testnet-and-mainnet "Direct link to Third-party FPCs on testnet and mainnet") On networks where the Sponsored FPC is unavailable, third-party FPCs deployed by ecosystem teams let you pay fees in tokens other than Fee Juice. Each FPC provider typically offers an SDK or API that handles payment method construction on the client side. This may include quote fetching and authwit creation, though the exact flow depends on the FPC design. For background on how FPCs work at the protocol level, see [how FPCs work](/developers/testnet/docs/foundational-topics/fees.md#how-fpcs-work). #### Example: Nethermind Private Multi Asset FPC[​](#example-nethermind-private-multi-asset-fpc "Direct link to Example: Nethermind Private Multi Asset FPC") To illustrate how a third-party FPC integration works, the following walkthrough uses Nethermind's [Private Multi Asset FPC](https://github.com/NethermindEth/aztec-fpc) as a reference. This is one implementation, other FPCs may differ in design and API. This FPC is quote-based and operates privately: * A single deployment accepts many tokens. The asset is selected per quote rather than hard-coded at deploy time. * Fee payments are transferred as private notes, so fee activity is not visible onchain. * An operator-run attestation service signs per-user quotes binding the FPC address, accepted asset, amounts, expiry, and user. * A cold-start entrypoint allows a brand-new account to bridge tokens from L1, claim on L2, and pay the fee in a single transaction. Note that the cold-start path calls `Token::mint_to_private`, which enqueues a public call to update the token's total supply, so the minted amount is visible onchain even though the user's identity and balances remain private. Third-party software This FPC is developed and maintained by Nethermind, not by Aztec Labs. The SDK (`@nethermindeth/aztec-fpc-sdk`) may not yet be published to npm; check the [repository README](https://github.com/NethermindEth/aztec-fpc/blob/main/sdk/README.md) for current install instructions. Review the [protocol spec](https://github.com/NethermindEth/aztec-fpc/blob/main/docs/spec/protocol-spec.md) and evaluate independently before integrating. The SDK wraps the quote-and-pay flow into a single call. The snippet below shows the general shape of the integration (illustrative; verify against the current SDK API before using): ``` import { FpcClient } from "@nethermindeth/aztec-fpc-sdk"; // Point the client at the FPC's attestation service const fpcClient = new FpcClient({ fpcAddress, // the deployed FPC contract address operator, // operator's Aztec address node, // PXE or node connection attestationBaseUrl: "https://...", // attestation service URL from the FPC provider }); // Estimate gas, fetch a signed quote, and build the payment method const payment = await fpcClient.createPaymentMethod({ wallet, user: aliceAddress, // the account paying the fee tokenAddress, // the token you want to pay in estimatedGas, // gas limits derived from a prior simulation's gasUsed }); // Use it like any other payment method const tx = await myContract.methods.myMethod(args).send({ fee: payment.fee }); await tx.wait(); ``` For the cold-start flow, deployment addresses, and the full API, see the [`aztec-fpc` repository](https://github.com/NethermindEth/aztec-fpc). ## Bridge Fee Juice from L1[​](#bridge-fee-juice-from-l1 "Direct link to Bridge Fee Juice from L1") Fee Juice is non-transferable on L2, but you can bridge it from L1, claim it on L2, and use it. This involves a few components that are part of a running network's infrastructure: * An L1 fee juice contract * An L1 fee juice portal * An L2 fee juice portal * An L2 fee juice contract `aztec.js` provides helpers to simplify the process: bridge\_fee\_juice\_setup ``` import { createExtendedL1Client } from "@aztec/ethereum/client"; import { L1FeeJuicePortalManager } from "@aztec/aztec.js/ethereum"; import { createLogger } from "@aztec/aztec.js/log"; // Create an L1 client (accepts a mnemonic or 0x-prefixed private key) const l1RpcUrl = process.env.ETHEREUM_HOST ?? "http://localhost:8545"; const l1Mnemonic = "test test test test test test test test test test test junk"; const l1Client = createExtendedL1Client([l1RpcUrl], l1Mnemonic); // Create a portal manager to interact with the L1 fee juice portal const logger = createLogger("docs:fee-juice-bridge"); const portalManager = await L1FeeJuicePortalManager.new(node, l1Client, logger); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L96-L110](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_connection/index.ts#L96-L110) Under the hood, `L1FeeJuicePortalManager` gets the L1 addresses from the node `aztec_getNodeInfo` endpoint. It then exposes an easy method `bridgeTokensPublic` which mints fee juice on L1 and sends it to an L2 address via the L1 portal: bridge\_fee\_juice\_execute ``` // portalManager is from the L1FeeJuicePortalManager setup above // feeJuiceAccount.address is an Aztec address from createSchnorrAccount const claim = await portalManager.bridgeTokensPublic( feeJuiceAccount.address, // the L2 address 1000000000000000000000n, // the amount to send to the L1 portal true, // whether to mint or not (set to false if your L1 account already has fee juice!) ); console.log("Claim secret:", claim.claimSecret); console.log("Claim amount:", claim.claimAmount); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L112-L123](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_connection/index.ts#L112-L123) After this transaction is minted on L1 and a few blocks pass, you can claim the message on L2 and use it directly to pay for fees: bridge\_fee\_juice\_claim ``` import { FeeJuicePaymentMethodWithClaim } from "@aztec/aztec.js/fee"; // claim is from the bridgeTokensPublic step above // Create a payment method that claims the bridged Fee Juice and uses it to pay const bridgePaymentMethod = new FeeJuicePaymentMethodWithClaim( feeJuiceAccount.address, claim, ); // Use it to pay for any transaction; here we deploy the account in one step const deployMethodBridged = await feeJuiceAccount.getDeployMethod(); await deployMethodBridged.send({ from: NO_FROM, fee: { paymentMethod: bridgePaymentMethod }, }); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L163-L179](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_connection/index.ts#L163-L179) ## Configure gas settings[​](#configure-gas-settings "Direct link to Configure gas settings") ### Understanding gas dimensions[​](#understanding-gas-dimensions "Direct link to Understanding gas dimensions") Gas settings specify limits and fees for both DA and L2 dimensions: * **gasLimits**: Maximum mana for main execution phase * **teardownGasLimits**: Maximum mana for teardown phase (used by FPCs for refunds) * **maxFeesPerGas**: Maximum price you're willing to pay per mana unit * **maxPriorityFeesPerGas**: Priority fee for faster inclusion The fee limit is calculated as `gasLimits × maxFeesPerGas` for each dimension. ### Set custom gas limits[​](#set-custom-gas-limits "Direct link to Set custom gas limits") Set custom gas limits by importing from `stdlib`: custom\_gas\_settings ``` // Query current network fees to set realistic limits const networkFees = await node.getCurrentMinFees(); // Declare at most what the network admits per tx; these limits vary by network geometry, // so read them from the node rather than hardcoding values that may exceed a given network's maximum. const { txsLimits } = await node.getNodeInfo(); const gasLimits = Gas.from(txsLimits.gas); const gasSettings = GasSettings.from({ gasLimits, // Teardown must be strictly less than the total limits so app logic has gas to run. teardownGasLimits: { daGas: Math.floor(gasLimits.daGas / 2), l2Gas: Math.floor(gasLimits.l2Gas / 8), }, maxFeesPerGas: { feePerDaGas: networkFees.feePerDaGas * 2n, feePerL2Gas: networkFees.feePerL2Gas * 2n, }, maxPriorityFeesPerGas: { feePerDaGas: 0n, feePerL2Gas: 0n }, }); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L416-L436](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L416-L436) Then pass the settings when sending: send\_with\_gas\_settings ``` const { receipt: gsReceipt } = await token.methods .mint_to_public(aliceAddress, 1n) .send({ from: aliceAddress, fee: { gasSettings }, }); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L438-L445](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L438-L445) Note that `gasLimits` and `teardownGasLimits` use `daGas`/`l2Gas` field names, while `maxFeesPerGas` and `maxPriorityFeesPerGas` use `feePerDaGas`/`feePerL2Gas`. ### Use automatic gas estimation[​](#use-automatic-gas-estimation "Direct link to Use automatic gas estimation") note When using `EmbeddedWallet`, gas estimation happens automatically on every `send()`; you don't need to declare gas limits at all. Reading `gasUsed` from a `simulate({ includeMetadata: true })` result is useful when you want to preview costs before sending, or to set explicit limits with a custom wallet implementation. auto\_gas\_estimation ``` // Read the gas a transaction would consume before sending, and pad it yourself. const { gasUsed: autoGasUsed } = await token.methods .mint_to_public(aliceAddress, 1n) .simulate({ from: aliceAddress, includeMetadata: true }); const autoEstimate = autoGasUsed!.totalGas.mul(1.2); // 20% padding console.log("Auto-estimated L2 gas:", autoEstimate.l2Gas); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L474-L481](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L474-L481) tip Gas estimation runs a simulation first to determine actual gas usage, then adds padding for safety. This works with all payment methods, including FPCs. ## Next steps[​](#next-steps "Direct link to Next steps") * Learn about [fee concepts](/developers/testnet/docs/foundational-topics/fees.md) in detail * Explore [authentication witnesses](/developers/testnet/docs/aztec-js/how_to_use_authwit.md) for delegated payments * See [testing guide](/developers/testnet/docs/aztec-js/how_to_test.md) for fee testing strategies --- # Reading Contract Data This guide shows you how to read data from Aztec contracts in TypeScript, including simulating function calls, reading raw logs, and retrieving typed events. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * [Connected to a network](how_to_connect_to_local_network) with an `EmbeddedWallet` instance and funded accounts * A deployed contract instance (see [How to Deploy a Contract](/developers/testnet/docs/aztec-js/how_to_deploy_contract.md)) ## Simulating functions[​](#simulating-functions "Direct link to Simulating functions") The `simulate` method executes a contract function locally and returns its result. It works with private, public, and utility functions. No transaction is created and no gas is spent. simulate\_function ``` const { result: balance } = await token.methods .balance_of_public(aliceAddress) .simulate({ from: aliceAddress }); console.log(`Alice's token balance: ${balance}`); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L148-L154](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_connection/index.ts#L148-L154) The `from` option specifies which account context to use for the simulation. This is required for all simulations. For private functions, it determines which account's private state is accessed. For public functions, it sets the `msg_sender` context. ### Handling return values[​](#handling-return-values "Direct link to Handling return values") For functions returning multiple values, destructure the result: ``` // contract and callerAddress are from the example above const { result: [value1, value2] } = await contract.methods .get_multiple_values() .simulate({ from: callerAddress }); ``` ### Including metadata[​](#including-metadata "Direct link to Including metadata") Set `includeMetadata: true` to get additional information about the simulation: simulate\_with\_metadata ``` const metaResult = await token.methods .balance_of_public(aliceAddress) .simulate({ from: aliceAddress, includeMetadata: true }); console.log("Balance:", metaResult.result); // `gasUsed` is the raw gas the simulation consumed; derive your own limits from it (see below). console.log("L2 gas used:", metaResult.gasUsed!.totalGas.l2Gas); console.log("DA gas used:", metaResult.gasUsed!.totalGas.daGas); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L355-L363](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L355-L363) The result includes `result` (the function return value), `stats` (execution statistics), `offchainEffects`, and `gasUsed` (the raw gas the simulation consumed, with `totalGas` and `teardownGas`). Derive your own gas limits from `gasUsed` if you want to declare them explicitly; otherwise the wallet fills in the network's per-tx admission limits. ### Private function considerations[​](#private-function-considerations "Direct link to Private function considerations") When simulating private functions, the caller must have access to any private state being read. The PXE only has visibility into notes belonging to registered accounts. simulate\_private\_access ``` // This works if aliceAddress owns the notes const { result: privateBalance } = await token.methods .balance_of_private(aliceAddress) .simulate({ from: aliceAddress }); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L492-L497](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L492-L497) If the caller doesn't have access to another address's notes, the simulation will fail with an error. tip If `.simulate()` is prompting the user to sign every call, or failing with `min_revertible_side_effect_counter must not be 0` when you pass `from: AztecAddress.ZERO`, see [Simulate without signing prompts](/developers/testnet/docs/aztec-js/how_to_simulate_without_signing.md). warning Simulation runs locally without generating proofs. No correctness guarantees are provided on the result. See [Call Types](/developers/testnet/docs/foundational-topics/call_types.md#simulate) for more details. ## Reading logs vs events[​](#reading-logs-vs-events "Direct link to Reading logs vs events") Contracts emit data in two forms you can read: | Aspect | Logs | Events | | ------------------ | --------------------------------- | -------------------------------------------------- | | **What** | Raw field arrays (untyped) | Decoded domain objects with type info | | **Storage** | Archiver (node-level) | PXE (client-level) for private events | | **API** | `aztecNode.getBlock()` tx effects | `wallet.getPrivateEvents()` or `getPublicEvents()` | | **Type awareness** | None - raw `Fr[]` data | Requires ABI metadata to decode | **Logs** are the low-level transport layer, while **events** are the semantic application layer decoded using ABI metadata from your contract. ## Reading raw public logs[​](#reading-raw-public-logs "Direct link to Reading raw public logs") Raw public logs are carried on each block's transaction effects. Fetch a block with `includeTransactions: true` and read `body.txEffects[*].publicLogs`: read\_public\_logs ``` // Raw public logs are carried on each block's transaction effects. const latestBlockNumber = await node.getBlockNumber(); const block = await node.getBlock(latestBlockNumber, { includeTransactions: true, }); const publicLogs = block?.body.txEffects.flatMap((tx) => tx.publicLogs) ?? []; if (publicLogs.length > 0) { const rawFields = publicLogs[0].getEmittedFields(); // Fr[] console.log("Raw log fields:", rawFields.length); } ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L365-L376](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L365-L376) You can scope this to a single transaction (by locating its block and matching its tx hash) or to a block range (by reading each block's tx effects): read\_logs\_by\_filter ``` // Get raw public logs for a specific transaction by locating its block and tx effect. const txReceiptForLogs = await node.getTxReceipt(gsReceipt.txHash); const txBlock = await node.getBlock(txReceiptForLogs.blockNumber!, { includeTransactions: true, }); const txLogs = txBlock?.body.txEffects .filter((tx) => tx.txHash.equals(gsReceipt.txHash)) .flatMap((tx) => tx.publicLogs) ?? []; // Get raw public logs for a block range by reading each block's tx effects. const tipBlockNumber = await node.getBlockNumber(); const rangeLogs = ( await Promise.all( Array.from({ length: tipBlockNumber }, (_, i) => BlockNumber(i + 1)).map( async (blockNumber) => { const rangeBlock = await node.getBlock(blockNumber, { includeTransactions: true, }); return rangeBlock?.body.txEffects.flatMap((tx) => tx.publicLogs) ?? []; }, ), ) ).flat(); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L447-L472](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L447-L472) ## Reading events[​](#reading-events "Direct link to Reading events") Events provide typed access to contract emissions. The event metadata from your contract artifact (`Contract.events.EventName`) contains the ABI type information needed for decoding. ### Reading public events[​](#reading-public-events "Direct link to Reading public events") Use the `getPublicEvents` helper to retrieve typed public events: import\_get\_public\_events ``` import { getPublicEvents as _importCheck } from "@aztec/aztec.js/events"; ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L483-L485](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L483-L485) get\_public\_events ``` const publicEventFilter: PublicEventFilter = { contractAddress: testLogContract.address, fromBlock: BlockNumber(firstTx.blockNumber!), toBlock: BlockNumber(lastTx.blockNumber! + 1), }; const { events: collectedEvent0s } = await getPublicEvents( aztecNode, TestLogContract.events.ExampleEvent0, publicEventFilter, ); const { events: collectedEvent1s } = await getPublicEvents( aztecNode, TestLogContract.events.ExampleEvent1, publicEventFilter, ); ``` > [Source code: yarn-project/end-to-end/src/e2e\_event\_logs.test.ts#L149-L167](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/yarn-project/end-to-end/src/e2e_event_logs.test.ts#L149-L167) The function parameters are: * `aztecNode` - The node to query * `Contract.events.EventName` - Event metadata from the contract artifact (contains the event selector) * `filter` - An object with optional fields: * `fromBlock` - Starting block number (inclusive) * `toBlock` - Ending block number (exclusive) * `contractAddress` - Filter to a specific contract * `txHash` - Filter to a specific transaction Each returned event includes both the decoded `event` data and `metadata` (block number, block hash, tx hash, contract address). ### Reading private events[​](#reading-private-events "Direct link to Reading private events") Private events are stored in the PXE with privacy scoping. Use `wallet.getPrivateEvents()` to retrieve them: import\_private\_event\_types ``` import type { PrivateEventFilter } from "@aztec/aztec.js/wallet"; import { BlockNumber } from "@aztec/aztec.js/fields"; ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L487-L490](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L487-L490) The `BlockNumber` type is a branded type that wraps raw numbers for type safety. Use it when setting `fromBlock` and `toBlock` in filters. get\_private\_events ``` const eventFilter: PrivateEventFilter = { contractAddress: testLogContract.address, fromBlock: BlockNumber(firstBlockNumber), toBlock: BlockNumber(lastBlockNumber + 1), scopes: [account1Address, account2Address], }; // Each emit_encrypted_events call emits 2 ExampleEvent0s and 1 ExampleEvent1 // So with 5 calls we expect 10 ExampleEvent0s and 5 ExampleEvent1s const collectedEvent0s = await wallet.getPrivateEvents( TestLogContract.events.ExampleEvent0, eventFilter, ); const collectedEvent1s = await wallet.getPrivateEvents( TestLogContract.events.ExampleEvent1, eventFilter, ); ``` > [Source code: yarn-project/end-to-end/src/e2e\_event\_logs.test.ts#L78-L97](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/yarn-project/end-to-end/src/e2e_event_logs.test.ts#L78-L97) The `PrivateEventFilter` includes: * `contractAddress` - The contract that emitted the events * `fromBlock` / `toBlock` - Block range to search * `scopes` - Array of account addresses whose private state is being queried * `txHash` (optional) - Filter to a specific transaction Private events return objects with an `event` property containing the decoded data: ``` collectedEvents.forEach((ev) => { console.log(ev.event.value0); // Access event fields via .event }); ``` ## Polling for events[​](#polling-for-events "Direct link to Polling for events") To continuously monitor for new events, poll at regular intervals while tracking the last processed block: poll\_for\_events ``` // Poll for new events at regular intervals let lastProcessedBlock = await node.getBlockNumber(); async function pollForTransferEvents() { const currentBlock = await node.getBlockNumber(); if (currentBlock > lastProcessedBlock) { const { events } = await getPublicEvents( node, TokenContract.events.Transfer, { contractAddress: token.address, fromBlock: BlockNumber(lastProcessedBlock + 1), toBlock: BlockNumber(currentBlock + 1), // toBlock is exclusive }, ); for (const { event, metadata } of events) { // Process each transfer event console.log( `Transfer: ${event.amount} from ${event.from} to ${event.to}`, ); console.log( ` in block ${metadata.l2BlockNumber}, tx ${metadata.txHash}`, ); } lastProcessedBlock = currentBlock; } } // Example: poll once (in production, use setInterval) await pollForTransferEvents(); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L297-L331](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L297-L331) For private events, use the same pattern with `wallet.getPrivateEvents()` and update the `fromBlock` in your filter accordingly. ## Next steps[​](#next-steps "Direct link to Next steps") * [Send transactions](/developers/testnet/docs/aztec-js/how_to_send_transaction.md) to modify contract state * Learn about [call types](/developers/testnet/docs/foundational-topics/call_types.md) and when to use simulation vs transactions * Explore [testing patterns](/developers/testnet/docs/aztec-js/how_to_test.md) that use simulation --- # Sending Transactions This guide shows you how to send transactions to smart contracts on Aztec. ## Overview[​](#overview "Direct link to Overview") Transactions on Aztec execute contract functions that modify state. Unlike simple reads, transactions go through private execution on your device, proving, and then submission to the network for inclusion in a block. You can send single transactions, batch multiple calls atomically, and query transaction status after submission. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * [Connected to a network](how_to_connect_to_local_network) with an `EmbeddedWallet` instance and funded accounts * Deployed contract with its address and ABI (see [How to Deploy](/developers/testnet/docs/aztec-js/how_to_deploy_contract.md)) * Understanding of [contract interactions](/developers/testnet/docs/aztec-nr/framework-description/calling_contracts.md) ## Send a transaction[​](#send-a-transaction "Direct link to Send a transaction") After connecting to a contract: connect\_to\_contract ``` // wallet is from the connection guide; token is the contract deployed in the deploy guide const contract = await Contract.at( token.address, TokenContract.artifact, wallet, ); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L333-L340](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L333-L340) Call a function and wait for it to be mined: basic\_send\_transaction ``` // contract is from the step above; aliceAddress is from the connection guide const { receipt: sendReceipt } = await contract.methods .transfer_in_public(aliceAddress, bobAddress, 100n, 0n) .send({ from: aliceAddress }); console.log(`Transaction mined in block ${sendReceipt.blockNumber}`); console.log(`Transaction fee: ${sendReceipt.transactionFee}`); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L342-L349](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L342-L349) The `from` field specifies which account sends the transaction. If that account has Fee Juice, it pays for the transaction automatically. For other fee payment options, see [paying fees](/developers/testnet/docs/aztec-js/how_to_pay_fees.md). ### What happens behind the scenes[​](#what-happens-behind-the-scenes "Direct link to What happens behind the scenes") When using `EmbeddedWallet`, calling `send()` triggers a **simulation** step before the transaction is actually sent. This simulation: 1. **Estimates gas limits** based on actual execution, with a configurable padding (default 10%) to avoid reverts. If you provide explicit gas limits via `fee.gasSettings`, they take precedence. 2. **Generates private authwits automatically**. If the contract you're calling requires a private [authentication witness](/developers/testnet/docs/aztec-js/how_to_use_authwit.md) (e.g., a token transfer on behalf of the sender), the wallet detects this during simulation and creates the authwit on the fly — no manual setup needed. This means a simple `.send()` is all most apps need. You can adjust the gas padding if desired: set\_gas\_padding ``` wallet.setEstimatedGasPadding(0.2); // 20% padding instead of the default 10% ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L351-L353](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L351-L353) note Public authwits still need to be set explicitly before the transaction, as they require a separate onchain transaction. See [Using Authentication Witnesses](/developers/testnet/docs/aztec-js/how_to_use_authwit.md) for details. ### Send without waiting[​](#send-without-waiting "Direct link to Send without waiting") Use the `NO_WAIT` option to get the transaction hash immediately without waiting for inclusion: no\_wait\_transaction ``` // Use NO_WAIT for regular transactions too const { txHash: transferTxHash } = await token.methods .transfer(bobAddress, 100n) .send({ from: aliceAddress, wait: NO_WAIT }); console.log(`Transaction sent: ${transferTxHash.toString()}`); // Wait for inclusion later using the node const transferReceipt = await waitForTx(node, transferTxHash); console.log(`Transaction mined in block ${transferReceipt.blockNumber}`); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L167-L178](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L167-L178) ## Send batch transactions[​](#send-batch-transactions "Direct link to Send batch transactions") Execute multiple calls atomically using `BatchCall`: batch\_call ``` // Execute multiple calls atomically using BatchCall const batch = new BatchCall(wallet, [ token.methods.mint_to_public(aliceAddress, 500n), token.methods.transfer(bobAddress, 200n), ]); const { receipt: batchReceipt } = await batch.send({ from: aliceAddress }); console.log(`Batch executed in block ${batchReceipt.blockNumber}`); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L180-L189](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L180-L189) warning All calls in a batch must succeed or the entire batch reverts. Use batch transactions when you need atomic execution of multiple operations. ## Query transaction status[​](#query-transaction-status "Direct link to Query transaction status") After sending a transaction without waiting, you can query its receipt using the node: query\_tx\_status ``` // Query transaction status after sending without waiting const { txHash: statusTxHash } = await token.methods .transfer(bobAddress, 10n) .send({ from: aliceAddress, wait: NO_WAIT }); // Check status using the node const txReceipt = await node.getTxReceipt(statusTxHash); console.log(`Status: ${txReceipt.status}`); console.log(`Block number: ${txReceipt.blockNumber}`); console.log(`Transaction fee: ${txReceipt.transactionFee}`); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L212-L224](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_advanced/index.ts#L212-L224) `getTxReceipt` always resolves to one of three lifecycle variants of the `TxReceipt` union, depending on where the transaction is in its lifecycle: * `PendingTxReceipt` - still in the mempool. Exposes `status` (`pending`) and, when requested, the pending `tx`. * `DroppedTxReceipt` - dropped by the node. Exposes `status` (`dropped`) and an optional `error` message. * `MinedTxReceipt` - included in a block. Exposes `status` (`proposed`, `checkpointed`, `proven`, or `finalized`), `blockNumber`, `blockHash`, `txIndexInBlock`, `transactionFee`, and the execution result. The `status`, `blockNumber`, and `transactionFee` fields are readable on the bare union, but block and fee details are only populated once the transaction is mined. Use the `isMined()`, `isPending()`, and `isDropped()` type guards to narrow to a specific variant before reading its fields: ``` const receipt = await node.getTxReceipt(txHash); if (receipt.isMined()) { console.log(`Mined in block ${receipt.blockNumber}, fee ${receipt.transactionFee}`); } ``` You can pass a second `options` argument to attach extra data to the receipt: * `includeTxEffect` - attaches the full `TxEffect` (note hashes, nullifiers, logs, and messages) to a mined receipt, available as `receipt.txEffect`. * `includePendingTx` - attaches the pending `Tx` to a pending receipt, available as `receipt.tx`. * `includeProof` - keeps the proof on that attached pending tx (only meaningful together with `includePendingTx`; the proof is stripped by default to avoid shipping large payloads over RPC). For example, to read a mined transaction's effects, request them with `includeTxEffect` and read `receipt.txEffect` after narrowing with `isMined()`: ``` const receipt = await node.getTxReceipt(txHash, { includeTxEffect: true }); if (receipt.isMined() && receipt.txEffect) { console.log(`Nullifiers: ${receipt.txEffect.nullifiers.length}`); } ``` ## Next steps[​](#next-steps "Direct link to Next steps") * Learn to [read contract data](/developers/testnet/docs/aztec-js/how_to_read_data.md) including simulating functions before sending * Understand [authentication witnesses](/developers/testnet/docs/aztec-js/how_to_use_authwit.md) for delegated transactions * Configure [gas and fees](/developers/testnet/docs/aztec-js/how_to_pay_fees.md) for transaction costs * Set up [transaction testing](/developers/testnet/docs/aztec-js/how_to_test.md) in your development workflow --- # Simulate without signing prompts You want to call `.simulate()` from an app and not have the user's wallet pop up a signing prompt. This page covers the symptoms that lead to that prompt, why the obvious workarounds do not work, and the right fix. For the conceptual model of what kernelless simulation is, see [Kernelless simulations](/developers/testnet/docs/foundational-topics/pxe/kernelless_simulations.md). ## Symptoms[​](#symptoms "Direct link to Symptoms") You are probably here because of one of these: * The wallet prompts the user for a signature on every `.simulate()` call, including reads of view-style functions. * `.simulate()` fails with one of: * `Account "0x0000…0000" does not exist on this wallet.` (from `EmbeddedWallet`) * `Account not found in wallet for address: 0x0000…0000` (from other wallets built on `BaseWallet`) * `Circuit execution failed: min_revertible_side_effect_counter must not be 0 for tail_to_public` (from a custom wallet that does not intercept the zero address, where the call reaches the kernel) All three are the same root cause: passing `from: AztecAddress.ZERO`. * A custom fee payment method breaks during simulation because `from` is `AztecAddress.ZERO`. * Simulations take long enough that you want to skip the kernel circuits entirely. ## The wrong fix[​](#the-wrong-fix "Direct link to The wrong fix") Do not pass `from: AztecAddress.ZERO`. That value was the old way to express "no account context," and it is no longer a supported input for `.simulate()`. The replacement is `NO_FROM` (from `@aztec/aztec.js/account`), which tells the wallet to execute the payload through the default entrypoint with no account contract mediation. `NO_FROM` is still only appropriate for calls that genuinely have no sender; use a real account address for everything else. ## The right fix[​](#the-right-fix "Direct link to The right fix") A simulation uses a **stub account contract override**: the wallet provides a `SimulationOverrides` payload whose `contracts` map swaps the caller's account contract for a stub whose `is_valid` always returns true, and the PXE applies that override during the kernelless simulation. With the stub in place, authwit validity checks pass without a signature, and the wallet collects any `CallAuthorizationRequest` offchain effects emitted during the run to turn them into real authentication witnesses for the eventual `.send()`. `EmbeddedWallet` installs this override automatically on every `.simulate()` call, so most apps do not need to construct overrides themselves. The two ways to wire this up below correspond to "use the default" and "implement it in your own wallet." ### Calling .simulate() from an app[​](#calling-simulate-from-an-app "Direct link to Calling .simulate() from an app") For a normal `.simulate()` you do not need to pass overrides yourself. The default simulation path is already kernelless, and wallets such as `EmbeddedWallet` install the stub-account override internally for you. Three things to remember: * Pass a real account address as `from`, or `NO_FROM` if the call genuinely has no sender. Do not use `AztecAddress.ZERO`. * For simple reads, you can omit the `fee` block. * For a transaction whose real fee path uses a fee payment contract (FPC) with private side effects (the FPC emits notes during fee payment), include that FPC in the simulation's fee options so gas estimation accounts for the FPC's side effects. Kernelless still applies; the gas number stays accurate. * If you have a stale call site that uses `from: AztecAddress.ZERO` plus a no-op fee payment method as a workaround, replace it with a real `from` (or `NO_FROM`) and drop the no-op fee contract. simulate-view-without-signing ``` // Reading a private view function would normally route through the account // contract's entrypoint, whose is_valid check would prompt the user to sign. // With EmbeddedWallet, .simulate() runs kernelless with a stub-account // override applied to alice's account, so no signing prompt is triggered. const { result: decimals } = await tokenContract.methods .private_get_decimals() .simulate({ from: aliceAddress }); console.log("Token decimals (read via private view):", decimals); ``` > [Source code: docs/examples/ts/aztecjs\_kernelless\_simulation/index.ts#L44-L54](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_kernelless_simulation/index.ts#L44-L54) If you genuinely need to construct your own `SimulationOverrides` (for example, to combine a contract-instance swap with a `fastForwardContractUpdate` for upgrade testing), you can pass them through `.simulate()`: ``` import { SimulationOverrides } from "@aztec/aztec.js/wallet"; const { result } = await contract.methods .transfer_in_private(sender, recipient, amount, nonce) .simulate({ from: sender, overrides: new SimulationOverrides({ /* contracts and/or publicStorage */ }), }); ``` The override map itself has to be built by code that knows the contract class id and live contract instance. That is normally the wallet, not the app. Note that `overrides` does not apply to [utility functions](/developers/testnet/docs/foundational-topics/pxe/kernelless_simulations.md#where-kernelless-does-not-apply), those are simulated through `wallet.executeUtility`, which rejects `SimulationOverrides`. If your wallet does not handle the override path for you and you are tempted to reimplement it in app code, read the next section instead. ### Implementing the override in a custom wallet[​](#implementing-the-override-in-a-custom-wallet "Direct link to Implementing the override in a custom wallet") `EmbeddedWallet` (`yarn-project/wallets/src/embedded/embedded_wallet.ts`, in `@aztec/wallets`) is the canonical implementation of the override pattern and the reference any custom wallet should follow. The three pieces it wires up are: 1. **Register the stub contract class with the PXE at wallet startup.** Inside `initStubClasses`, `EmbeddedWallet` calls `pxe.registerContractClass` for each supported account type's stub artifact and caches the resulting class id by account type. 2. **Build an override map for every account in scope.** Inside `buildAccountOverrides`, it fetches the live contract instance for each scoped address and returns a `ContractOverrides` map that copies the instance with `currentContractClassId` rewritten to the stub class id. The map covers every account in scope, not only `from`. 3. **Use the stub entrypoint and pass the override to `pxe.simulateTx`.** Inside the overridden `simulateViaEntrypoint`, it constructs the `TxExecutionRequest` through the stub account's `DefaultAccountEntrypoint` (so the request is signed by the stub's empty-signature provider) and calls `pxe.simulateTx` with the resulting `SimulationOverrides`. The key constraints on this path: * `skipKernels` must be `true` to use `contracts` overrides. The PXE rejects the combination otherwise. `pxe.simulateTx` already defaults `skipKernels` to `true`. * The stub contract class must be registered with the PXE before you reference it in an override. * The override map must cover every scoped account, not only `from`. ## Collecting authwit requests[​](#collecting-authwit-requests "Direct link to Collecting authwit requests") A simulation with the stub override active will reach `#[authorize_once]` call sites in app and token contracts without prompting for signatures. Each such site emits a `CallAuthorizationRequest` as an offchain effect, which the wallet can collect and turn into a real authentication witness for the eventual `.send()`. The example below uses the canonical contract-mediated pattern: Alice calls `Crowdfunding.donate(amount)`, which internally calls `transfer_in_private(alice, crowdfunding, amount, 0)` with `msg_sender = crowdfunding`. The token's `#[authorize_once]` macro requires an authwit from Alice authorizing the crowdfunding contract. Because Alice is the transaction sender, her PXE already has her notes and nullifier key, so the simulation can run end-to-end against her own state without any cross-wallet state sharing. Run the simulation and filter the offchain effects by the `CallAuthorizationRequest` selector: simulate-and-collect-effects ``` // Alice calls Crowdfunding.donate(amount). Internally the contract calls // transfer_in_private(alice, crowdfunding, amount, 0) with msg_sender = // crowdfunding, so the token's #[authorize_once] macro requires an authwit // from Alice authorizing the crowdfunding contract. Alice is the sender, so // her PXE has her notes and nullifier key — no cross-wallet state sharing // is required. // With kernelless + stub override (default in EmbeddedWallet), the simulation // runs without prompting Alice to sign; the macro emits a // CallAuthorizationRequest as an offchain effect that the wallet can turn // into a real authwit before .send(). const donationAmount = 100n; const donateAction = crowdfundingContract.methods.donate(donationAmount); const { offchainEffects } = await donateAction.simulate({ from: aliceAddress, includeMetadata: true, }); // Filter offchain effects for authwit requests by selector. const authwitSelector = await CallAuthorizationRequest.getSelector(); const authwitEffects = offchainEffects.filter( (effect) => effect.data.length > 0 && effect.data[0].equals(authwitSelector.toField()), ); if (authwitEffects.length !== 1) { throw new Error( `Expected exactly one CallAuthorizationRequest, got ${authwitEffects.length}`, ); } ``` > [Source code: docs/examples/ts/aztecjs\_kernelless\_simulation/index.ts#L82-L114](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_kernelless_simulation/index.ts#L82-L114) Decode each effect into a `CallAuthorizationRequest`. The `innerHash` field is the piece the authorizing account needs to sign: decode-call-authorization ``` // Decode each effect into a CallAuthorizationRequest. The inner hash is the // piece the authorizing account (Alice) needs to sign. const authorizationRequests = await Promise.all( authwitEffects.map((effect) => CallAuthorizationRequest.fromFields(effect.data), ), ); for (const request of authorizationRequests) { console.log("Authwit needed:", { onBehalfOf: request.onBehalfOf.toString(), msgSender: request.msgSender.toString(), functionSelector: request.functionSelector.toString(), }); } if (!authorizationRequests[0].onBehalfOf.equals(aliceAddress)) { throw new Error( `Expected onBehalfOf to be alice (${aliceAddress.toString()}), got ${authorizationRequests[0].onBehalfOf.toString()}`, ); } ``` > [Source code: docs/examples/ts/aztecjs\_kernelless\_simulation/index.ts#L116-L138](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_kernelless_simulation/index.ts#L116-L138) Build a real authentication witness from each inner hash and send the transaction with the collected witnesses attached: build-authwits-and-send ``` // Alice creates a real authentication witness from each inner hash. The // `consumer` is the contract that consumes the authwit — here, the token, // because that's where the inner transfer_in_private (and its #[authorize_once] // site) runs. const authWitnesses = await Promise.all( authorizationRequests.map((request) => wallet.createAuthWit(request.onBehalfOf, { consumer: tokenContract.address, innerHash: request.innerHash, }), ), ); // Alice now sends the real donate transaction with the collected authwits // attached. // Note: EmbeddedWallet.sendTx already runs this pre-simulation + authwit // collection internally, so passing `authWitnesses` here is redundant for // EmbeddedWallet. We pass them explicitly anyway because this is the pattern // a wallet that does not auto-collect needs to follow. await donateAction.send({ from: aliceAddress, authWitnesses, }); ``` > [Source code: docs/examples/ts/aztecjs\_kernelless\_simulation/index.ts#L140-L165](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_kernelless_simulation/index.ts#L140-L165) The app does not need to know which calls require authwits ahead of time. The simulation discovers them; the wallet signs them at send time. `EmbeddedWallet.sendTx` runs this same simulate-then-collect flow internally before delegating to `BaseWallet.sendTx`, so an app that uses `EmbeddedWallet` does not need to call `.simulate()` manually and pass `authWitnesses` to `.send()`. The explicit pattern above is the one a wallet that does not auto-collect must implement, either inside its `sendTx` (as `EmbeddedWallet` does) or inside the app call site. ## Things to watch out for[​](#things-to-watch-out-for "Direct link to Things to watch out for") * **`AztecAddress.ZERO` is not "no sender".** Use `NO_FROM` (from `@aztec/aztec.js/account`) for calls that genuinely have no account context, and a real account address otherwise. * **A private FPC needs to be included in fee options for accurate gas.** Kernelless simulation matches full simulation on gas, but only if the simulation sees the same fee path as the real transaction. If the user will pay through a fee payment contract (FPC) that emits private notes, pass that FPC in the simulation's fee options so its side effects are accounted for. Kernelless plus a private FPC is the supported path; you do not need a full simulation to get accurate gas. * **`profile()` is not kernelless.** If you call `.profile()` to count circuit gates, the kernels run regardless. Use `.simulate()` if you only need return values, offchain effects, or gas estimates. * **Utility functions reject overrides.** `FunctionType.UTILITY` calls go through `wallet.executeUtility`, and `ContractFunctionInteraction.simulate` throws `overrides are not supported for utility function simulation` if you pass non-empty `overrides.publicStorage` or `overrides.contracts`. Utility functions do not need an override anyway, since they do not run through an account contract. ## Related[​](#related "Direct link to Related") * [Kernelless simulations](/developers/testnet/docs/foundational-topics/pxe/kernelless_simulations.md) for the conceptual model. * [Reading contract data](/developers/testnet/docs/aztec-js/how_to_read_data.md) for the basic `.simulate()` API. * [Authentication witnesses](/developers/testnet/docs/foundational-topics/advanced/authwit.md) for what `CallAuthorizationRequest` represents. --- # Testing Smart Contracts This guide covers how to test Aztec smart contracts by connecting to a local network, deploying contracts, and verifying their behavior. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * A running [local Aztec network](/developers/testnet/getting_started_on_local_network.md) * A compiled contract artifact (see [How to compile a contract](/developers/testnet/docs/aztec-nr/compiling_contracts.md)) * Node.js test framework (Jest, Vitest, or similar) ## Setting up the test environment[​](#setting-up-the-test-environment "Direct link to Setting up the test environment") Connect to your local Aztec network and create an embedded wallet: connect\_to\_network ``` import { createAztecNodeClient, waitForNode } from "@aztec/aztec.js/node"; import { EmbeddedWallet } from "@aztec/wallets/embedded"; import { getInitialTestAccountsData } from "@aztec/accounts/testing"; const nodeUrl = process.env.AZTEC_NODE_URL ?? "http://localhost:8080"; const node = createAztecNodeClient(nodeUrl); // Wait for the network to be ready await waitForNode(node); // Create an EmbeddedWallet connected to the node const wallet = await EmbeddedWallet.create(node, { ephemeral: true }); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L1-L14](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_connection/index.ts#L1-L14) The `EmbeddedWallet` manages accounts, tracks deployed contracts, and handles transaction proving. It connects to the Aztec node which provides access to both the Private eXecution Environment (PXE) and the network. ## Loading test accounts[​](#loading-test-accounts "Direct link to Loading test accounts") The local network comes with pre-funded accounts. Load them into your wallet: load\_test\_accounts ``` import { registerInitialLocalNetworkAccountsInWallet } from "@aztec/wallets/testing"; // wallet is the EmbeddedWallet from the setup section above const [alice, bob] = await registerInitialLocalNetworkAccountsInWallet(wallet); ``` > [Source code: docs/examples/ts/aztecjs\_testing/index.ts#L117-L122](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_testing/index.ts#L117-L122) ## Deploying contracts in tests[​](#deploying-contracts-in-tests "Direct link to Deploying contracts in tests") Deploy contracts using the generated contract class: deploy\_test\_contract ``` // wallet is from the setup section; alice is from registerInitialLocalNetworkAccountsInWallet const { contract: testToken } = await TokenContract.deploy( wallet, alice, // admin "TestToken", "TST", 18, ).send({ from: alice }); ``` > [Source code: docs/examples/ts/aztecjs\_testing/index.ts#L124-L133](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_testing/index.ts#L124-L133) ## Verifying contract state[​](#verifying-contract-state "Direct link to Verifying contract state") Use `.simulate()` to read contract state without creating a transaction: simulate\_function ``` const { result: balance } = await token.methods .balance_of_public(aliceAddress) .simulate({ from: aliceAddress }); console.log(`Alice's token balance: ${balance}`); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L148-L154](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_connection/index.ts#L148-L154) Simulations are free (no gas cost) and return the function's result directly. Use them for: * Checking balances and state before/after transactions * Validating expected outcomes in assertions * Debugging contract behavior ## Sending test transactions[​](#sending-test-transactions "Direct link to Sending test transactions") Send transactions and wait for confirmation: send\_transaction ``` const { receipt } = await token.methods .mint_to_public(aliceAddress, 1000n) .send({ from: aliceAddress }); console.log(`Transaction mined in block ${receipt.blockNumber}`); console.log(`Transaction fee: ${receipt.transactionFee}`); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L139-L146](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_connection/index.ts#L139-L146) The `send()` method returns when the transaction is included in a block. ## Example test structure[​](#example-test-structure "Direct link to Example test structure") Here's a complete test example showing the typical structure with setup, test cases, and assertions: complete\_test\_example ``` import { createAztecNodeClient, waitForNode } from "@aztec/aztec.js/node"; import { EmbeddedWallet } from "@aztec/wallets/embedded"; import { getInitialTestAccountsData } from "@aztec/accounts/testing"; import { TokenContract } from "@aztec/noir-contracts.js/Token"; import { AztecAddress } from "@aztec/aztec.js/addresses"; // This file demonstrates a complete Jest test structure. // In a real test file, wrap this in describe() and it() blocks. // Test setup variables let wallet: EmbeddedWallet; let aliceAddress: AztecAddress; let bobAddress: AztecAddress; let token: TokenContract; // beforeAll equivalent - setup async function setup() { const node = createAztecNodeClient( process.env.AZTEC_NODE_URL ?? "http://localhost:8080", ); await waitForNode(node); wallet = await EmbeddedWallet.create(node, { ephemeral: true }); const testAccounts = await getInitialTestAccountsData(); [aliceAddress, bobAddress] = await Promise.all( testAccounts.slice(0, 2).map(async (account) => { return ( await wallet.createSchnorrInitializerlessAccount( account.secret, account.salt, account.signingKey, ) ).address; }), ); ({ contract: token } = await TokenContract.deploy( wallet, aliceAddress, "Test", "TST", 18, ).send({ from: aliceAddress, })); } // Test: mints tokens to an account async function testMintTokens() { await token.methods .mint_to_public(aliceAddress, 1000n) .send({ from: aliceAddress }); const { result: balance } = await token.methods .balance_of_public(aliceAddress) .simulate({ from: aliceAddress }); if (balance !== 1000n) { throw new Error(`Expected balance 1000n, got ${balance}`); } console.log("✓ Mint tokens test passed"); } // Test: transfers tokens between accounts async function testTransferTokens() { // First mint some tokens await token.methods .mint_to_public(aliceAddress, 1000n) .send({ from: aliceAddress }); // Transfer to bob using public transfer await token.methods .transfer_in_public(aliceAddress, bobAddress, 100n, 0n) .send({ from: aliceAddress }); const { result: aliceBalance } = await token.methods .balance_of_public(aliceAddress) .simulate({ from: aliceAddress }); const { result: bobBalance } = await token.methods .balance_of_public(bobAddress) .simulate({ from: bobAddress }); // Note: balances accumulate from previous test console.log(`Alice balance: ${aliceBalance}, Bob balance: ${bobBalance}`); console.log("✓ Transfer tokens test passed"); } // Test: reverts when transferring more than balance async function testRevertOnOverTransfer() { const { result: balance } = await token.methods .balance_of_public(aliceAddress) .simulate({ from: aliceAddress }); try { await token.methods .transfer_in_public(aliceAddress, bobAddress, balance + 1n, 0n) .simulate({ from: aliceAddress }); throw new Error("Expected simulation to throw"); } catch (error) { // Expected to throw console.log("✓ Revert on over-transfer test passed"); } } // Run all tests async function runTests() { await setup(); await testMintTokens(); await testTransferTokens(); await testRevertOnOverTransfer(); console.log("\n✓ All tests passed"); } await runTests(); ``` > [Source code: docs/examples/ts/aztecjs\_testing/index.ts#L1-L115](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_testing/index.ts#L1-L115) ## Testing failure cases[​](#testing-failure-cases "Direct link to Testing failure cases") Test that invalid operations revert as expected: test\_revert\_case ``` async function testRevertExample() { // testToken and alice are from the deploy/load sections above const { result: balance } = await testToken.methods .balance_of_public(alice) .simulate({ from: alice }); let reverted = false; try { await testToken.methods .transfer_in_public(alice, bob, balance + 1n, 0n) .simulate({ from: alice }); } catch (error) { reverted = true; } if (!reverted) { throw new Error("Expected simulation to revert for over-transfer"); } console.log("✓ Revert on over-transfer test passed"); } await testRevertExample(); ``` > [Source code: docs/examples/ts/aztecjs\_testing/index.ts#L135-L158](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_testing/index.ts#L135-L158) Use `.simulate()` to test reverts without spending gas. The simulation will throw if the transaction would fail onchain. ## Simulating with overrides[​](#simulating-with-overrides "Direct link to Simulating with overrides") `.simulate()` accepts an `overrides` option that injects values into the simulator's (ephemeral) world-state fork and contract DB before the call runs. The override is scoped to that single simulation and thrown away afterwards. Override a public-storage slot: ``` const result = await contract.methods.read_balance(account).simulate({ overrides: { publicStorage: [{ contract: contract.address, slot: BALANCE_SLOT, value: new Fr(1_000_000n) }], }, }); ``` Use this to set up state preconditions, reproduce production bugs against pinned storage, or exercise rare value branches without orchestrating the contract calls that produce them. ### Fast-forwarding a contract update[​](#fast-forwarding-a-contract-update "Direct link to Fast-forwarding a contract update") `fastForwardContractUpdate` returns a `SimulationOverrides` object that simulates a deployed instance as if it had already been upgraded to a new contract class. The new class must already be registered on chain. The cheat mirrors a real `pxe.updateContract` followed by waiting out the upgrade delay: the instance's `currentContractClassId` is bumped, and the `ContractInstanceRegistry`'s delayed-public-mutable storage is rewritten to look like the upgrade was scheduled in the past. ``` import { fastForwardContractUpdate } from '@aztec/aztec.js'; const overrides = await fastForwardContractUpdate({ instanceAddress: contract.address, newClassId: upgradedClass.id, node, }); const result = await contract.methods.upgraded_method().simulate({ overrides }); ``` Use this to test code paths that only execute after an upgrade, without orchestrating the full delayed-mutable upgrade flow. ## Further reading[​](#further-reading "Direct link to Further reading") * [How to read contract data](/developers/testnet/docs/aztec-js/how_to_read_data.md) * [How to send transactions](/developers/testnet/docs/aztec-js/how_to_send_transaction.md) * [How to deploy a contract](/developers/testnet/docs/aztec-js/how_to_deploy_contract.md) * [How to create an account](/developers/testnet/docs/aztec-js/how_to_create_account.md) * [How to compile a contract](/developers/testnet/docs/aztec-nr/compiling_contracts.md) --- # Using Authentication Witnesses This guide shows you how to create and use authentication witnesses (authwits) to authorize other accounts to perform actions on your behalf. Automatic private authwits with EmbeddedWallet When using `EmbeddedWallet`, **private authwits are created automatically**. The wallet simulates your transaction before sending and detects which private authwits are needed, then generates them on the fly. You don't need to create them manually. Public authwits still need to be set explicitly, as they require a separate onchain transaction before use. The manual approach described below is also relevant if you're building a custom wallet implementation. aztec-nr Using AuthWitnesses is always a two-part process. This guide shows how to generate and use them, but you still need to set up your contract to accept and authenticate them. Therefore it is recommended to read the `aztec-nr` [guide on authwitnesses](/developers/testnet/docs/aztec-nr/framework-description/authentication_witnesses.md) before this one. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * [Connected to a network](how_to_connect_to_local_network) with an `EmbeddedWallet` instance and funded accounts * Contract with authwit validation (see [smart contract authwits](/developers/testnet/docs/aztec-nr/framework-description/authentication_witnesses.md)) * Understanding of [authwit concepts](/developers/testnet/docs/foundational-topics/advanced/authwit.md) ## Intent types[​](#intent-types "Direct link to Intent types") The authwit system supports different intent types depending on your use case: * **`CallIntent`**: Use when authorizing a specific contract function call. Contains `{ caller, call }` where `call` is a `FunctionCall`, typically obtained with `await interaction.getFunctionCall()`. * **`ContractFunctionInteractionCallIntent`**: Convenience form that takes the interaction directly. Contains `{ caller, action }` where `action` is a `ContractFunctionInteraction`; internally resolved to a `FunctionCall` before signing. * **`IntentInnerHash`**: Use when authorizing arbitrary data. Contains `{ consumer, innerHash }` where `consumer` is the contract that will verify the authwit. ## Create private authwits[​](#create-private-authwits "Direct link to Create private authwits") note If you're using `EmbeddedWallet`, this section is handled for you automatically. See the tip above. Private authwits authorize actions in the private domain. The authorization is included directly in the transaction that uses it. Let's say Alice wants to allow Bob to transfer tokens from her account. Alice is the **authorizer** (she owns the tokens) and Bob is the **caller** (he will execute the transfer): private\_authwit ``` // Alice wants to allow Bob to transfer tokens from her account (private) const privateNonce = Fr.random(); // Define the action Bob will execute const privateAction = tokenContract.methods.transfer_in_private( aliceAddress, // from bobAddress, // to 100n, // amount privateNonce, // authwit nonce for replay protection ); // Alice creates an authwit authorizing Bob to call this function const privateWitness = await wallet.createAuthWit(aliceAddress, { caller: bobAddress, call: await privateAction.getFunctionCall(), }); // Bob executes the transfer, providing the authwit // additionalScopes lets the PXE access Alice's private state // during authwit verification await privateAction.send({ from: bobAddress, authWitnesses: [privateWitness], additionalScopes: [aliceAddress], }); ``` > [Source code: docs/examples/ts/aztecjs\_authwit/index.ts#L45-L71](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_authwit/index.ts#L45-L71) tip The nonce prevents replay attacks. When `from` and `msg_sender` are the same (self-transfer), set the nonce to `0`. ## Create public authwits[​](#create-public-authwits "Direct link to Create public authwits") Public authwits require a transaction to store the authorization in the `AuthRegistry` contract before the authorized action can be executed: public\_authwit ``` // Alice wants to allow Bob to transfer tokens from her account (public) const publicNonce = Fr.random(); // Define the action Bob will execute const publicAction = tokenContract.methods.transfer_in_public( aliceAddress, // from bobAddress, // to 100n, // amount publicNonce, // authwit nonce ); // Alice sets the public authwit (this requires a transaction) const authwit = await SetPublicAuthwitContractInteraction.create( wallet, aliceAddress, { caller: bobAddress, action: publicAction }, true, // authorized ); await authwit.send(); // Now Bob can execute the transfer await publicAction.send({ from: bobAddress }); ``` > [Source code: docs/examples/ts/aztecjs\_authwit/index.ts#L73-L96](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_authwit/index.ts#L73-L96) ## Create arbitrary message authwits[​](#create-arbitrary-message-authwits "Direct link to Create arbitrary message authwits") Use this when authorizing arbitrary data rather than a specific contract function call: arbitrary\_authwit ``` import { computeInnerAuthWitHash } from "@aztec/aztec.js/authorization"; // Create hash of arbitrary data const innerHash = await computeInnerAuthWitHash([ Fr.fromHexString("0xcafe"), Fr.fromHexString("0xbeef"), ]); // Create an intent with the consumer contract address const intent = { consumer: tokenContract.address, innerHash, }; // Create the authwit for arbitrary data const arbitraryWitness = await wallet.createAuthWit(aliceAddress, intent); console.log("Arbitrary authwit created:", arbitraryWitness); ``` > [Source code: docs/examples/ts/aztecjs\_authwit/index.ts#L98-L116](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_authwit/index.ts#L98-L116) The `consumer` is the contract address that will verify this authwit. ## Revoke public authwits[​](#revoke-public-authwits "Direct link to Revoke public authwits") Public authwits can be revoked by setting `authorized` to `false`: revoke\_authwit ``` // Revoke a public authwit by setting authorized to false const revokeNonce = Fr.random(); const revokeAction = tokenContract.methods.transfer_in_public( aliceAddress, bobAddress, 50n, revokeNonce, ); // First, set the authwit const setAuthwit = await SetPublicAuthwitContractInteraction.create( wallet, aliceAddress, { caller: bobAddress, action: revokeAction }, true, ); await setAuthwit.send(); // Later, revoke it const revokeInteraction = await SetPublicAuthwitContractInteraction.create( wallet, aliceAddress, { caller: bobAddress, action: revokeAction }, false, // revoke authorization ); await revokeInteraction.send(); ``` > [Source code: docs/examples/ts/aztecjs\_authwit/index.ts#L118-L145](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_authwit/index.ts#L118-L145) ## Next steps[​](#next-steps "Direct link to Next steps") * Learn about [authwits in smart contracts](/developers/testnet/docs/aztec-nr/framework-description/authentication_witnesses.md) * Understand [authwit concepts](/developers/testnet/docs/foundational-topics/advanced/authwit.md) * Explore [account abstraction](/developers/testnet/docs/foundational-topics/accounts.md) --- # Pay Fees Privately This guide explains how private fee payment works on Aztec and walks through a concrete example. A fully private FPC can pay transaction fees without revealing the payer: it has no public functions, no owner, and no offchain agent. Because the contract is fully private, **no onchain deployment transaction is required**. Every app just derives the address deterministically from the class hash and a shared salt, and users interact with it privately. To illustrate the pattern, this guide uses [`PrivateFPC`](https://github.com/defi-wonderland/aztec-fee-payment), a community-built implementation by [Wonderland](https://github.com/defi-wonderland). You could write your own private FPC following the same design principles. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * [Connected to a network](how_to_connect_to_local_network) with an `EmbeddedWallet` instance and funded accounts * Familiarity with [fee concepts](/developers/testnet/docs/foundational-topics/fees.md) and [Paying Fees](/developers/testnet/docs/aztec-js/how_to_pay_fees.md) info The fee asset is only transferrable within a block to the current sequencer, as it powers the fee abstraction mechanism on Aztec. The asset is not transferable beyond this to ensure credible neutrality between all third party developer made asset portals and to ensure local compliance rules can be followed. ## Why a fully private FPC?[​](#why-a-fully-private-fpc "Direct link to Why a fully private FPC?") On Aztec, the transaction's setup phase is non-revertible, and a protocol-level allowlist controls which public function calls are permitted during it. Public token functions (like `transfer_in_public` and `_increase_public_balance`) have been removed from the default allowlist; custom FPCs may only call protocol-contract setup functions like those on `AuthRegistry` and `FeeJuice`. The reference [`FPC` contract](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/fees/fpc_contract/src/main.nr) collects user payment during setup by calling those token functions, so its flow is now rejected on public networks. The `PrivateFeePaymentMethod` shipped in `@aztec/aztec.js/fee` (which targets that contract) is therefore deprecated. See the [migration note](/developers/testnet/docs/resources/migration_notes.md#custom-token-fpcs-removed-from-default-public-setup-allowlist) for details. A fully private FPC side-steps the allowlist entirely. Instead of collecting payment from the user during setup, it holds Fee Juice as its own internal private balance (funded earlier by the user through the Fee Juice portal). When a transaction runs, the FPC just verifies that a **Fee Juice claim nullifier** exists in the nullifier tree (proof that the L1 deposit was consumed on L2) and deducts from its private note-based balance. No public cross-contract token calls happen during setup, so the allowlist never blocks anything. ## How a private FPC works[​](#how-a-private-fpc-works "Direct link to How a private FPC works") This section describes the design pattern using Wonderland's `PrivateFPC` as an example. The contract stores an internal, note-based `BalanceSet` of Fee Juice per user. There is no constructor, no admin, and no public surface. ### Two salts, not one[​](#two-salts-not-one "Direct link to Two salts, not one") Two different salt values show up in this flow; it's worth naming them up front so they don't get confused: * **Deployment salt.** Used to derive the FPC's contract address. Once a community agrees on the bytecode and this salt, everyone can derive the same address locally without an onchain deployment tx. The convention for Wonderland's `PrivateFPC` is `Fr.ZERO` (see [Recommended salt](#recommended-salt-0)). * **Bridge salt.** A random value the user chooses per L1 deposit. Combined with the user's Aztec address, it derives the *bridge secret* (`secret = poseidon2([salt, claimer], DOM_SEP__FPC_BRIDGE_SECRET)`), whose hash is passed as the `secretHash` on the L1 deposit. Only the user knows the preimage, so only the user can later produce the `secret` that `FeeJuice.claim` requires to consume the L1-to-L2 message. `PrivateFPC.mint(amount, salt, leaf_index)` and `PrivateFPC.mint_and_pay_fee(amount, salt, leaf_index)` take the **bridge** salt (along with the leaf index and the user's claimer address, which is `msg_sender`) to reconstruct the Fee Juice claim nullifier and verify the bridge was consumed. ### Two flows[​](#two-flows "Direct link to Two flows") 1. **Bridge + mint + pay** (run once to seed the user's private Fee Juice balance inside the FPC, and run again each time that balance runs low and the user wants to add more by bridging another deposit from L1): 1. **L1 deposit.** Call `FeeJuicePortal.depositToAztecPublic(_to = fpcAddress, _amount = amount, _secretHash = computeSecretHash(bridgeSecret))` where `bridgeSecret = poseidon2([bridgeSalt, claimer], DOM_SEP__FPC_BRIDGE_SECRET)`. The FPC is the *recipient* of the deposit, the user is the *claimer*. 2. **L2 claim.** In a normal L2 transaction, call `FeeJuice.claim(fpcAddress, amount, bridgeSecret, leafIndex)` directly. This consumes the L1-to-L2 message, credits Fee Juice to the FPC's **public** Fee Juice balance, and emits the claim nullifier. The fee for this transaction is paid by whatever mechanism the user normally uses (their own Fee Juice, `FeeJuicePaymentMethodWithClaim` on a *separate* bridge they control, the Sponsored FPC on devnet/testnet, and so on). The `PrivateFPC` does *not* sponsor this call, because at this point the user has no balance with it yet. 3. **Mint.** In a follow-up L2 transaction, call `PrivateFPC.mint(amount, bridgeSalt, leafIndex)` (again paid by whatever mechanism the user normally uses). `mint` does **not** call `FeeJuice.claim` again, because the claim already happened in step 1.2. The contract recomputes the same nullifier value that the earlier `claim` emitted (possible because the user supplies the `bridgeSalt` that originally produced it), asserts that nullifier exists in the nullifier tree as proof the L1 deposit was consumed, emits its own FPC-scoped nullifier to prevent double-minting the same bridge credit, and credits `amount` to the claimer's private balance inside the FPC. 4. **Pay.** From that point on, the user can pass `new FPCFeePaymentMethod(fpcAddress)` as the payment method on any transaction. Under the hood, the method calls `PrivateFPC.pay_fee()` in setup, which deducts `max_gas_cost` from the user's private balance and makes the FPC the fee payer. 2. **Cold-start** (single-transaction equivalent of steps 2–4 above, for first-time users who have only done the L1 deposit): 1. **L1 deposit.** Same as step 1.1 above. 2. **Single L2 transaction.** Pass `new PrivateMintAndPayFeePaymentMethod(fpcAddress, amount, bridgeSecret, bridgeSalt, leafIndex)` as the payment method on the user's first real transaction. The SDK bundles two calls into the setup phase of that single transaction: * `FeeJuice.claim(fpcAddress, amount, bridgeSecret, leafIndex)`: consumes the L1-to-L2 message, crediting Fee Juice to the FPC and emitting the claim nullifier (pending within the same tx). * `PrivateFPC.mint_and_pay_fee(amount, bridgeSalt, leafIndex)`: asserts the (pending) claim nullifier, credits `amount - max_gas_cost` to the user's private balance in the FPC, and marks the FPC as fee payer. The bridged amount itself funds this transaction's fee, so the user doesn't need prior Fee Juice or a sponsor to bootstrap. Any remaining credit (`amount - max_gas_cost`) is available for subsequent transactions via `FPCFeePaymentMethod`. Cold-start exists for users who have no other way to pay fees: the bridged amount itself funds that very first transaction, but `max_gas_cost` of it is consumed in the process. For top-ups (when the user already has another fee mechanism), the three-step `claim → mint → pay` path is preferable because it credits the full `amount` rather than `amount - max_gas_cost`, and it decouples the L1 bridge from the first app transaction (useful for privacy). For protocol details and the full API surface, see the [SDK README](https://github.com/defi-wonderland/aztec-fee-payment/blob/dev/src/ts/README.md) and [PRD](https://github.com/defi-wonderland/aztec-fee-payment/blob/dev/docs/private-product-requirements.md). Because neither `pay_fee` nor `mint_and_pay_fee` makes public cross-contract token calls in setup (they only deduct from the FPC's internal private balance and invoke `set_as_fee_payer`), the [setup-phase allowlist](/developers/testnet/docs/foundational-topics/transactions.md#setup-phase-non-revertible) never blocks these flows. No refund `PrivateFPC.pay_fee()` deducts the full `max_gas_cost` and does not refund unused gas. Read `gasUsed` from a simulation (see [Estimate mana costs](/developers/testnet/docs/aztec-js/how_to_pay_fees.md#estimate-mana-costs)) to right-size your limits. ## Share one FPC address across the ecosystem[​](#share-one-fpc-address-across-the-ecosystem "Direct link to Share one FPC address across the ecosystem") Privacy on Aztec comes from indistinguishability. Private calldata and user identities are hidden. What an observer sees of a private call is its onchain *footprint*: the number of nullifiers and note commitments it emits, any logs, and its public gas usage. They do not learn which contract or which function produced those. Any two transactions whose footprints match are indistinguishable, even if they originated from entirely different contracts or functions, so an anonymity set at the private layer can span many unrelated contract–function pairs. Fee payments add one extra observable: the transaction's fee payer address, set via `set_as_fee_payer()`, is recorded onchain by the protocol. Every fee paid through a given FPC address is therefore publicly tagged with that address. If your app uses its own copy of the private FPC at a unique address, that tag distinguishes your users' fee payments from everyone else's. If every app derives the *same* FPC address and routes fees through it, every private fee payment in the ecosystem shares the same public fee-payer tag and joins a single, much larger shared set. This is the whole point of a fully private FPC. Because you don't have to deploy it on L2, there is no race to "be the deployer": the only thing that matters is that everyone agrees on the address. ## Recommended salt: `0`[​](#recommended-salt-0 "Direct link to recommended-salt-0") Two parties derive the same contract address if and only if they use the same compiled artifact and the same deployment salt. For any fully private FPC, using a common salt maximizes the shared privacy set. The community convention for Wonderland's `PrivateFPC` is `Fr.ZERO`. This is a convention, not a protocol-enforced default. It is up to each developer to pass the salt when registering the contract with their PXE, just as they choose any other deployment parameter. Following the convention means your users' private fee payments join the same privacy set as every other app that follows it. Version-specific addresses The `PrivateFPC` address depends on the compiled contract bytecode. A different Aztec version produces different bytecode and therefore a **different address**. Sending Fee Juice to the wrong address means **unrecoverable loss**. Before using a derived address on a given network, verify the network runs the same Aztec version as the Wonderland SDK version you have installed. ## Example: pay fees with Wonderland's `PrivateFPC`[​](#example-pay-fees-with-wonderlands-privatefpc "Direct link to example-pay-fees-with-wonderlands-privatefpc") The SDK exports two payment methods plus a `registerPrivateContract` helper that registers the FPC with your PXE using the shared deployment salt, with no deployment transaction needed: * `new FPCFeePaymentMethod(fpcAddress)`: for users who already have a private balance in the FPC. Wraps `PrivateFPC.pay_fee()`. * `new PrivateMintAndPayFeePaymentMethod(fpcAddress, amount, bridgeSecret, bridgeSalt, leafIndex)`: for cold-start. Bundles `FeeJuice.claim` and `PrivateFPC.mint_and_pay_fee` into the setup phase of a single transaction. For installation, the complete bridge-claim-mint-pay flow, required `send()` options (including `additionalScopes` and `gasSettings`), and a runnable end-to-end example, see the [SDK README](https://github.com/defi-wonderland/aztec-fee-payment/blob/dev/src/ts/README.md) and the [integration test](https://github.com/defi-wonderland/aztec-fee-payment/blob/dev/src/ts/test/private.test.ts). Transaction behavior | Scenario | Status | Execution result | Fee paid? | | -------------- | --------------------------------- | ---------------- | -------------- | | Private revert | `DROPPED` (not included in block) | N/A | No | | Public revert | `PROPOSED` | `REVERTED` | Yes (FPC pays) | | Success | `PROPOSED` | `SUCCESS` | Yes (FPC pays) | ## Reference implementation[​](#reference-implementation "Direct link to Reference implementation") Wonderland's repository ships detailed documentation for this design and its security properties: * [Private FPC Product Requirements](https://github.com/defi-wonderland/aztec-fee-payment/blob/dev/docs/private-product-requirements.md): problem statement, requirements matrix, cryptographic design (secret derivation, nullifier reconstruction, double-spend prevention), and security properties * [`PrivateFPC` Noir source](https://github.com/defi-wonderland/aztec-fee-payment/blob/dev/src/nr/private_contract/src/main.nr): the contract itself, annotated with the full bridge-to-mint-to-pay flow * [`src/ts/README.md`](https://github.com/defi-wonderland/aztec-fee-payment/blob/dev/src/ts/README.md): SDK reference with every exported class and utility * [Integration test `private.test.ts`](https://github.com/defi-wonderland/aztec-fee-payment/blob/dev/src/ts/test/private.test.ts): canonical end-to-end example of the bridge, claim, mint, sponsor flow ## Next steps[​](#next-steps "Direct link to Next steps") * Learn about [fee concepts](/developers/testnet/docs/foundational-topics/fees.md) in detail * Review the other [fee payment methods](/developers/testnet/docs/aztec-js/how_to_pay_fees.md) available in `aztec.js` * Browse Wonderland's [`aztec-fee-payment`](https://github.com/defi-wonderland/aztec-fee-payment) repository for the Noir source, TypeScript SDK, and integration examples --- # TypeScript API Reference This section provides API reference documentation for the Aztec TypeScript packages. These packages enable developers to build applications on Aztec, from simple contract interactions to complex privacy-preserving protocols. ## Package Categories[​](#package-categories "Direct link to Package Categories") ### Client SDKs[​](#client-sdks "Direct link to Client SDKs") Packages for building Aztec applications: | Package | Description | | ---------------------- | --------------------------------------------------------------------------------------------------------------------- | | **@aztec/aztec.js** | Main SDK for building Aztec applications. Provides contract deployment, transaction creation, and account management. | | **@aztec/accounts** | Sample account contract implementations including ECDSA and Schnorr accounts. | | **@aztec/pxe** | Private eXecution Environment client library for orchestrating private transaction execution and proving. | | **@aztec/wallet-sdk** | Wallet SDK for browser and extension integrations. | | **@aztec/wallets** | Embedded wallet for browser and Node.js environments. | | **@aztec/entrypoints** | Transaction entrypoint implementations for account abstraction. | ### Core Libraries[​](#core-libraries "Direct link to Core Libraries") Foundational types and utilities used across the Aztec stack: | Package | Description | | --------------------- | -------------------------------------------------------------------------------------- | | **@aztec/stdlib** | Protocol-level types including transactions, blocks, proofs, and kernel circuit types. | | **@aztec/foundation** | Low-level utilities including crypto primitives, serialization, and async helpers. | | **@aztec/constants** | Protocol constants shared between TypeScript and Noir circuits. | note Common types like `Fr`, `AztecAddress`, and `EthAddress` are re-exported through `@aztec/aztec.js` subpaths (e.g., `@aztec/aztec.js/fields`, `@aztec/aztec.js/addresses`). Most developers won't need to import from `@aztec/stdlib` directly. ## LLM-Optimized Documentation[​](#llm-optimized-documentation "Direct link to LLM-Optimized Documentation") For LLM consumption, we provide machine-readable documentation in multiple formats: * **[llms.txt](/llms.txt)** - Full documentation optimized for LLM context * [**LLM Summary**](/typescript-api/testnet/llm-summary.txt) - Human-readable API summary ### Markdown API Files[​](#markdown-api-files "Direct link to Markdown API Files") The following markdown files are available for LLM context inclusion at `/typescript-api/testnet/`: | File | Description | | ------------------------------------------------------------- | ----------------------------------------------- | | [`llm-summary.txt`](/typescript-api/testnet/llm-summary.txt) | Human-readable summary with package overview | | [`aztec.js.md`](/typescript-api/testnet/aztec.js.md) | Main SDK - contracts, transactions, accounts | | [`accounts.md`](/typescript-api/testnet/accounts.md) | Account implementations (ECDSA, Schnorr) | | [`pxe.md`](/typescript-api/testnet/pxe.md) | Private execution environment client | | [`wallet-sdk.md`](/typescript-api/testnet/wallet-sdk.md) | Browser/extension wallet integration | | [`wallets.md`](/typescript-api/testnet/wallets.md) | Embedded wallet for browser and Node.js | | [`entrypoints.md`](/typescript-api/testnet/entrypoints.md) | Transaction entrypoints for account abstraction | | [`stdlib.md`](/typescript-api/testnet/stdlib.md) | Protocol types (transactions, blocks, proofs) | | [`foundation.md`](/typescript-api/testnet/foundation.md) | Low-level utilities (crypto, serialization) | | [`constants.md`](/typescript-api/testnet/constants.md) | Protocol constants for circuits | ## Related Resources[​](#related-resources "Direct link to Related Resources") * [Aztec.js Getting Started](/developers/testnet/docs/tutorials/js_tutorials/aztecjs-getting-started.md) * [GitHub: aztec-packages](https://github.com/AztecProtocol/aztec-packages/tree/v5.0.0-rc.2/yarn-project) --- # Overview Aztec.nr is a Noir framework used to develop and test Aztec smart contracts. It contains both high-level abstractions (state variables, messages) and low-level protocol primitives, providing granular control to developers if they want custom contracts. tip If you are already familiar with writing Aztec smart contracts and Aztec.nr, visit the [API reference](/aztec-nr-api/testnet/). ## Motivation[​](#motivation "Direct link to Motivation") Noir *can* be used to write circuits, but Aztec contracts are more complex than this. They include multiple external functions, each of a different type: circuits for private functions, AVM bytecode for public functions, and brillig bytecode for utility functions. The circuits for private functions also need to interact with the protocol's kernel circuits in specific ways, so manually writing them, and then combining everything into a contract artifact is involved work. Aztec.nr takes care of all of this heavy lifting and makes writing contracts as simple as marking functions with the corresponding attributes e.g. `#[external("private")]`. It allows safe and easy implementation of well understood design patterns, such as the multiple kinds of private state variables, meaning developers don't need to understand the low-levels of how the protocol works. These features are optional, however, advanced developers are not prevented from building their own custom solutions. ## Design principles[​](#design-principles "Direct link to Design principles") * Make it hard to shoot yourself in the foot by making it clear when something is unsafe. * Dangerous actions should be easy to spot. e.g. ignoring return values or calling functions with the `_unsafe` prefix. * This is achieved by having rails that intentionally trigger a developer's "WTF?" response, to ensure they understand what they're doing. A good example of this is writing to private state variables. These functions return a `NoteMessage` struct, which results in a compiler error unless used. This is because writing to private state also requires sending an encrypted message with the new state to the people that need to access it - otherwise, because it is private, they will not even know the state changed. ``` storage.votes.insert(new_vote); // compiler error - unused NoteMessage return value storage.votes.insert(new_vote).deliver(MessageDelivery::onchain_constrained()); // deliver the note message onchain ``` ## Contract Development[​](#contract-development "Direct link to Contract Development") ### Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Install [Aztec Local Network and Tooling](/developers/testnet/getting_started_on_local_network.md) * Install the [Noir VSCode Extension](/developers/testnet/docs/aztec-nr/installation.md) for syntax highlighting and error detection. ### Flow[​](#flow "Direct link to Flow") 1. Write your contract and specify your contract dependencies. Create a new project with `aztec new my_project`, which scaffolds a workspace with two crates: a `my_project_contract` crate for your contract and a `my_project_test` crate for tests, with the `aztec` dependency already configured. If you need additional dependencies, add them to `my_project_contract/Nargo.toml`: ``` # my_project_contract/Nargo.toml [dependencies] aztec = { git="https://github.com/AztecProtocol/aztec-nr/", tag="v5.0.0-rc.2", directory="aztec" } ``` Update your `my_project_contract/src/main.nr` contract file to use the Aztec.nr macros for writing contracts. setup ``` use aztec::macros::aztec; #[aztec] pub contract Counter { ``` > [Source code: docs/examples/contracts/counter\_contract/src/main.nr#L1-L6](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/counter_contract/src/main.nr#L1-L6) and import dependencies from the Aztec.nr library. imports ``` use aztec::{ macros::{functions::{external, initializer}, storage::storage}, messages::delivery::MessageDelivery, oracle::logging::debug_log_format, protocol::{address::AztecAddress, traits::ToField}, state_vars::Owned, }; use balance_set::BalanceSet; ``` > [Source code: docs/examples/contracts/counter\_contract/src/main.nr#L7-L16](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/counter_contract/src/main.nr#L7-L16) info You can see a complete example of a simple counter contract written with Aztec.nr [here](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/counter_contract/src/main.nr). 2. [Profile](/developers/testnet/docs/aztec-nr/framework-description/advanced/how_to_profile_transactions.md) the private functions in your contract to get a sense of how long generating client side proofs will take 3. Write unit tests [directly in Noir](/developers/testnet/docs/aztec-nr/testing_contracts.md) and end-to-end tests [with TypeScript](/developers/testnet/docs/aztec-js/how_to_test.md) 4. [Compile](/developers/testnet/docs/aztec-nr/compiling_contracts.md) your contract 5. [Deploy](/developers/testnet/docs/aztec-js/how_to_deploy_contract.md) your contract with Aztec.js ## Section Contents[​](#section-contents "Direct link to Section Contents") ## [📄️Noir VSCode Extension](/developers/testnet/docs/aztec-nr/installation.md) [Learn how to install and configure the Noir Language Server for a better development experience.](/developers/testnet/docs/aztec-nr/installation.md) ## [📄️Compiling Contracts](/developers/testnet/docs/aztec-nr/compiling_contracts.md) [Compile your Aztec smart contracts into deployable artifacts using aztec command.](/developers/testnet/docs/aztec-nr/compiling_contracts.md) ## [📄️Contract Deployment Reference](/developers/testnet/docs/aztec-nr/contract_readiness_states.md) [A practical guide to determine which deployment steps your Aztec contract needs and when functions become callable.](/developers/testnet/docs/aztec-nr/contract_readiness_states.md) ## [🗃Framework Description](/developers/testnet/docs/aztec-nr/framework-description/functions.md) [17 items](/developers/testnet/docs/aztec-nr/framework-description/functions.md) ## [📄️Logging from Contracts](/developers/testnet/docs/aztec-nr/logging.md) [Add log statements to your Aztec contracts and control log verbosity in tests and local networks.](/developers/testnet/docs/aztec-nr/logging.md) ## [📄️Debugging Aztec Code](/developers/testnet/docs/aztec-nr/debugging.md) [This guide shows you how to debug issues in your Aztec contracts.](/developers/testnet/docs/aztec-nr/debugging.md) ## [📄️Testing Contracts](/developers/testnet/docs/aztec-nr/testing_contracts.md) [Write and run tests for your Aztec smart contracts using Noir's TestEnvironment.](/developers/testnet/docs/aztec-nr/testing_contracts.md) ## [🗃Standards](/developers/testnet/docs/aztec-nr/standards.md) [6 items](/developers/testnet/docs/aztec-nr/standards.md) ## [📄️Aztec.nr API Reference](/developers/testnet/docs/aztec-nr/api.md) [Auto-generated API reference documentation for the Aztec.nr smart contract framework.](/developers/testnet/docs/aztec-nr/api.md) --- # Aztec.nr API Reference The Aztec.nr API reference documentation is auto-generated from the source code using `nargo doc`. ## View the API Documentation[​](#view-the-api-documentation "Direct link to View the API Documentation") [**Aztec.nr**](/aztec-nr-api/testnet/noir_aztec/index.html) The API reference includes documentation for all public modules, functions, structs, and types in the aztec-nr workspace: ### Core Crates[​](#core-crates "Direct link to Core Crates") * [**noir\_aztec**](/aztec-nr-api/testnet/noir_aztec/index.html) - Core Aztec contract framework including: * [`context`](/aztec-nr-api/testnet/noir_aztec/context/index.html) - Private and public execution contexts * [`state_vars`](/aztec-nr-api/testnet/noir_aztec/state_vars/index.html) - State variable types (PrivateMutable, PublicMutable, Map, etc.) * [`note`](/aztec-nr-api/testnet/noir_aztec/note/index.html) - Note interfaces and utilities * [`authwit`](/aztec-nr-api/testnet/noir_aztec/authwit/index.html) - Authentication witness support * [`history`](/aztec-nr-api/testnet/noir_aztec/history/index.html) - Historical state proofs * [`messages`](/aztec-nr-api/testnet/noir_aztec/messages/index.html) - Cross-chain messaging * [`oracle`](/aztec-nr-api/testnet/noir_aztec/oracle/index.html) - Oracle interfaces * [`macros`](/aztec-nr-api/testnet/noir_aztec/macros/index.html) - Contract macros and attributes * [`hash`](/aztec-nr-api/testnet/noir_aztec/hash/index.html) - Hash functions and utilities * [`keys`](/aztec-nr-api/testnet/noir_aztec/keys/index.html) - Key management utilities * [`event`](/aztec-nr-api/testnet/noir_aztec/event/index.html) - Event emission and interfaces * [`test`](/aztec-nr-api/testnet/noir_aztec/test/index.html) - Testing utilities * [`utils`](/aztec-nr-api/testnet/noir_aztec/utils/index.html) - General utilities ### Note Types[​](#note-types "Direct link to Note Types") * [**address\_note**](/aztec-nr-api/testnet/address_note/index.html) - Note type for storing Aztec addresses * [**field\_note**](/aztec-nr-api/testnet/field_note/index.html) - Note type for storing a single Field value * [**uint\_note**](/aztec-nr-api/testnet/uint_note/index.html) - Note type for storing unsigned integers ### State Variables[​](#state-variables "Direct link to State Variables") * [**balance\_set**](/aztec-nr-api/testnet/balance_set/index.html) - State variable for managing private balances ### Utilities[​](#utilities "Direct link to Utilities") * [**compressed\_string**](/aztec-nr-api/testnet/compressed_string/index.html) - Compressed string utilities for efficient storage --- # Compiling Contracts This guide shows you how to compile your Aztec contracts into artifacts ready for deployment and interaction. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * An Aztec contract written in Aztec.nr * `aztec` installed * Contract project with proper `Nargo.toml` configuration ## Compile your contract[​](#compile-your-contract "Direct link to Compile your contract") Compile your Noir contracts to generate JSON artifacts: ``` aztec compile ``` This outputs contract artifacts to the `target` folder. ## Use generated interfaces[​](#use-generated-interfaces "Direct link to Use generated interfaces") The compiler automatically generates type-safe interfaces for contract interaction. ### Import and use contract interfaces[​](#import-and-use-contract-interfaces "Direct link to Import and use contract interfaces") Use generated interfaces instead of manual function calls: ``` contract MyContract { use token::Token; #[external("private")] fn transfer_tokens(token_address: AztecAddress, recipient: AztecAddress, amount: u128) { // Use the generated Token interface to call another contract self.call(Token::at(token_address).transfer(recipient, amount)); } #[external("private")] fn transfer_then_mint(token_address: AztecAddress, recipient: AztecAddress, amount: u128) { // Private call executed immediately self.call(Token::at(token_address).transfer(recipient, amount)); // Public call enqueued for later execution self.enqueue(Token::at(token_address).mint_to_public(recipient, amount)); } } ``` warning Do not import generated interfaces from the same project as the source contract to avoid circular references. ## Next steps[​](#next-steps "Direct link to Next steps") After compilation, use the generated artifacts to: * Deploy contracts with the `Contract` class from `aztec.js` * Interact with deployed contracts using type-safe interfaces * Import contracts in other Aztec.nr projects --- # Contract Deployment Reference This guide helps you quickly determine which deployment steps your contract needs. For conceptual background on how contract deployment works, see [Contract Deployment](/developers/testnet/docs/foundational-topics/contract_creation.md). ## What Do I Need to Do?[​](#what-do-i-need-to-do "Direct link to What Do I Need to Do?") Use this decision tree to determine which steps your contract needs. No initializer? If your contract has no `#[initializer]` function and was deployed with `without_initializer()`, it's considered initialized immediately. Skip the initialization checks above. ## Checking Contract State Programmatically[​](#checking-contract-state-programmatically "Direct link to Checking Contract State Programmatically") Use `wallet.getContractMetadata(contractAddress)` to check whether a contract is registered, published, and initialized. See [Verify deployment](/developers/testnet/docs/aztec-js/how_to_deploy_contract.md#verify-deployment) for usage examples and details on what the PXE checks automatically versus what you need to verify manually. ## When Can You Skip States?[​](#when-can-you-skip-states "Direct link to When Can You Skip States?") | Contract Type | Class Registration | Instance Creation | Initialization | Public Deployment | | ------------------------- | ------------------ | ----------------- | -------------- | ----------------- | | Private-only | Optional | Required | Depends | Skip | | Public-only | Required | Required | Depends | Required | | Hybrid (private + public) | Required | Required | Depends | Required | | Stateless helper | Optional | Required | Skip | Depends | "Depends" means it depends on whether your contract has a constructor marked with `#[initializer]`. ## When Functions Become Callable[​](#when-functions-become-callable "Direct link to When Functions Become Callable") | State | Private Functions | Public Functions | | ----------------------------------- | --------------------- | ---------------- | | Address computed only | With `#[noinitcheck]` | No | | Class registered | With `#[noinitcheck]` | No | | Instance deployed (not initialized) | With `#[noinitcheck]` | No | | Initialized | Yes | No | | Publicly deployed | Yes | Yes | Private functions marked with `#[noinitcheck]` can be called as soon as you know the address, even before initialization. This enables patterns like pre-funded accounts. Contracts without initializers If your contract has no initializer and is deployed with `without_initializer()`, it's considered initialized immediately. Private functions are callable right after instance creation without needing `#[noinitcheck]`. Public functions still require public deployment. ## Further Reading[​](#further-reading "Direct link to Further Reading") * [Contract Deployment](/developers/testnet/docs/foundational-topics/contract_creation.md) - Conceptual foundation of classes, instances, and lifecycle states * [Deploying Contracts](/developers/testnet/docs/aztec-js/how_to_deploy_contract.md) - TypeScript deployment guide * [Defining Initializer Functions](/developers/testnet/docs/aztec-nr/framework-description/functions/how_to_define_functions.md#define-initializer-functions) - How to use `#[initializer]` and `#[noinitcheck]` * [Communicating Cross-Chain](/developers/testnet/docs/aztec-nr/framework-description/ethereum_aztec_messaging.md) - Portal contracts and L1/L2 messaging --- # Debugging Aztec Code This guide shows you how to debug issues in your Aztec development environment. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Running Aztec local network * Aztec.nr contract or aztec.js application * Basic understanding of Aztec architecture ## Enable logging[​](#enable-logging "Direct link to Enable logging") For adding log statements to your contracts, controlling log verbosity, and understanding the `LOG_LEVEL` syntax, see the [Logging from Contracts](/developers/testnet/docs/aztec-nr/logging.md) guide. To enable verbose system-level logging on a local network: ``` LOG_LEVEL=verbose aztec start --local-network ``` ## Debugging common errors[​](#debugging-common-errors "Direct link to Debugging common errors") ### Contract Errors[​](#contract-errors "Direct link to Contract Errors") | Error | Solution | | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Aztec dependency not found` | Add to Nargo.toml: `aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v5.0.0-rc.2", directory="noir-projects/aztec-nr/aztec" }` | | `Public state writes only supported in public functions` | Move state writes to public functions | | `Unknown contract 0x0` | Call `wallet.registerContract(...)` to register contract | | `No public key registered for address` | Call `wallet.registerSender(...)` | | `Direct invocation of ... functions is not supported` | Use `self.call()`, `self.view()`, or `self.enqueue()` to [call contract functions](/developers/testnet/docs/aztec-nr/framework-description/calling_contracts.md) | | `Failed to solve brillig function` | Check function parameters and note validity | | `Cross-contract utility call denied` | Configure an `authorizeUtilityCall` [execution hook](#cross-contract-utility-call-denied) on your PXE | #### Cross-contract utility call denied[​](#cross-contract-utility-call-denied "Direct link to Cross-contract utility call denied") Utility functions execute on the user's device and have access to private state. A cross-contract utility call made by a malicious or compromised contract could leak private information to an untrusted contract. PXE therefore denies cross- contract utility calls by default and requires explicit authorization via an execution hook. Calls to standard contracts (such as the HandshakeRegistry, which is queried during every contract's sync) are always automatically authorized. When a contract executes a utility function that calls into a different contract, PXE asks the wallet through an [execution hook](/developers/testnet/docs/foundational-topics/pxe/execution_hooks.md) whether the call should be allowed. If no hook is configured, or the wallet denies the request, you will see: ``` Cross-contract utility call denied: . attempted to call : (). ``` See [execution hooks](/developers/testnet/docs/foundational-topics/pxe/execution_hooks.md#authorizeutilitycall) for how to authorize calls, both in production and in Noir tests. ### Circuit Errors[​](#circuit-errors "Direct link to Circuit Errors") | Error Code | Meaning | Fix | | ----------- | ---------------------------- | -------------------------------------------------- | | `2002` | Invalid contract address | Ensure contract is deployed and address is correct | | `2005/2006` | Static call violations | Remove state modifications from static calls | | `2017` | User intent mismatch | Verify transaction parameters match function call | | `3001` | Unsupported operation | Check if operation is supported in current context | | `3005` | Non-empty private call stack | Ensure private functions complete before public | | `4007/4008` | Chain ID/version mismatch | Verify L1 chain ID and Aztec version | | `7008` | Membership check failed | Ensure using valid historical state | | `7009` | Array overflow | Reduce number of operations in transaction | ### Quick Fixes for Common Issues[​](#quick-fixes-for-common-issues "Direct link to Quick Fixes for Common Issues") ``` # Archiver sync issues - force progress with dummy transactions. # Assumes you have imported the local network test accounts # (aztec-wallet import-test-accounts) and have a deployed token # aliased as `testtoken`. aztec-wallet send transfer --from test0 --contract-address testtoken --args accounts:test0 0 aztec-wallet send transfer --from test0 --contract-address testtoken --args accounts:test0 0 # L1 to L2 message pending - wait for inclusion # Messages need 2 blocks to be processed ``` ## Debugging WASM errors[​](#debugging-wasm-errors "Direct link to Debugging WASM errors") ### Enable debug WASM[​](#enable-debug-wasm "Direct link to Enable debug WASM") ``` // In vite.config.ts or similar export default { define: { "process.env.BB_WASM_PATH": JSON.stringify("https://debug.wasm.url"), }, }; ``` ### Profile transactions[​](#profile-transactions "Direct link to Profile transactions") ``` import { serializePrivateExecutionSteps } from "@aztec/stdlib"; // Profile the transaction const profileTx = await contract.methods .myMethod(param1, param2) .profile({ profileMode: "execution-steps" }); // Serialize for debugging const ivcMessagePack = serializePrivateExecutionSteps(profileTx.executionSteps); // Download debug file const blob = new Blob([ivcMessagePack]); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = "debug-steps.msgpack"; link.click(); ``` ⚠️ **Warning:** Debug files may contain private data. Use only in development. ## Interpret error messages[​](#interpret-error-messages "Direct link to Interpret error messages") ### Circuit and protocol errors[​](#circuit-and-protocol-errors "Direct link to Circuit and protocol errors") * **Private kernel errors (2xxx)**: Issues with private function execution * **Public kernel errors (3xxx)**: Issues with public function execution * **Rollup errors (4xxx)**: Block production issues * **Generic errors (7xxx)**: Resource limits or state validation ### Transaction limits[​](#transaction-limits "Direct link to Transaction limits") Current limits that trigger `7009 - ARRAY_OVERFLOW`: * Max new notes per tx: Check `MAX_NOTE_HASHES_PER_TX` * Max nullifiers per tx: Check `MAX_NULLIFIERS_PER_TX` * Max function calls: Check call stack size limits * Max L2→L1 messages: Check message limits ## Debugging sequencer issues[​](#debugging-sequencer-issues "Direct link to Debugging sequencer issues") ### Common sequencer errors[​](#common-sequencer-errors "Direct link to Common sequencer errors") | Error | Cause | Solution | | ------------------------------------ | --------------------- | ------------------------------------------------ | | `tree root mismatch` | State inconsistency | Restart local network or check state transitions | | `next available leaf index mismatch` | Tree corruption | Verify tree updates are sequential | | `Public call stack size exceeded` | Too many public calls | Reduce public function calls | | `Failed to publish block` | L1 submission failed | Check L1 connection and gas | ## Reporting issues[​](#reporting-issues "Direct link to Reporting issues") When debugging fails: 1. Collect error messages and codes 2. Generate transaction profile (if applicable) 3. Note your environment setup 4. Create issue at [aztec-packages](https://github.com/AztecProtocol/aztec-packages/issues/new) ## Quick reference[​](#quick-reference "Direct link to Quick reference") ### Enable verbose logging[​](#enable-verbose-logging "Direct link to Enable verbose logging") ``` LOG_LEVEL=verbose aztec start --local-network ``` ### Contract logging[​](#contract-logging "Direct link to Contract logging") See the full [Logging from Contracts](/developers/testnet/docs/aztec-nr/logging.md) guide for all available log functions and `LOG_LEVEL` configuration. ``` use aztec::oracle::logging::{debug_log, debug_log_format}; ``` ### Check contract registration[​](#check-contract-registration "Direct link to Check contract registration") ``` await wallet.getContractMetadata(myContractInstance.address); ``` ### Decode L1 errors[​](#decode-l1-errors "Direct link to Decode L1 errors") Check hex errors against [Errors.sol](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/l1-contracts/src/core/libraries/Errors.sol) ## Tips[​](#tips "Direct link to Tips") * Always check logs before diving into circuit errors * State-related errors often indicate timing issues * Array overflow errors mean you hit transaction limits * Use debug WASM for detailed stack traces * Profile transactions when errors are unclear ## Next steps[​](#next-steps "Direct link to Next steps") * [Circuit Architecture](/developers/testnet/docs/foundational-topics/advanced/circuits.md) * [Call Types](/developers/testnet/docs/foundational-topics/call_types.md) * [Aztec.nr Dependencies](/developers/testnet/docs/aztec-nr/framework-description/dependencies.md) --- # Profiling Transactions This guide shows you how to profile Aztec transactions to understand gate counts and identify optimization opportunities. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * `aztec` command installed ([see installation](/developers/testnet/getting_started_on_local_network.md)) * Aztec contract compiled (`aztec compile`) * Basic understanding of proving and gate counts ## Choosing a profiling tool[​](#choosing-a-profiling-tool "Direct link to Choosing a profiling tool") Aztec provides three ways to profile. Each serves a different purpose: | Tool | What it measures | Needs deployment? | When to use | | ------------------------------------------------- | --------------------------------------------------------- | ----------------- | -------------------------------------------------------- | | `aztec profile gates` | Per-function gate counts | No | Quick check of individual function costs after compiling | | `aztec profile flamegraph` | Per-function flamegraph SVG | No | Deep-dive into where gates come from inside a function | | `aztec-wallet profile` / `.profile()` in aztec.js | Full transaction gate count including all kernel circuits | Yes\* | Understanding the true cost of a transaction end-to-end | \* `aztec-wallet profile` and `ContractFunctionInteraction.profile()` require a deployed contract. However, `DeployMethod.profile()` in aztec.js can profile deployment transactions before the contract exists. In most cases, start with `aztec profile gates` for a quick overview, then use the full transaction profiling tools when you need to understand kernel overhead. ## Quick profiling with `aztec profile`[​](#quick-profiling-with-aztec-profile "Direct link to quick-profiling-with-aztec-profile") These commands work on compiled artifacts directly — no deployment or running network required. ### Gate counts[​](#gate-counts "Direct link to Gate counts") ``` # Compile your contract aztec compile # Get gate counts for all functions aztec profile gates ./target ``` Example output: ``` Gate counts: ──────────────────────────────────────────────────────────────────── my_contract-MyContract::constructor 5,200 my_contract-MyContract::my_function 14,832 my_contract-MyContract::transfer 31,559 ──────────────────────────────────────────────────────────────────── Total: 3 circuit(s) ``` These are the gate counts for your contract functions alone, **without** kernel circuit overhead. See [Understanding kernel overhead](#understanding-kernel-overhead) for how this translates to total transaction cost. BB binary `aztec profile` needs the Barretenberg (`bb`) backend binary. It is auto-detected from the `@aztec/bb.js` package. If auto-detection fails, set the `BB` environment variable: ``` BB=/path/to/bb aztec profile gates ./target ``` Machine-readable output For build automation, use `--json` to emit gate counts as a JSON array. Each entry has `name`, `type` (`contract-function` or `program`), and `gates`: ``` aztec profile gates --json ./target ``` ### Flamegraphs[​](#flamegraphs "Direct link to Flamegraphs") To generate an interactive flamegraph SVG for a specific function: ``` aztec profile flamegraph ./target/my_contract-MyContract.json my_function ``` This outputs a file like `my_contract-MyContract-my_function-flamegraph.svg` in the same directory. Open it in a browser for an interactive view where: * **Width** represents gate count * **Height** represents call stack depth * **Wide sections** indicate optimization targets tip If `noir-profiler` is not on your PATH, set the `PROFILER_PATH` environment variable: ``` PROFILER_PATH=/path/to/noir-profiler aztec profile flamegraph ./target/my_contract-MyContract.json my_function ``` ## Full transaction profiling[​](#full-transaction-profiling "Direct link to Full transaction profiling") The tools above measure individual function gate counts. To understand the **total** proving cost of a transaction — including account entrypoints and kernel circuits — use `aztec-wallet profile` or `.profile()` in aztec.js. These require a running network and, in most cases, a deployed contract. The exception is `DeployMethod.profile()` in aztec.js, which can profile deployment transactions before the contract exists. ### Profile with aztec-wallet[​](#profile-with-aztec-wallet "Direct link to Profile with aztec-wallet") Use the `profile` command instead of `send` to get detailed gate counts: ``` # Import test accounts aztec-wallet import-test-accounts # Deploy your contract aztec-wallet deploy MyContractArtifact \ --from accounts:test0 \ --args [CONSTRUCTOR_ARGS] \ -a mycontract # Profile a function call aztec-wallet profile my_function \ -ca mycontract \ --args [FUNCTION_ARGS] \ -f accounts:test0 ``` #### Reading the output[​](#reading-the-output "Direct link to Reading the output") The profile command outputs a per-circuit breakdown: ``` Per circuit breakdown: Function name Time Gates Subtotal -------------------------------------------------------------------------------- - SchnorrAccount:entrypoint 12.34ms 21,724 21,724 - private_kernel_init 23.45ms 45,351 67,075 - MyContract:my_function 15.67ms 31,559 98,634 - private_kernel_inner 34.56ms 78,452 177,086 Total gates: 177,086 (Biggest circuit: private_kernel_inner -> 78,452) ``` Key metrics: * **Gates**: Circuit complexity for each step * **Subtotal**: Accumulated gate count * **Time**: Execution time per circuit Notice that the kernel circuits (`private_kernel_init`, `private_kernel_inner`) appear alongside your contract functions. These are protocol overhead — see [Understanding kernel overhead](#understanding-kernel-overhead). ### Profile with aztec.js[​](#profile-with-aztecjs "Direct link to Profile with aztec.js") ``` const result = await contract.methods.my_function(args).profile({ from: walletAddress, profileMode: "full", skipProofGeneration: true, }); // Access gate counts from execution steps for (const step of result.executionSteps) { console.log(`${step.functionName}: ${step.gateCount} gates`); } // Access timing information console.log("Total time:", result.stats.timings.total, "ms"); ``` #### Profile modes[​](#profile-modes "Direct link to Profile modes") * `gates`: Gate counts per circuit * `execution-steps`: Detailed execution trace with bytecode and witnesses * `full`: Complete profiling information (gates + execution steps) Set `skipProofGeneration: true` for faster iteration when you only need gate counts. ## Generate flamegraphs with noir-profiler[​](#generate-flamegraphs-with-noir-profiler "Direct link to Generate flamegraphs with noir-profiler") For deeper analysis of individual contract functions beyond what `aztec profile flamegraph` provides, you can use the Noir profiler directly. The profiler is installed automatically with Nargo (starting noirup v0.1.4). ``` # Compile your contract first aztec compile # Generate a gates flamegraph (requires bb backend) noir-profiler gates \ --artifact-path ./target/my_contract-MyContract.json \ --backend-path bb \ --output ./target # Generate an ACIR opcodes flamegraph noir-profiler opcodes \ --artifact-path ./target/my_contract-MyContract.json \ --output ./target ``` For detailed usage, see the [Noir profiler documentation](https://noir-lang.org/docs/tooling/profiler). ## Understanding kernel overhead[​](#understanding-kernel-overhead "Direct link to Understanding kernel overhead") When you profile a full transaction, you'll see kernel circuits alongside your contract functions. These are protocol overhead — the private kernel runs once per private function call in the transaction. Even a typical transaction calling a single contract function involves two private calls (the account entrypoint + your function), totaling \~427k gates of which only \~14k are your function. For a detailed breakdown of kernel phases and their gate costs, see [Private Kernel Circuit - Performance Impact](/developers/testnet/docs/foundational-topics/advanced/circuits/private_kernel.md#performance-impact). ## Gate count guidelines[​](#gate-count-guidelines "Direct link to Gate count guidelines") These are rough guidelines for a **single contract function's** gate count (i.e. what `aztec profile gates` reports). A typical transaction (e.g. a token transfer) totals \~500,000 gates across all circuits including kernel overhead, so use that as a reference point. | Gate Count | Assessment | | ----------------- | ---------------------------- | | < 50,000 | Excellent | | 50,000 - 200,000 | Good | | 200,000 - 500,000 | Consider optimizing | | > 500,000 | Worth optimizing if possible | Note that a high gate count does **not** prevent transaction inclusion — it only affects client-side proving time. See [Private Kernel Circuit - Performance Impact](/developers/testnet/docs/foundational-topics/advanced/circuits/private_kernel.md#performance-impact) for details. ## Next steps[​](#next-steps "Direct link to Next steps") * [Writing efficient contracts](/developers/testnet/docs/aztec-nr/framework-description/advanced/writing_efficient_contracts.md) - optimization strategies and examples * [Transaction lifecycle](/developers/testnet/docs/foundational-topics/transactions.md) * [Testing contracts](/developers/testnet/docs/aztec-nr/testing_contracts.md) --- # Proving historic state This guide shows you how to prove facts about Aztec's historical state from inside a private function: that a note existed, that a nullifier was or wasn't present, that a contract was deployed, or what a public storage slot held at a past block. Each proof is a Merkle membership (or non-membership) proof against a state tree root committed in a past block header: the note hash tree, the nullifier tree, or the public data tree. (The Archive tree, by contrast, holds a hash of each block header and is what proves a header is canonical.) Private functions always read from a past block header rather than the live chain tip, because the chain advances while a proof is generated on your device. The proofs therefore tell you what was true once every transaction in the chosen block had executed. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * An Aztec contract project set up * Understanding of Aztec's note and nullifier system ## What you can prove[​](#what-you-can-prove "Direct link to What you can prove") You can create proofs for these elements at any past block height: * **Note inclusion** - prove a note existed in the note hash tree * **Note validity** - prove a note existed and wasn't nullified at a specific block * **Nullifier inclusion/non-inclusion** - prove a nullifier was or wasn't in the nullifier tree * **Contract deployment** - prove a contract's bytecode was published or initialized * **Public storage** - read the value a public storage slot held at a past block Common use cases: * Verify ownership of an asset from another contract without revealing which specific note * Prove eligibility based on historical state (e.g., "owned tokens at block X") * Claim rewards based on past contributions (see the [claim contract](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/claim_contract/src/main.nr) for a complete example) ## Choosing a block header[​](#choosing-a-block-header "Direct link to Choosing a block header") Every function below takes a `BlockHeader`. Two getters on the private context provide one: * `self.context.get_anchor_block_header()` returns the transaction's anchor block header. The protocol's circuits verify it once per transaction, so using it adds no extra constraints to your function. The protocol requires a transaction to expire within 24 hours of its anchor block, so the anchor is always a block from the last 24 hours. * `self.context.get_block_header_at(block_number)` returns the header of any block at or before the anchor block, letting you prove against older state at the cost of extra constraints (see [Prove at a specific historical block](#prove-at-a-specific-historical-block)). Data availability Producing these proofs requires the historical state trees (note hashes, nullifiers, public storage) for the chosen block. Many nodes prune this data after a few hours, so proving against older blocks requires a node that retains it, such as an archive node. ## Prove note inclusion[​](#prove-note-inclusion "Direct link to Prove note inclusion") Import the function: history\_import ``` use aztec::history::note::assert_note_existed_by; ``` > [Source code: noir-projects/noir-contracts/contracts/app/claim\_contract/src/main.nr#L5-L7](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/claim_contract/src/main.nr#L5-L7) Prove a note exists in the note hash tree: prove\_note\_inclusion ``` let header = self.context.get_anchor_block_header(); let confirmed_note = assert_note_existed_by(header, hinted_note); ``` > [Source code: noir-projects/noir-contracts/contracts/app/claim\_contract/src/main.nr#L41-L44](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/claim_contract/src/main.nr#L41-L44) ## Prove note validity[​](#prove-note-validity "Direct link to Prove note validity") To prove a note was valid (existed AND wasn't nullified) at a historical block: ``` use aztec::history::note::assert_note_was_valid_by; let header = self.context.get_anchor_block_header(); assert_note_was_valid_by(header, hinted_note, &mut self.context); ``` This verifies both: 1. The note was included in the note hash tree 2. The note's nullifier was not in the nullifier tree ## Prove at a specific historical block[​](#prove-at-a-specific-historical-block "Direct link to Prove at a specific historical block") To prove against state at a specific past block (not just the anchor block): ``` use aztec::history::note::assert_note_existed_by; let historical_header = self.context.get_block_header_at(block_number); assert_note_existed_by(historical_header, hinted_note); ``` warning Using `get_block_header_at` adds \~3k constraints to prove Archive tree membership. The anchor block header is effectively free since it's verified once per transaction. ## Prove a note was nullified[​](#prove-a-note-was-nullified "Direct link to Prove a note was nullified") To prove a note has been spent/nullified: ``` use aztec::history::note::assert_note_was_nullified_by; let header = self.context.get_anchor_block_header(); assert_note_was_nullified_by(header, confirmed_note, &mut self.context); ``` ## Prove contract bytecode was published[​](#prove-contract-bytecode-was-published "Direct link to Prove contract bytecode was published") To prove a contract's bytecode was published at a historical block: ``` use aztec::history::deployment::assert_contract_bytecode_was_published_by; let header = self.context.get_anchor_block_header(); assert_contract_bytecode_was_published_by(header, contract_address); ``` You can also prove a contract was initialized (constructor was called): ``` use aztec::history::deployment::assert_contract_was_initialized_by; use aztec::oracle::get_contract_instance::get_contract_instance; let header = self.context.get_anchor_block_header(); let instance = get_contract_instance(contract_address); assert_contract_was_initialized_by(header, contract_address, instance.initialization_hash); ``` ## Read historical public storage[​](#read-historical-public-storage "Direct link to Read historical public storage") To read the value a public storage slot held at a past block in a private function, use `public_storage_historical_read`. It returns the stored value and constrains it against the public data tree root in the given block header: ``` use aztec::history::storage::public_storage_historical_read; let header = self.context.get_anchor_block_header(); let value = public_storage_historical_read(header, storage_slot, contract_address); ``` An uninitialized slot reads as `0`. Because this proves the value against a past block header rather than reading the live chain tip, a private function can read public state this way **without enqueuing a public call**. Enqueuing a public call is only needed to read the *current* value or to write to public storage. Higher-level state variables build on this. Reading a `PublicImmutable` or `DelayedPublicMutable` from a private function performs a historical public storage read internally (through `WithHash`), so you rarely need to call `public_storage_historical_read` directly. Both types are designed so that a historical read is also a correct read of the current value: a `PublicImmutable` can only be initialized once, so its value never changes, while a `DelayedPublicMutable` delays every write and sets the transaction's expiration timestamp so the transaction is only valid for as long as the value it read still holds. ## Available history functions[​](#available-history-functions "Direct link to Available history functions") The `aztec::history` module provides these functions: | Function | Module | Purpose | | ----------------------------------------------- | --------------------- | ------------------------------------------------- | | `assert_note_existed_by` | `history::note` | Prove note exists in note hash tree | | `assert_note_was_valid_by` | `history::note` | Prove note exists and is not nullified | | `assert_note_was_nullified_by` | `history::note` | Prove note's nullifier is in nullifier tree | | `assert_note_was_not_nullified_by` | `history::note` | Prove note's nullifier is not in nullifier tree | | `assert_nullifier_existed_by` | `history::nullifier` | Prove a siloed nullifier exists | | `assert_nullifier_did_not_exist_by` | `history::nullifier` | Prove a siloed nullifier does not exist | | `assert_contract_bytecode_was_published_by` | `history::deployment` | Prove a contract's bytecode was published | | `assert_contract_bytecode_was_not_published_by` | `history::deployment` | Prove a contract's bytecode was not published | | `assert_contract_was_initialized_by` | `history::deployment` | Prove a contract was initialized | | `assert_contract_was_not_initialized_by` | `history::deployment` | Prove a contract was not initialized | | `public_storage_historical_read` | `history::storage` | Read a public storage value at a historical block | The nullifier functions take a *siloed* nullifier: the nullifier hashed together with the contract address, which is the value actually stored in the global nullifier tree. Use `compute_siloed_nullifier` to convert an inner nullifier (the value passed to `push_nullifier_unsafe`) into its siloed form. --- # Retrieving and Filtering Notes This guide shows you how to retrieve and filter notes from private storage using `NoteGetterOptions`. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Aztec contract with note storage * Understanding of note structure and properties ## Required imports[​](#required-imports "Direct link to Required imports") ``` use aztec::note::note_getter_options::{NoteGetterOptions, NoteStatus, SortOrder}; use aztec::utils::comparison::Comparator; ``` ## Set up basic note retrieval[​](#set-up-basic-note-retrieval "Direct link to Set up basic note retrieval") ### Step 1: Create default options[​](#step-1-create-default-options "Direct link to Step 1: Create default options") ``` let mut options = NoteGetterOptions::new(); ``` This returns up to `MAX_NOTE_HASH_READ_REQUESTS_PER_CALL` notes without filtering. ### Step 2: Retrieve notes from storage[​](#step-2-retrieve-notes-from-storage "Direct link to Step 2: Retrieve notes from storage") ``` // Returns BoundedVec, ...> let confirmed_notes = storage.my_notes.at(owner).get_notes(options); ``` get\_notes vs pop\_notes * `get_notes`: Retrieves notes without nullifying. Note data is not guaranteed to be current or non-nullified—use when you only need to read note data without consuming it. * `pop_notes`: Retrieves AND nullifies notes in one operation. Use when consuming notes (e.g., spending tokens). More efficient than calling `get_notes` followed by manual nullification. Here's an example of `pop_notes` with filtering from the NFT contract: pop\_notes ``` let notes = nfts.at(from).pop_notes(NoteGetterOptions::new() .select(NFTNote::properties().token_id, Comparator.EQ, token_id) .set_limit(1)); assert(notes.len() == 1, "NFT not found when transferring"); ``` > [Source code: noir-projects/noir-contracts/contracts/app/nft\_contract/src/main.nr#L215-L220](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr#L215-L220) ## Filter notes by properties[​](#filter-notes-by-properties "Direct link to Filter notes by properties") ### Step 1: Select notes with specific field values[​](#step-1-select-notes-with-specific-field-values "Direct link to Step 1: Select notes with specific field values") ``` // Assuming MyNote has an 'owner' field let mut options = NoteGetterOptions::new(); options = options.select( MyNote::properties().owner, Comparator.EQ, owner ); ``` ### Step 2: Apply multiple selection criteria[​](#step-2-apply-multiple-selection-criteria "Direct link to Step 2: Apply multiple selection criteria") ``` let mut options = NoteGetterOptions::new(); options = options .select(MyNote::properties().value, Comparator.EQ, value) .select(MyNote::properties().owner, Comparator.EQ, owner); ``` tip Chain multiple `select` calls to filter by multiple fields. Remember to call `get_notes(options)` after applying all your selection criteria to retrieve the filtered notes. ## Sort retrieved notes[​](#sort-retrieved-notes "Direct link to Sort retrieved notes") ### Sort and paginate results[​](#sort-and-paginate-results "Direct link to Sort and paginate results") ``` let mut options = NoteGetterOptions::new(); options = options .select(MyNote::properties().owner, Comparator.EQ, owner) .sort(MyNote::properties().value, SortOrder.DESC) .set_limit(10) // Max 10 notes .set_offset(20); // Skip first 20 ``` ## Apply custom filters[​](#apply-custom-filters "Direct link to Apply custom filters") Filter Performance Database `select` is more efficient than custom filters. Use custom filters only for complex logic. ### Create and use a custom filter[​](#create-and-use-a-custom-filter "Direct link to Create and use a custom filter") custom\_filter ``` pub fn filter_notes_min_sum( notes: [Option>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL], min_sum: Field, ) -> [Option>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] { let mut selected = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; let mut sum = 0; for i in 0..notes.len() { if notes[i].is_some() & sum.lt(min_sum) { let hinted_note = notes[i].unwrap_unchecked(); selected[i] = Option::some(hinted_note); sum += hinted_note.note.value; } } selected } ``` > [Source code: noir-projects/noir-contracts/contracts/test/pending\_note\_hashes\_contract/src/filter.nr#L4-L22](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/test/pending_note_hashes_contract/src/filter.nr#L4-L22) Then use it with `NoteGetterOptions`: ``` let options = NoteGetterOptions::with_filter(filter_notes_min_sum, min_value); ``` Note Limits Maximum notes per call: `MAX_NOTE_HASH_READ_REQUESTS_PER_CALL` (currently 16) Available Comparators * `Comparator.EQ`: Equal to * `Comparator.NEQ`: Not equal to * `Comparator.LT`: Less than * `Comparator.LTE`: Less than or equal * `Comparator.GT`: Greater than * `Comparator.GTE`: Greater than or equal ## Call from TypeScript[​](#call-from-typescript "Direct link to Call from TypeScript") You can pass comparator values from TypeScript to your contract functions: ``` import { Comparator } from '@aztec/aztec.js/note'; // Pass comparator to a contract function that accepts it as a parameter await contract.methods.read_notes(Comparator.GTE, 5).simulate({ from: senderAddress }); ``` ## View notes without constraints[​](#view-notes-without-constraints "Direct link to View notes without constraints") Use `NoteViewerOptions` in unconstrained utility functions to query notes without generating proofs: view\_notes ``` #[external("utility")] unconstrained fn get_private_nfts(owner: AztecAddress, page_index: u32) -> ([Field; MAX_NOTES_PER_PAGE], bool) { let offset = page_index * MAX_NOTES_PER_PAGE; let options = NoteViewerOptions::new().set_offset(offset); let notes = self.storage.private_nfts.at(owner).view_notes(options); let mut owned_nft_ids = [0; MAX_NOTES_PER_PAGE]; for i in 0..options.limit { if i < notes.len() { owned_nft_ids[i] = notes.get_unchecked(i).token_id; } } let page_limit_reached = notes.len() == options.limit; (owned_nft_ids, page_limit_reached) } ``` > [Source code: noir-projects/noir-contracts/contracts/app/nft\_contract/src/main.nr#L255-L272](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr#L255-L272) Viewer vs Getter * `NoteGetterOptions`: For constrained private functions with proof generation (max 16 notes) * `NoteViewerOptions`: For unconstrained utility functions, no proofs (max 10 notes per page via `MAX_NOTES_PER_PAGE`) ## Query notes with different status[​](#query-notes-with-different-status "Direct link to Query notes with different status") ### Set status to include nullified notes[​](#set-status-to-include-nullified-notes "Direct link to Set status to include nullified notes") ``` let mut options = NoteGetterOptions::new(); options = options.set_status(NoteStatus.ACTIVE_OR_NULLIFIED); ``` Note Status Options * `NoteStatus.ACTIVE`: Only active (non-nullified) notes (default) * `NoteStatus.ACTIVE_OR_NULLIFIED`: Both active and nullified notes ## Next steps[​](#next-steps "Direct link to Next steps") * Learn about [custom note implementations](/developers/testnet/docs/aztec-nr/framework-description/custom_notes.md) * Explore [note discovery mechanisms](/developers/testnet/docs/foundational-topics/advanced/storage/note_discovery.md) * Understand [partial notes](/developers/testnet/docs/aztec-nr/framework-description/advanced/partial_notes.md) --- # Using Capsules Capsules provide per-contract non-volatile storage in the PXE. Data is stored locally (not onchain), scoped per contract address, and persists until explicitly deleted. ## Basic usage[​](#basic-usage "Direct link to Basic usage") ``` use aztec::oracle::capsules; // Capsule operations are unconstrained, so these values are typically // passed in as parameters from the calling context. let contract_address: AztecAddress = /* self.address */; let slot: Field = 1; // scope is an AztecAddress used for capsule isolation, allowing multiple // independent namespaces within the same contract. let scope: AztecAddress = /* e.g. the account address */; // Store data at a slot (overwrites existing data) capsules::store(contract_address, slot, value, scope); // Load data (returns Option) let result: Option = capsules::load(contract_address, slot, scope); // Delete data at a slot capsules::delete(contract_address, slot, scope); // Copy contiguous slots (supports overlapping regions) // copy(contract_address, src_slot, dst_slot, num_entries: u32, scope) capsules::copy(contract_address, src_slot, dst_slot, 3, scope); ``` Types must implement `Serialize` and `Deserialize` traits. warning All capsule operations are `unconstrained`. Data loaded from capsules should be validated in constrained contexts. Contracts can only access their own capsules. ## CapsuleArray[​](#capsulearray "Direct link to CapsuleArray") `CapsuleArray` provides dynamic array storage backed by capsules: ``` use aztec::capsules::CapsuleArray; use aztec::protocol::hash::sha256_to_field; // Use a hash for base_slot to avoid collisions with other storage global BASE_SLOT: Field = sha256_to_field("MY_CONTRACT::MY_ARRAY".as_bytes()); let array: CapsuleArray = CapsuleArray::at(contract_address, BASE_SLOT, scope); array.push(value); // Append to end let value = array.get(index); // Read at index (throws if out of bounds) let length = array.len(); // Get current size (returns u32) array.remove(index); // Delete & shift elements (index is u32) // Iterate and optionally remove elements array.for_each(|index, value| { if some_condition(value) { array.remove(index); // Safe to remove current element only } }); ``` `for_each` Safety It is safe to remove the current element during `for_each`, but **do not push new elements** during iteration. Storage Layout CapsuleArray stores length at the base slot, with elements in consecutive slots (base+1 for index 0, base+2 for index 1, etc.). Ensure sufficient space between different array base slots. --- # Partial notes Token standard Where this page refers to a concrete token, it assumes the [AIP-20 fungible token standard](/developers/testnet/docs/aztec-nr/standards/aip-20.md). The partial-note primitive (`UintNote` / `PartialUintNote`) is token-agnostic; AIP-20 is one standard built on top of it. ## What are partial notes?[​](#what-are-partial-notes "Direct link to What are partial notes?") Partial notes are notes created with incomplete data, usually during private execution, which can be completed with additional information that becomes available later, usually during public execution. Let's say, for example, we have a `UintNote`: uint\_note\_def ``` #[derive(Deserialize, Eq, Serialize, Packable)] #[custom_note] pub struct UintNote { /// The number stored in the note. pub value: u128, } ``` > [Source code: noir-projects/aztec-nr/uint-note/src/uint\_note.nr#L29-L36](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/aztec-nr/uint-note/src/uint_note.nr#L29-L36) The `UintNote` struct itself only contains the `value` field. Additional fields including `owner`, `randomness`, and `storage_slot` are passed as parameters during note hash computation. When creating the note locally during private execution, the `owner` and `storage_slot` are known, but the `value` potentially is not (e.g., it depends on some onchain dynamic variable). First, a **partial note** can be created during private execution that commits to the `owner` and `randomness`, and then the note is *"completed"* to create a full note by later adding the `storage_slot` and `value` fields, usually during public execution. ![](/assets/ideal-img/partial-notes.167e271.640.png) ## Use cases[​](#use-cases "Direct link to Use cases") Partial notes are useful when part of the note struct is a value that depends on dynamic, public onchain data that isn't available during private execution, such as: * AMM swap prices * Current gas prices * Time-dependent interest accrual They are also useful as **payment endpoints**: a recipient can create a partial note ahead of time and share the commitment with prospective senders. Senders later complete the partial note to pay the recipient, with no action needed from the recipient at payment time. See [partial notes as payment endpoints](/developers/testnet/docs/aztec-nr/framework-description/advanced/partial_notes_as_payment_endpoints.md) for the full design. ## The completer[​](#the-completer "Direct link to The completer") A partial note is finalized by a later completion step that supplies the public fields (`storage_slot` and `value`). At that point its private preimage (`owner` and `randomness`) is not re-derived or re-checked, and the partial note itself is just a `Field` that can be copied and shared freely. If anyone holding it could complete it, they could insert a note with an arbitrary, unbacked `value` into the note hash tree, or complete the note at the wrong time or with the wrong values. To prevent this, the creator designates a **completer** at creation time. During the constrained private execution that creates the partial note, the contract records a validity commitment `H(partial_commitment, completer)` in the nullifier tree. Completion recomputes this commitment and asserts it exists, using its presence as proof that a legitimate, constrained execution created the partial note and authorized this specific completer to supply the public values and finalize it. With AIP-20, the completer is chosen explicitly when calling `initialize_transfer_commitment`; completion (`transfer_private_to_commitment` / `transfer_public_to_commitment`) binds the completer to the caller's `msg_sender` and debits a separately authorized `from` account. For `UintNote`, the fields split cleanly across the two phases: | Field | Fixed at | How | | -------------- | ------------------------------- | ---------------------------------------------------------------------------------------------- | | `owner` | Partial note creation (private) | Committed in `partial_commitment = H(owner, randomness)` | | `randomness` | Partial note creation (private) | Same commitment; fresh per note, blinds the owner | | `completer` | Partial note creation (private) | Bound in the validity commitment `H(partial_commitment, completer)`; not part of the note hash | | `storage_slot` | Completion (public or private) | Hashed into `note_hash = H(storage_slot, partial_commitment, value)` | | `value` | Completion (public or private) | Same hash; supplied by the completer's call | (`storage_slot` is typically known during private execution too; it is just not bound into the note hash until completion.) The creator fixes who gets paid (`owner`) and who may finalize (`completer`); the completer later fixes how much (`value`). Funds therefore flow from the completing side to the note's owner: in AIP-20, completion debits the authorized `from` account (the completer itself, or a payer who authorized it) and credits the `owner` chosen by the creator. ## Single-use semantics[​](#single-use-semantics "Direct link to Single-use semantics") Each partial note is intended to be completed exactly once. The protocol does not enforce this directly: completion checks that a validity commitment exists in the nullifier tree but does not consume it, so a partial note can technically be completed more than once. However, reuse is unsafe for two independent reasons: 1. **Privacy.** The completion log is tagged by `H(partial_commitment)`. Two completions of the same partial note emit logs with the same tag, which publicly links those completions as paying the same recipient. 2. **Discovery.** The recipient's Private eXecution Environment (PXE) treats the partial note as pending until the first matching completion log is found. After the first match, the pending entry is removed. A second completion against the same commitment may not be discovered by the recipient's wallet, so the funds are effectively lost. This is why an AIP-20 commitment should be completed only once. A second `transfer_private_to_commitment` (or `transfer_public_to_commitment`) against the same commitment is not found by the recipient's log processing on the second pass, so the amount is most likely lost. The takeaway: treat each partial note as a one-shot object. To accept multiple payments, create multiple partial notes. ## Completion in public and private contexts[​](#completion-in-public-and-private-contexts "Direct link to Completion in public and private contexts") `PartialUintNote` supports completion in two contexts: * `complete` runs in a public function (AIP-20's `transfer_public_to_commitment`). The storage slot and value are emitted in a public log tagged by the partial note's commitment. Anyone observing the chain learns the amount. * `complete_from_private` runs in a private function (AIP-20's `transfer_private_to_commitment`). The same storage slot and value are emitted in a private log with the same tag. The payload is plaintext, but it is only discoverable by a party that can derive the tag, and the tag derives from the partial note's commitment. For private→private completion, the privacy of the amount depends on whether the partial note's commitment itself is held secret. If the commitment is published publicly (e.g., in an onchain registry), anyone can derive the tag and read the amount from the private log payload. If the commitment is shared only with prospective senders, the amount stays hidden from outside observers. One additional protocol constraint: `complete_from_private` requires the validity commitment to be settled in a prior transaction. A partial note cannot be both created and completed in the same private transaction. The public completion path has no such restriction. ## Implementation[​](#implementation "Direct link to Implementation") All notes in Aztec use the partial note format internally. This ensures that notes produce identical note hashes regardless of whether they were created as complete notes (with all fields known in private) or as partial notes (completed later in public). By having all notes follow the same two-phase hash commitment process, the protocol maintains consistency and allows notes created through different flows to behave identically. ### Note structure example[​](#note-structure-example "Direct link to Note structure example") The `UintNote` struct contains only the `value` field: uint\_note\_def ``` #[derive(Deserialize, Eq, Serialize, Packable)] #[custom_note] pub struct UintNote { /// The number stored in the note. pub value: u128, } ``` > [Source code: noir-projects/aztec-nr/uint-note/src/uint\_note.nr#L29-L36](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/aztec-nr/uint-note/src/uint_note.nr#L29-L36) ### Two-phase commitment process[​](#two-phase-commitment-process "Direct link to Two-phase commitment process") **Phase 1: partial commitment (private execution)** The private fields (`owner` and `randomness`) are committed during local, private execution: compute\_partial\_commitment ``` fn compute_partial_commitment(owner: AztecAddress, randomness: Field) -> Field { poseidon2_hash_with_separator( [owner.to_field(), randomness], DOM_SEP__PARTIAL_NOTE_COMMITMENT, ) } ``` > [Source code: noir-projects/aztec-nr/uint-note/src/uint\_note.nr#L144-L151](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/aztec-nr/uint-note/src/uint_note.nr#L144-L151) This creates a partial note commitment: ``` partial_commitment = H(owner, randomness) ``` **Phase 2: note completion (public execution)** The note is completed by hashing the partial commitment with the public value: compute\_complete\_note\_hash ``` fn compute_complete_note_hash(self, storage_slot: Field, value: u128) -> Field { compute_note_hash(storage_slot, [self.commitment, value.to_field()]) } ``` > [Source code: noir-projects/aztec-nr/uint-note/src/uint\_note.nr#L245-L249](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/aztec-nr/uint-note/src/uint_note.nr#L245-L249) The resulting structure is a nested commitment: ``` note_hash = H(storage_slot, H(owner, randomness), value) = H(storage_slot, partial_commitment, value) ``` ## Universal note format[​](#universal-note-format "Direct link to Universal note format") All notes in Aztec use the partial note format internally, even when all data is known during private execution. This ensures consistent note hash computation regardless of how the note was created. When a note is created with all fields known (including `owner`, `storage_slot`, `randomness`, and `value`): 1. A partial commitment is computed from the private fields (`owner`, `randomness`) 2. The partial commitment is immediately completed with the `storage_slot` and `value` fields compute\_note\_hash ``` fn compute_note_hash(self, owner: AztecAddress, storage_slot: Field, randomness: Field) -> Field { // Partial notes can be implemented by having the note hash be either the result of multiscalar multiplication // (MSM), or two rounds of poseidon. MSM results in more constraints and is only required when multiple // variants of partial notes are supported. Because UintNote has just one variant (where the value is public), // we use poseidon instead. // We must compute the same note hash as would be produced by a partial note created and completed with the // same values, so that notes all behave the same way regardless of how they were created. To achieve this, we // perform both steps of the partial note computation. // First we create the partial note from a commitment to the private content. let partial_note = PartialUintNote { commitment: compute_partial_commitment(owner, randomness) }; // Then compute the completion note hash. In a real partial note this step would be performed in public. partial_note.compute_complete_note_hash(storage_slot, self.value) } ``` > [Source code: noir-projects/aztec-nr/uint-note/src/uint\_note.nr#L39-L56](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/aztec-nr/uint-note/src/uint_note.nr#L39-L56) This two-step process ensures that notes with identical field values produce identical note hashes, regardless of whether they were created as partial notes or complete notes. ## Partial notes in practice[​](#partial-notes-in-practice "Direct link to Partial notes in practice") To understand how to use partial notes in practice, [this AMM contract](https://github.com/AztecProtocol/aztec-packages/tree/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/amm_contract) uses partial notes to initiate and complete the swap of `token1` to `token2`. Since the exchange rate is onchain, it cannot be known ahead of time while executing in private so a full note cannot be created. Instead, a partial note is created for the `owner` swapping the tokens. This partial note is then completed during public execution once the exchange rate can be read. For a different application of the same primitive, where the partial note represents an offer to be paid rather than a deferred DeFi settlement, see [partial notes as payment endpoints](/developers/testnet/docs/aztec-nr/framework-description/advanced/partial_notes_as_payment_endpoints.md). --- # Partial notes as payment endpoints Assumed token standard This page assumes the [AIP-20 fungible token standard](/developers/testnet/docs/aztec-nr/standards/aip-20.md), which exposes commitment-based transfers directly: the completer is an explicit argument to `initialize_transfer_commitment`, and completion debits a separately authorized account. The example `token_contract` in aztec-packages also supports a commitment-based flow (the two-step `prepare_private_balance_increase` plus `finalize_transfer_to_private`), but its prepare step takes no `completer` argument. It always sets the completer to `msg_sender`, the caller of `prepare_private_balance_increase`. The private relayer flow described on this page is still achievable there, but the chosen completer (for example, the relayer) must itself be the caller of `prepare`, rather than the recipient naming an arbitrary completer in a single self-issued call as AIP-20 allows. ## The problem[​](#the-problem "Direct link to The problem") Consider a naming service that resolves `alice.aztec` to something that anyone can pay. If the name resolves to a stable Aztec address, every sender and every registry observer can link payments to the same recipient identifier, even when the payment notes themselves are private. The recipient is forced to share that identifier with every sender, so two senders can collude to confirm they paid the same person. A privacy-conscious naming service should let a sender pay `alice.aztec` without: * learning the recipient's actual Aztec address, * producing a sender-visible or registry-visible link between any two payments to the same name, * requiring the recipient to take any action when the payment is made. [Partial notes](/developers/testnet/docs/aztec-nr/framework-description/advanced/partial_notes.md) provide the primitive. This page covers how to use them to back a stable name with a rotating supply of unlinkable, one-shot payment endpoints. The term *payment endpoint* in this page is application-level. Aztec's protocol terminology stays with "partial note," "completer," and "completion log." ## What lives where[​](#what-lives-where "Direct link to What lives where") The trick is that the name never maps to an address at all. It maps to a list of partial note commitments, and senders pay into a commitment. Concretely, for `alice.aztec`: 1. **Alice's wallet creates partial notes ahead of time.** Each one is a commitment `H(alice_address, randomness_i)` with fresh randomness per note. Her address is inside the hash, blinded by the randomness, so the commitment reveals nothing about her. 2. **The chain records that the note can be completed, and by whom.** Creating each partial note writes a *validity commitment* `H(partial_commitment, completer)` to the nullifier tree. This is not the partial note itself, it binds the partial note's commitment to its designated completer, and is what the token checks at completion to confirm the completer is legitimate. It is also just a hash, so no address is visible. 3. **The lookup channel stores `alice.aztec → [c1, c2, c3, ...]`.** The *lookup channel* is wherever the name resolves: an ENS text record, a JSON file Alice hosts, an onchain registry contract. The commitments are opaque `Field` values that can be stored as a plain array, and this mapping is the only thing senders ever read. 4. **Alice's Private eXecution Environment (PXE) holds the preimages.** Her wallet knows which commitments are hers and watches for their completion logs, so payments land without any action from her. The rest of this page unpacks each piece: how a partial note works as an offer to be paid, who is allowed to complete one, and where the lookup channel can live. ## A partial note as an offer to be paid[​](#a-partial-note-as-an-offer-to-be-paid "Direct link to A partial note as an offer to be paid") A partial note is a commitment to `(owner, randomness)`. Once created, the commitment is just a `Field`: it can be copied freely, stored anywhere, and shared with any sender. Holding the commitment is not enough to complete it, though; only the note's designated *completer* can fill in `(storage_slot, value)` and finalize it. With AIP-20, the recipient creates a partial note by calling `initialize_transfer_commitment`, choosing both the eventual owner and the completer: ``` #[external("private")] fn initialize_transfer_commitment(to: AztecAddress, completer: AztecAddress) -> Field ``` The completer is bound into a *validity commitment* `H(partial_commitment, completer)` recorded in the nullifier tree. Despite the name, the nullifier tree is used here only as an append-only set whose entries can be checked for existence. The validity commitment uses its own domain separator, so it is not a note nullifier and consumes nothing. Completion (`transfer_private_to_commitment` or `transfer_public_to_commitment`) sets the completer to the caller's `msg_sender` and checks that the matching validity commitment exists, so a partial note can only be finalized by the address set as its completer. Completion debits a separate, authorized `from` account, so the payer and the completer can be different parties. Two properties make this useful as a payment endpoint: * **The recipient takes no action at payment time.** When the note is created, the token sends the recipient a private message identifying it. The recipient's PXE holds this pending entry and scans for the completion log whenever it has the chance. Receiving an ordinary transfer is just as passive; the difference here is that the recipient also never has to interact with the sender, because the commitment was published ahead of time. * **The completer is fixed when the note is created.** The recipient chooses who can finalize the note at creation time, and that choice is enforced cryptographically. Each partial note is single-use (see [single-use semantics](/developers/testnet/docs/aztec-nr/framework-description/advanced/partial_notes.md#single-use-semantics)), so an endpoint that accepts many payments must hold many partial notes. ## The completer choice[​](#the-completer-choice "Direct link to The completer choice") The choice of `completer` determines who can complete a given partial note, and so how senders pay through it. Two options matter: ### Completer = a specific sender[​](#completer--a-specific-sender "Direct link to Completer = a specific sender") The recipient creates one partial note per known sender ahead of time, with each `completer` set to that sender's address. The sender finalizes the note with their own funds by calling `transfer_private_to_commitment` (or `transfer_public_to_commitment`), acting as both the `from` and the `msg_sender`/completer. This works without any new contracts but requires the recipient to know each sender's address in advance. It is useful for repeat payers (subscriptions, regular invoices) but does not scale to "anyone can pay this name." ### Completer = a relayer contract[​](#completer--a-relayer-contract "Direct link to Completer = a relayer contract") The recipient creates partial notes whose `completer` is a known *relayer* contract: a contract whose only job is to forward a sender's payment into the token's completion function. Any sender can invoke the relayer, the relayer is what the token sees as `msg_sender` (so the validity commitment check passes), and the sender is debited as the authorized `from`. This is the option that scales to unknown senders. The relayer needs no per-name logic, so a single global relayer contract could serve every payment endpoint on the network. ## The relayer contract[​](#the-relayer-contract "Direct link to The relayer contract") An illustrative sketch (not a compilable reference): ``` contract PaymentRelayer { // Called by any sender holding a commitment. Forwards into the token's // completion from the relayer's call frame, so the token sees the relayer // as the completer, while the sender is debited as the authorized `from`. #[external("private")] fn pay_private(token: AztecAddress, commitment: Field, amount: u128, authwit_nonce: Field) { let from = self.context.msg_sender(); self.call(Token::at(token).transfer_private_to_commitment( from, commitment, amount, authwit_nonce, )) } } ``` How the flow works: * The recipient creates notes with `initialize_transfer_commitment(to: recipient, completer: relayer)` and publishes the commitments through their [lookup channel](#what-lives-where). Creating a note is a direct call to the token; the relayer only participates at completion. * A sender calls `pay_private` with a commitment obtained from the recipient's lookup channel. The relayer reads `from = msg_sender` (the sender) and forwards into the token. At the token's frame, `msg_sender` is the relayer, matching the completer the note was created with, so the token debits the sender and completes the note. * An [authwit](/developers/testnet/docs/aztec-nr/framework-description/authentication_witnesses.md) signed by the sender authorizes the relayer to call `transfer_private_to_commitment` with these specific arguments, since the token sees the relayer, not the sender, as the immediate caller. Both private and public payments are supported: `transfer_private_to_commitment` debits the sender's private balance, `transfer_public_to_commitment` debits their public balance. Both take an explicit authorized `from`, so the relayer never needs to hold or be pre-funded with the sender's tokens. Fee sponsorship is separate, handled by a [Fee Payment Contract (FPC)](/developers/testnet/docs/foundational-topics/fees.md) during the transaction's setup phase. ## The distribution choice[​](#the-distribution-choice "Direct link to The distribution choice") Where the mapping "name → partial-note commitments" lives is independent of who completes the notes: * **Offchain.** A static file at `alice.example/aztec.json`, an ENS text record, IPFS, or any other lookup channel. The chain never sees the name or the list size. Requires trust in the hosting and a way to authenticate the result. * **Onchain.** A registry contract with public storage mapping names to commitments. Censorship-resistant and allows atomic lookup-and-pay in a single transaction, but exposes the list size, refill cadence, and the plaintext name. The two choices are orthogonal. Either channel can hand out commitments created with any completer; only the lookup mechanism differs. ## A recommended pattern[​](#a-recommended-pattern "Direct link to A recommended pattern") For "name → unlinkable payment endpoint, passive recipient," the combination that gives the best privacy / UX ratio is offchain distribution (the recipient hosts the lookup), a relayer contract as the completer, and private→private completion so the amount stays hidden from observers who do not hold the commitment. The recipient is responsible for: 1. Periodically creating fresh partial notes with the relayer as completer. 2. Publishing the resulting commitments through whatever lookup channel they choose. 3. Pruning commitments that have been consumed (the recipient's PXE knows when each partial note has been completed). Refill cadence is an operational concern. If senders consume commitments faster than the recipient refills, the lookup will return nothing. Batching the creation of many partial notes in a single transaction reduces the per-payment cost. ## What is hidden, what leaks[​](#what-is-hidden-what-leaks "Direct link to What is hidden, what leaks") The pattern hides: * The recipient's Aztec address from senders. * The link between any two payments to the same name, as long as each payment consumes a different partial note. * The amount, in private→private completion, provided the commitment is not exposed to the observer. The completion log payload is plaintext but only discoverable by a party who can derive the tag, and the tag derives from the commitment. If the recipient's lookup channel hands out the same list to every viewer, any observer who fetches the list can derive tags and read completed amounts from it. Authenticated or sender-specific distribution narrows this exposure. The pattern leaks: * The fact that the relayer contract was invoked in a transaction. * The completion log tag for each payment. The tag does not reveal the recipient, but it confirms that *some* completion happened against *some* partial note. * Anything the lookup channel itself reveals. An offchain channel can hide the existence of the name; an onchain registry cannot. * The recipient's refill cadence, if the transactions that create fresh partial notes are visible. A reused partial note breaks both the privacy property (linkable completions) and the discovery property (the recipient's wallet will miss the second completion). The published commitments must rotate; commitments must not be republished after consumption. ## What this does not solve[​](#what-this-does-not-solve "Direct link to What this does not solve") This pattern is not a stealth-address scheme. It requires the recipient to create and publish a list of commitments ahead of time, and to keep refilling it; senders draw from that list. A stealth-address scheme, by contrast, lets a recipient publish a single meta-address once and stay otherwise passive, with each sender deriving a fresh one-time address non-interactively. Partial-note endpoints trade that recipient passivity for an explicit, recipient-controlled supply of payment slots. ## Forward-looking note[​](#forward-looking-note "Direct link to Forward-looking note") Partial-note creation uses `MessageDelivery::onchain_unconstrained` today. A constrained delivery mode (`MessageDelivery::onchain_constrained`) now exists, but its log tag is not yet fully constrained and partial notes do not use it. Constrained tagging and handshaking is tracked in [aztec-packages issue #14565](https://github.com/AztecProtocol/aztec-packages/issues/14565) and may change how recipients discover partial-note creation messages. The concepts on this page (single-use commitments, completer binding, distribution choice) are stable across that change; specific code patterns on the recipient's discovery side may shift. ## Related[​](#related "Direct link to Related") * [Partial notes](/developers/testnet/docs/aztec-nr/framework-description/advanced/partial_notes.md): the underlying primitive. * [AIP-20: Fungible Token](/developers/testnet/docs/aztec-nr/standards/aip-20.md): the token standard assumed throughout this page. * [Keys](/developers/testnet/docs/foundational-topics/accounts/keys.md): how Aztec accounts derive addresses. Partial-note endpoints offer an alternative to addresses for the specific use case of being paid. * [Fees](/developers/testnet/docs/foundational-topics/fees.md): how fee payment and sponsorship work, independent of the partial-note pattern. --- # Oracle Functions This page goes over what oracles are in Aztec and how they work. Looking for a hands-on guide? You can learn how to use oracles in a smart contract [here](/developers/testnet/docs/aztec-nr/framework-description/advanced/how_to_use_capsules.md). An oracle is something that allows us to get data from the outside world into our contracts. The most widely-known types of oracles in blockchain systems are probably Chainlink price feeds, which allow us to get the price of an asset in USD taking non-blockchain data into account. While this is one type of oracle, the more general oracle, allows us to get any data into the contract. In the context of oracle functions or oracle calls in Aztec, it can essentially be seen as user-provided arguments, that can be fetched at any point in the circuit, and don't need to be an input parameter. **Why is this useful? Why don't just pass them as input parameters?** In the world of EVM, you would just read the values directly from storage and call it a day. However, when we are working with circuits for private execution, this becomes more tricky as you cannot just read the storage directly from your state tree, because there are only commitments (e.g. hashes) there. The pre-images (content) of your commitments need to be provided to the function to prove that you actually allowed to modify them. If we fetch the notes using an oracle call, we can keep the function signature independent of the underlying data and make it easier to use. A similar idea, applied to the authentication mechanism is used for the Authentication Witnesses that allow us to have a single function signature for any wallet implementation, see [AuthWit](/developers/testnet/docs/aztec-nr/framework-description/authentication_witnesses.md) for more information on this. Oracles introduce **non-determinism** into a circuit, and thus are `unconstrained`. It is important that any information that is injected into a circuit through an oracle is later constrained for correctness. Otherwise, the circuit will be **under-constrained** and potentially insecure! `Aztec.nr` has a [module dedicated to its oracles](/aztec-nr-api/testnet/noir_aztec/oracle/index.html) where you can browse the full list. ## Inbuilt oracles[​](#inbuilt-oracles "Direct link to Inbuilt oracles") * [`debug_log`](/aztec-nr-api/testnet/noir_aztec/protocol/logging/fn.debug_log) - Provides debug functions that can be used to log information to the console. Read more about debugging [here](/developers/testnet/docs/aztec-nr/debugging.md). * [`auth_witness`](/aztec-nr-api/testnet/noir_aztec/oracle/auth_witness/index.html) - Provides a way to fetch the authentication witness for a given address. This is useful when building account contracts to support approve-like functionality. * [`get_l1_to_l2_membership_witness`](/aztec-nr-api/testnet/noir_aztec/oracle/get_l1_to_l2_membership_witness/index.html) - Returns the leaf index and sibling path for an L1 to L2 message, used to prove message existence in cross-chain applications like token bridges. * [`notes`](/aztec-nr-api/testnet/noir_aztec/oracle/notes/index.html) - Provides functions related to notes, such as fetching notes from storage, used behind the scenes for value notes and other pre-built note implementations. * [`logs`](/aztec-nr-api/testnet/noir_aztec/oracle/logs/index.html) - Provides functions to log encrypted and unencrypted data. Find a full list [on GitHub](https://github.com/AztecProtocol/aztec-packages/tree/v5.0.0-rc.2/noir-projects/aztec-nr/aztec/src/oracle). Please note that it is **not** possible to write a custom oracle for your dapp. Oracles are implemented in the PXE, so all users of your dapp would have to use a PXE with your custom oracle included. If you want to inject some arbitrary data that does not have a dedicated oracle, you can use [capsules](/developers/testnet/docs/aztec-nr/framework-description/advanced/how_to_use_capsules.md). --- # Writing Efficient Contracts ## Writing functions[​](#writing-functions "Direct link to Writing functions") On Ethereum L1, all data is public and all execution is completely reproducible. The Aztec L2 takes on the challenge of execution of private functions on private data. This is done client side, along with the generation of corresponding proofs, so that the network can verify the proofs and append any encrypted data/nullifiers (privacy preserving state update). This highlights a key difference with how public vs private functions are written. Writing efficiently * **Public functions** can be written intuitively - optimising for execution/gas as one would for EVM L2s * **Private functions** are optimized differently, as they are compiled to a circuit to be proven locally (see [Thinking in Circuits](https://noir-lang.org/docs/explainers/explainer-writing-noir)) ## Assessing efficiency[​](#assessing-efficiency "Direct link to Assessing efficiency") On Aztec (like other L2s) there are several costs/limit to consider... * L1 costs - execution, blobs, events * L2 costs - public execution, data, logs * Local limits - proof generation time, execution ### Local Proof generation[​](#local-proof-generation "Direct link to Local Proof generation") Since proof generation is a significant local burden, being mindful of the gate-count of private functions is important. The gate-count is a proportionate indicator of the memory and time required to prove locally, so should not be ignored. #### Noir for circuits[​](#noir-for-circuits "Direct link to Noir for circuits") An explanation of efficient use of Noir for circuits should be considered for each subsection under [writing efficient Noir](https://noir-lang.org/docs/explainers/explainer-writing-noir#writing-efficient-noir-for-performant-products) to avoid hitting local limits. The general theme is to use language features that favour the underlying primitives and representation of a circuit from code. A couple of examples: * Since the underlying cryptography uses an equation made of additions and multiplications, these are more efficient (wrt gate count) in Noir than say bit-shifting. * Unconstrained functions by definition do not constrain their operations/output, so do not contribute to gate count. Using them carefully can bring in some savings, but the results must then be constrained so that proofs are meaningful for your application. Tradeoffs and caveats Each optimisation technique has its own tradeoffs and caveats so should be carefully considered with the full details in the linked [section](https://noir-lang.org/docs/explainers/explainer-writing-noir#writing-efficient-noir-for-performant-products). #### Overhead of nested private calls[​](#overhead-of-nested-private-calls "Direct link to Overhead of nested private calls") Every transaction pays a fixed kernel overhead (\~290k gates for init, reset, and tail circuits). Each additional private function call beyond the account entrypoint adds a `private_kernel_inner` iteration (\~101k gates). This overhead compounds with the number of distinct private function calls, so be mindful of calling/nesting too many private functions — this may influence your design towards larger private functions rather than conventionally atomic ones. For example, if you have a function that calls an external verification step as a separate private function, inlining that verification saves an entire kernel iteration (\~101k gates), even if it slightly increases the calling function's own gate count. See [Private Kernel Circuit - Performance Impact](/developers/testnet/docs/foundational-topics/advanced/circuits/private_kernel.md#performance-impact) for detailed numbers. #### Profiling[​](#profiling "Direct link to Profiling") Measuring gate counts is explained in the [profiling guide](/developers/testnet/docs/aztec-nr/framework-description/advanced/how_to_profile_transactions.md). Use `aztec profile gates` for quick per-function gate counts, or `aztec-wallet profile` for full transaction profiling including kernel overhead. ### L2 Data costs[​](#l2-data-costs "Direct link to L2 Data costs") Of the L2 costs, the public/private data being updated is most significant. As L2 functions create notes, nullifiers, encrypted logs, all of this get posted into blobs on ethereum and will be quite expensive Data packing You can reduce storage operation costs by implementing custom `Packable` for your structs, packing multiple sub-`Field` values into fewer `Field` elements. See [Data Packing and Serialization](/developers/testnet/docs/aztec-nr/framework-description/data_packing.md) for details. ### L1 Limits[​](#l1-limits "Direct link to L1 Limits") While most zk rollups don't leverage the zero-knowledge property like Aztec, they do leverage the succinctness property. That is, what is stored in an L1 contract is simply a hash. For data availability, blobs are utilized since data storage is often cheaper here than in contracts. Like other L2s such costs are factored into the L2 fee mechanisms. These limits can be seen and iterated on when a transaction is simulated/estimated. ## Examples for private functions (reducing gate count)[​](#examples-for-private-functions-reducing-gate-count "Direct link to Examples for private functions (reducing gate count)") After the first section about generating a flamegraph for an Aztec function, each section shows an example of different optimisation techniques. ### Inspecting with flamegraphs[​](#inspecting-with-flamegraphs "Direct link to Inspecting with flamegraphs") Use the Noir profiler to generate flamegraphs for your contract functions. The profiler is installed automatically with Nargo (starting noirup v0.1.4). ``` # Generate a gates flamegraph (requires bb backend) noir-profiler gates \ --artifact-path ./target/counter-Counter.json \ --backend-path bb \ --output ./target ``` Open the generated `.svg` file in a browser for an interactive view. For more details, see the [profiling guide](/developers/testnet/docs/aztec-nr/framework-description/advanced/how_to_profile_transactions.md). ![](/assets/ideal-img/flamegraph-counter.aeb3d35.640.png) To get a sense of things, here is a table of gate counts for common operations: | Gates | Operation | | ------- | ------------------------------------------------------------------------------ | | \~75 | Hashing 3 fields with Poseidon2 | | 3500 | Reading a value from a tree (public data tree, note hash tree, nullifier tree) | | 4000 | Reading a delayed public mutable read | | \~5,000 | Calculating sha256 (varies by input size) | | Varies | Constrained encryption of a private log (depends on field count) | | Varies | Constrained encryption and tagging of a private log (depends on field count) | ### Optimization: use arithmetic instead of non-arithmetic operations[​](#optimization-use-arithmetic-instead-of-non-arithmetic-operations "Direct link to Optimization: use arithmetic instead of non-arithmetic operations") Because the underlying equation in the proving backend makes use of multiplication and addition, these operations incur less gates than bit-shifting or bit-masking. For example: ``` comptime global TWO_POW_16: Field = 2.pow_32(16); // ... { #[external("private")] fn mul_inefficient(number: Field) -> u128 { number as u128 << 16 as u8 } // 5244 gates #[external("private")] fn mul_efficient(number: Field) -> u128 { (number * TWO_POW_16) as u128 } // 5184 gates (60 gates less) } ``` When comparing the flamegraph of the two functions, the inefficient shift example has a section of gates not present in the multiplication example. This difference equates to a saving of 60 gates. In the same vein bitwise `AND`/`OR`, and inequality relational operators (`>`, `<`) are expensive. Try avoid these in your circuits. For example, use boolean equality effectively instead of `>=`: ``` { #[external("private")] fn sum_from_inefficient(from: u32, array: [u32; 1000]) -> u32 { let mut sum: u32 = 0; for i in 0..1000 { if i >= from { // condition based on `>=` each time (higher gate count) sum += array[i]; } } sum } // 44317 gates #[external("private")] fn sum_from_efficient(from: u32, array: [u32; 1000]) -> u32 { let mut sum: u32 = 0; let mut do_sum = false; for i in 0..1000 { if i == from { // latches boolean at transition (equality comparison) do_sum = true; } if do_sum { // condition based on boolean true (lower gate count) sum += array[i]; } } sum } // 45068 gates (751 gates more due to the boolean operations, but the pattern demonstrates how to avoid range checks) } ``` So for a loop of 1000 iterations, 751 gates were saved by: * Adding an equivalence check and a boolean assignment * Replacing `>=` with a boolean equivalence check Difference with Rust Such designs with boolean flags lend themselves well into logical comparisons too since `&&` and `||` do not exist. With booleans, using `&` and `|` can give you the required logic efficiently. For more points specific to the Noir language, see [this](https://noir-lang.org/docs/explainers/explainer-writing-noir#translating-from-rust) section. ### Optimization: Loop design[​](#optimization-loop-design "Direct link to Optimization: Loop design") Since private functions are circuits, their size must be known at compile time, which is equivalent to its execution trace. See [this example](https://github.com/noir-lang/noir-examples/blob/master/noir_by_example/loops/noir/src/main.nr#L11) for how to use loops when dynamic execution lengths (ie variable number of loops) is not possible. ### Optimization: considered use of `unconstrained` functions[​](#optimization-considered-use-of-unconstrained-functions "Direct link to optimization-considered-use-of-unconstrained-functions") #### Example - calculating square root[​](#example---calculating-square-root "Direct link to Example - calculating square root") Consider the following example of an implementation of the `sqrt` function: ``` use aztec::macros::aztec; #[aztec] pub contract OptimisationExample { use aztec::macros::{functions::{external, initializer}, storage::storage}; #[storage] struct Storage {} #[external("public")] #[initializer] fn constructor() {} #[external("private")] fn sqrt_inefficient(number: Field) -> Field { super::sqrt_constrained(number) } #[external("private")] fn sqrt_efficient(number: Field) -> Field { // Safety: calculate in unconstrained function, then constrain the result let x = unsafe { super::sqrt_unconstrained(number) }; assert(x * x == number, "x*x should be number"); x } } fn sqrt_constrained(number: Field) -> Field { let MAX_LEN = 100; let mut guess = number; let mut guess_squared = guess * guess; for _ in 1..MAX_LEN as u32 + 1 { // only use square root part of circuit when required, otherwise use alternative part of circuit that does nothing // Note: both parts of the circuit exist MAX_LEN times in the circuit, regardless of whether the square root part is used or not if (guess_squared != number) { guess = (guess + number / guess) / 2; guess_squared = guess * guess; } } guess } unconstrained fn sqrt_unconstrained(number: Field) -> Field { let mut guess = number; let mut guess_squared = guess * guess; while guess_squared != number { guess = (guess + number / guess) / 2; guess_squared = guess * guess; } guess } ``` The two implementations after the contract differ in one being constrained vs unconstrained, as well as the loop implementation (which has other design considerations). Measuring the two, we find the `sqrt_inefficient` to require around 1500 extra gates compared to `sqrt_efficient`. To generate flamegraphs for each function: ``` noir-profiler gates \ --artifact-path ./target/optimisation_example-OptimisationExample.json \ --backend-path bb \ --output ./target ``` If you make changes to the code, recompile and regenerate the flamegraph, then refresh the `.svg` file in your browser. Note: this is largely a factor of the loop size choice based on the maximum size of `number` you are required to be calculating the square root of. For larger numbers, the loop would have to be much larger, so perform in an unconstrained way (then constraining the result) is much more efficient. #### Example - sorting an array[​](#example---sorting-an-array "Direct link to Example - sorting an array") Like with sqrt, we have the inefficient function that does the sort with constrained operations, and the efficient function that uses the unconstrained sort function then constrains the result. ``` //... { #[external("private")] fn sort_inefficient(array: [u32; super::ARRAY_SIZE]) -> [u32; super::ARRAY_SIZE] { let mut sorted_array = array; for i in 0..super::ARRAY_SIZE as u32 { for j in 0..super::ARRAY_SIZE as u32 { if sorted_array[i] < sorted_array[j] { let temp = sorted_array[i as u32]; sorted_array[i as u32] = sorted_array[j as u32]; sorted_array[j as u32] = temp; } } } sorted_array } // 6823 gates for 10 elements, 127780 gates for 100 elements #[external("private")] fn sort_efficient(array: [u32; super::ARRAY_SIZE]) -> [u32; super::ARRAY_SIZE] { // Safety: calculate in unconstrained function, then constrain the result let sorted_array = unsafe { super::sort_array(array) }; // constrain that sorted_array elements are sorted for i in 0..super::ARRAY_SIZE as u32 - 1 { assert(sorted_array[i] <= sorted_array[i + 1], "array should be sorted"); } // Note: A production implementation should also verify that sorted_array is a // permutation of the input array to prevent a malicious prover from returning // arbitrary sorted values. sorted_array } // 5870 gates (953 gates less) for 10 elements, 12582 gates for 100 elements (115198 gates less) } unconstrained fn sort_array(array: [u32; ARRAY_SIZE]) -> [u32; ARRAY_SIZE] { let mut sorted_array = array; for i in 0..ARRAY_SIZE as u32 { for j in 0..ARRAY_SIZE as u32 { if sorted_array[i] < sorted_array[j] { let temp = sorted_array[i as u32]; sorted_array[i as u32] = sorted_array[j as u32]; sorted_array[j as u32] = temp; } } } sorted_array } ``` Like before, `noir-profiler` can be used to visualize the gate counts of the private functions, highlighting that 953 gates could be saved. Note: The stdlib provides a highly optimized version of sort on arrays, `array.sort()`, which saves even more gates. ``` #[external("private")] fn sort_stdlib(array: [u32; super::ARRAY_SIZE]) -> [u32; super::ARRAY_SIZE] { array.sort() } // 5943 gates (880 gates less) for 10 elements, 13308 gates for 100 elements (114472 gates less) ``` #### Example - refactoring arrays[​](#example---refactoring-arrays "Direct link to Example - refactoring arrays") In the same vein, refactoring is inefficient when done constrained, and more efficient to do unconstrained then constrain the output. ``` { #[external("private")] fn refactor_inefficient(array: [u32; super::ARRAY_SIZE]) -> [u32; super::ARRAY_SIZE] { let mut compacted_array = [0; super::ARRAY_SIZE]; let mut index = 0; for i in 0..super::ARRAY_SIZE as u32 { if (array[i] != 0) { compacted_array[index] = array[i]; index += 1; } } compacted_array } // 6570 gates for 10 elements, 93071 gates for 100 elements #[external("private")] fn refactor_efficient(array: [u32; super::ARRAY_SIZE]) -> [u32; super::ARRAY_SIZE] { let compacted_array = unsafe { super::refactor_array(array) }; // count non-zero elements in array let mut count = 0; for i in 0..super::ARRAY_SIZE as u32 { if (array[i] != 0) { count += 1; } } // count non-zero elements in compacted_array let mut count_compacted = 0; for i in 0..super::ARRAY_SIZE as u32 { if (compacted_array[i] != 0) { count_compacted += 1; } else { assert(compacted_array[i] == 0, "trailing compacted_array elements should be 0"); } } assert(count == count_compacted, "count should be equal to count_compacted"); compacted_array } // 5825 gates (745 gates less), 12290 gates for 100 elements (80781 gates less) } unconstrained fn refactor_array(array: [u32; ARRAY_SIZE]) -> [u32; ARRAY_SIZE] { let mut compacted_array = [0; ARRAY_SIZE]; let mut index = 0; for i in 0..ARRAY_SIZE as u32 { if (array[i] != 0) { compacted_array[index] = array[i]; index += 1; } } compacted_array } ``` ### Optimizing: Reducing L2 reads[​](#optimizing-reducing-l2-reads "Direct link to Optimizing: Reducing L2 reads") If a struct has many fields to be read, we can design an extra variable maintained as the hash of all values within it (like a checksum). When it comes to reading, we can now do an unconstrained read (incurring no read requests), and then check the hash of the result against that stored for the struct. This final check is thus only one read request rather than one per variable. Leverage unconstrained functions When needing to make use of large private operations (eg private execution or many read requests), use of [unconstrained functions](https://noir-lang.org/docs/explainers/explainer-writing-noir#leverage-unconstrained-execution) wisely to reduce the gate count of private functions. --- # Authentication Witnesses Authentication witnesses (authwit) allow other contracts to execute actions on behalf of your account. This guide shows you how to implement and use authwits in your Aztec smart contracts. For a video walkthrough of the concepts and the implementation pattern, watch this explainer (find more on the [video lessons](/developers/testnet/docs/resources/video_lessons.md) page): [How Authorization Works on Aztec](https://www.youtube-nocookie.com/embed/VRZVOCdjGZ4) ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * An Aztec contract project set up with `aztec-nr` dependency * Understanding of private and public functions in Aztec For conceptual background, see [Authentication Witnesses](/developers/testnet/docs/foundational-topics/advanced/authwit.md). ## Import the authwit library[​](#import-the-authwit-library "Direct link to Import the authwit library") The `aztec` library includes authwit functionality. Import the necessary components: ``` use aztec::{ authwit::auth::{compute_authwit_message_hash_from_call, set_authorized}, macros::functions::authorize_once, }; ``` ## Using the `authorize_once` macro[​](#using-the-authorize_once-macro "Direct link to using-the-authorize_once-macro") The `#[authorize_once]` macro validates that a caller has authorization from the `from` address. It handles authwit verification and nullifier emission automatically. ### Private function example[​](#private-function-example "Direct link to Private function example") transfer\_in\_private ``` #[authorize_once("from", "authwit_nonce")] #[external("private")] fn transfer_in_private(from: AztecAddress, to: AztecAddress, amount: u128, authwit_nonce: Field) { self.storage.balances.at(from).sub(amount).deliver(MessageDelivery::onchain_constrained()); self.storage.balances.at(to).add(amount).deliver(MessageDelivery::onchain_constrained()); } ``` > [Source code: noir-projects/noir-contracts/contracts/app/token\_contract/src/main.nr#L270-L277](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L270-L277) ### Public function example[​](#public-function-example "Direct link to Public function example") transfer\_in\_public ``` #[authorize_once("from", "authwit_nonce")] #[external("public")] fn transfer_in_public(from: AztecAddress, to: AztecAddress, amount: u128, authwit_nonce: Field) { let from_balance = self.storage.public_balances.at(from).read().sub(amount); self.storage.public_balances.at(from).write(from_balance); let to_balance = self.storage.public_balances.at(to).read().add(amount); self.storage.public_balances.at(to).write(to_balance); } ``` > [Source code: noir-projects/noir-contracts/contracts/app/token\_contract/src/main.nr#L156-L165](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L156-L165) The macro parameters specify: * `"from"` - the parameter name containing the address that must have authorized the call * `"authwit_nonce"` - the parameter name containing the nonce for replay protection ## Setting authorization from contracts[​](#setting-authorization-from-contracts "Direct link to Setting authorization from contracts") When a contract needs to authorize another contract to act on its behalf, use `set_authorized` to update the auth registry. This is common in bridge contracts where contract A authorizes contract B to perform actions. authwit\_uniswap\_set ``` // This helper method approves the bridge to burn this contract's funds and exits the input asset to L1 // Assumes contract already has funds. // Assume `token` relates to `token_bridge` (ie token_bridge.token == token) // Note that private can't read public return values so created an `only_self` public that handles everything // this method is used for both private and public swaps. #[external("public")] #[only_self] fn _approve_bridge_and_exit_input_asset_to_L1(token: AztecAddress, token_bridge: AztecAddress, amount: u128) { // Since we will authorize and instantly spend the funds, all in public, we can use the same nonce // every interaction. In practice, the authwit should be squashed, so this is also cheap! let authwit_nonce = 0xdeadbeef; let selector = FunctionSelector::from_signature("burn_public((Field),u128,Field)"); let message_hash = compute_authwit_message_hash_from_call( token_bridge, token, self.context.chain_id(), self.context.version(), selector, [self.address.to_field(), amount as Field, authwit_nonce], ); // We need to make a call to update it. set_authorized(self.context, message_hash, true); let this_portal_address = self.storage.portal_address.read(); // Exit to L1 Uniswap Portal ! self.call(TokenBridge::at(token_bridge).exit_to_l1_public( this_portal_address, amount, this_portal_address, authwit_nonce, )); } ``` > [Source code: noir-projects/noir-contracts/contracts/app/uniswap\_contract/src/main.nr#L152-L187](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/uniswap_contract/src/main.nr#L152-L187) Key steps: 1. Compute the message hash using `compute_authwit_message_hash_from_call` 2. Call `set_authorized` to store the approval in the registry 3. Execute the authorized action When authorization and consumption happen in the same transaction, state changes are squashed, saving gas. ## Canceling authwits[​](#canceling-authwits "Direct link to Canceling authwits") Users can revoke an authwit before it's used by emitting its nullifier: cancel\_authwit ``` #[external("private")] fn cancel_authwit(inner_hash: Field) { let on_behalf_of = self.msg_sender(); let nullifier = compute_authwit_nullifier(on_behalf_of, inner_hash); self.context.push_nullifier_unsafe(nullifier); } ``` > [Source code: noir-projects/noir-contracts/contracts/app/token\_contract/src/main.nr#L261-L268](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L261-L268) note The cancel transaction must be finalized before any transaction attempts to use the authwit. If both are pending simultaneously, the outcome depends on which the sequencer includes first. ## Next steps[​](#next-steps "Direct link to Next steps") * [Using authwits in aztec.js](/developers/testnet/docs/aztec-js/how_to_use_authwit.md) - Create and manage authwits from your client application * [Authentication Witnesses concepts](/developers/testnet/docs/foundational-topics/advanced/authwit.md) - Deeper explanation of the authwit mechanism --- # Calling Other Contracts This guide shows you how to call functions in other contracts from your Aztec smart contracts. ## Add the target contract as a dependency[​](#add-the-target-contract-as-a-dependency "Direct link to Add the target contract as a dependency") Add the contract you want to call to your `Nargo.toml` dependencies: ``` [dependencies] token = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v5.0.0-rc.2", directory="noir-projects/noir-contracts/contracts/app/token_contract" } ``` Then import the contract interface at the top of your contract file: ``` use token::Token; ``` ## Call contract functions[​](#call-contract-functions "Direct link to Call contract functions") Use `self.call()` to call functions on other contracts: ``` self.call(Token::at(token_address).transfer(recipient, amount)); ``` The pattern is: 1. Form the call: `Contract::at(address).function_name(args)` 2. Execute it: `self.call(...)` or `self.view(...)` for read-only calls ### Private-to-private calls[​](#private-to-private-calls "Direct link to Private-to-private calls") private\_call ``` let _ = self.call(Token::at(stable_coin).burn_private(from, amount, authwit_nonce)); ``` > [Source code: noir-projects/noir-contracts/contracts/app/lending\_contract/src/main.nr#L218-L220](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/lending_contract/src/main.nr#L218-L220) ### Public-to-public calls[​](#public-to-public-calls "Direct link to Public-to-public calls") From a public function, call other public functions directly: ``` self.call(Token::at(token_address).transfer_in_public(recipient, amount)); ``` Capture return values by assigning the result: ``` let balance = self.view(Token::at(token_address).balance_of_public(account)); ``` Use `self.view()` for read-only calls that cannot modify state. ### Private-to-public calls[​](#private-to-public-calls "Direct link to Private-to-public calls") From a private function, enqueue public function calls for later execution: ``` self.enqueue(Token::at(token_address).mint_to_public(recipient, amount)); ``` info Public functions execute after all private execution completes. Return values are not available in the private context. Learn more about [call types](/developers/testnet/docs/foundational-topics/call_types.md). --- # Contract Artifacts Compiling an Aztec contract produces a contract artifact file (`.json`) containing everything needed to interact with that contract: its name, functions, their interfaces, and compiled bytecode. Since private function bytecode is never published to the network, you need this artifact file to call private functions. Most developers don't need this When you [compile a contract](/developers/testnet/docs/aztec-nr/compiling_contracts.md) and use [`aztec codegen`](/developers/testnet/docs/aztec-js/how_to_deploy_contract.md#generate-typescript-bindings), you get type-safe TypeScript classes that handle artifacts automatically. This page is useful if you're: * Building custom tooling around Aztec contracts * Debugging compilation or deployment issues * Understanding what data is available in artifacts ## Where to Find Artifacts[​](#where-to-find-artifacts "Direct link to Where to Find Artifacts") After running `aztec compile`, artifacts are output to the `target/` directory: ``` target/ └── my_contract-MyContract.json # Contract artifact ``` Use `aztec codegen` to generate TypeScript bindings from these artifacts for type-safe contract interaction. ## Contract Artifact Structure[​](#contract-artifact-structure "Direct link to Contract Artifact Structure") A contract artifact contains: * **`name`**: The contract name as defined in Noir * **`functions`**: Array of function artifacts (private, public dispatch, and utility functions) * **`nonDispatchPublicFunctions`**: Public function ABIs (excluding the dispatch function) * **`outputs`**: Exported structs and globals from the contract * **`storageLayout`**: Storage slot mappings for contract state * **`fileMap`**: Source file mappings for debugging ## Function Properties[​](#function-properties "Direct link to Function Properties") Each function in the artifact includes: | Property | Description | | ----------------- | -------------------------------------------------------------------- | | `name` | Function name as defined in Noir | | `functionType` | One of `private`, `public`, or `utility` | | `isOnlySelf` | If `true`, function can only be called from within the same contract | | `isStatic` | If `true`, function cannot alter state | | `isInitializer` | If `true`, function can be used as a constructor | | `parameters` | Array of input parameters with name, type, and visibility | | `returnTypes` | Array of return value types | | `errorTypes` | Custom error types the function can throw | | `bytecode` | Compiled ACIR bytecode (base64 encoded) | | `verificationKey` | Verification key for private functions (optional) | | `debugSymbols` | Compressed debug information linking to source code | ### Function Types[​](#function-types "Direct link to Function Types") * **`private`**: Executed and proved locally by the client. Bytecode is not published to the network. * **`public`**: Executed and proved by the sequencer. Bytecode is published to the network. * **`utility`**: Executed locally to compute information (e.g., view functions). Cannot be called in transactions. ## Parameter and Return Types[​](#parameter-and-return-types "Direct link to Parameter and Return Types") Parameters and return values use these type definitions: | Type | Description | | --------- | ----------------------------------------------------------------- | | `field` | A field element in the BN254 curve's scalar field | | `boolean` | True/false value | | `integer` | Whole number with `sign` (`signed`/`unsigned`) and `width` (bits) | | `array` | Collection of elements with `length` and element `type` | | `string` | Character sequence with fixed `length` | | `struct` | Composite type with named `fields` and a `path` identifier | | `tuple` | Unnamed composite type with ordered `fields` | Parameter visibility can be `public`, `private`, or `databus`. ## Next Steps[​](#next-steps "Direct link to Next Steps") * [Compile contracts](/developers/testnet/docs/aztec-nr/compiling_contracts.md) to generate artifacts * [Deploy contracts](/developers/testnet/docs/aztec-js/how_to_deploy_contract.md) using generated TypeScript bindings * [Send transactions](/developers/testnet/docs/aztec-js/how_to_send_transaction.md) to interact with deployed contracts --- # Contract Structure High-level structure of how Aztec smart contracts including the different components. ## Directory structure[​](#directory-structure "Direct link to Directory structure") When you create a new project with `aztec new my_project`, it generates a two-crate Noir workspace: a contract crate for your smart contract code and a sibling test crate for Noir tests. layout of an aztec contract project ``` ─── my_project ├── Nargo.toml <-- workspace file ([workspace] members) ├── my_project_contract │ ├── Nargo.toml <-- contract package (type = "contract") │ └── src │ └── main.nr <-- your contract └── my_project_test ├── Nargo.toml <-- test package (type = "lib") └── src └── lib.nr <-- Noir tests ``` The top-level `Nargo.toml` is a workspace file. Contract dependencies live in `my_project_contract/Nargo.toml` (with `type = "contract"`). Tests live in the separate `my_project_test` crate and import the contract by package name (for example, `use my_project_contract::MyContract;`) — see [Testing Contracts](/developers/testnet/docs/aztec-nr/testing_contracts.md). To add another contract to the same workspace, run `aztec new ` from inside the workspace directory; this adds a new `_contract` and `_test` crate pair. To initialize a project inside an existing empty directory, `cd` into it and run `aztec init`, which scaffolds the same two-crate layout pre-populated with a runnable [Counter example](/developers/testnet/docs/tutorials/contract_tutorials/counter_contract.md) (use `aztec new` if you want a blank starting point instead). See the vanilla Noir docs for [more info on packages](https://noir-lang.org/docs/noir/modules_packages_crates/crates_and_packages). ## Contract block[​](#contract-block "Direct link to Contract block") All contracts start with importing the required files and declaring a contract using the `contract` keyword: ``` // import the `aztec` macro from Aztec.nr use aztec::macros::aztec; // use the 'contract' keyword to declare a contract, applying the `aztec` macro #[aztec] pub contract MyContract { // contract code here } ``` By convention, contracts are named in `PascalCase`. The `#[aztec]` macro performs a lot of the low-level operations required to take a circuit language like Noir and build smart contracts out of it - including automatically creating external interfaces, inserting standard contract functions, etc. **All Aztec smart contracts must have this macro applied to them.** **Note:** each Noir crate (package) can only have *a single* contract. If you are writing a multi-contract system, then each of them needs to be in their own separate crate. To learn more about crates and packages, visit the [Noir documentation](https://noir-lang.org/docs/noir/modules_packages_crates/crates_and_packages). ## Imports[​](#imports "Direct link to Imports") Aside from the [`#[aztec]`](/aztec-nr-api/testnet/noir_aztec/macros/fn.aztec) macro import, all other imports need to go *inside* the `contract` block - this is because `contract` acts like `mod`, creating a new [module](https://noir-lang.org/docs/noir/modules_packages_crates/modules). ``` use aztec::macros::aztec; #[aztec] pub contract MyContract { // other imports go here use aztec::state_vars::{PrivateMutable, PrivateSet}; } ``` **Note:** [Noir's VSCode extension](/developers/testnet/docs/aztec-nr/installation.md) is able to take care of most imports and put them in the correct place automatically. ## State Variables[​](#state-variables "Direct link to State Variables") With the boilerplate out of the way, it is now the time to begin defining the contract logic. It is recommended to start development by understanding the shape the *state* of the contract will have: * Which values will be private? * Which will be public? * What properties are required (is mutability or immutability needed? Is there a single global value, like a token total supply, or does each user get one, like a balance?). In Solidity, this is done by simply declaring variables inside of the contract, like so: ``` contract MyContract { uint128 public my_public_state_variable; } ``` In Aztec, defining state requires a few more steps, as there are both private and public variables (where these keywords refer to the privacy of the variable rather than their accessibility), and multiple *kinds* of state variables. We define state using a [`struct`](https://noir-lang.org/docs/noir/concepts/data_types/structs) that will hold the entire contract state. We call this struct *the storage struct*, and each variable inside this struct is called [*a state variable*.](/developers/testnet/docs/aztec-nr/framework-description/state_variables.md) ``` use aztec::macros::aztec; #[aztec] pub contract MyContract { use aztec::{ macros::storage, state_vars::{Owned, PrivateMutable, PublicMutable} }; use uint_note::UintNote; // The storage struct must be named `Storage` and must have the `#[storage]` macro applied to it. // This struct must also have a generic type called C or Context. #[storage] struct Storage { // A private numeric value which can change over time. This value will be hidden, and only those with the secret can know its current value. my_private_state_variable: Owned, Context>, // A public numeric value which can change over time. This value will be known to everyone and is equivalent to the Solidity example above. my_public_state_variable: PublicMutable, } } ``` ## Events[​](#events "Direct link to Events") Like Solidity contracts, Aztec contracts can define events to notify that some state has changed. However, in Aztec, events can also be emitted privately, in which case only some users will learn of the event. [Events](/developers/testnet/docs/aztec-nr/framework-description/events_and_logs.md) are a struct marked with the `#[event]` macro: ``` #[event] struct Transfer { from: AztecAddress, to: AztecAddress, amount: u128, } ``` ## Functions[​](#functions "Direct link to Functions") Contracts are interacted with by invoking their `external` [functions](/developers/testnet/docs/aztec-nr/framework-description/functions.md). There are three kinds of `external` functions: * External **private** functions, which reveal nothing about their execution and are executed off chain on the user's device, producing a zero-knowledge proof of execution that is sent to the network as part of a transaction. * External **public** functions, which nodes in the network invoke publicly (like any `external` Solidity contract function). * External **utility** functions, which are executed off chain on the user's device by applications in order to display useful information, e.g. retrieve contract state. These are never part of a transaction. ``` use aztec::macros::aztec; #[aztec] contract MyContract { use aztec::macros::functions::external; use aztec::protocol::address::AztecAddress; #[external("private")] fn my_private_function(parameter_a: u128, parameter_b: AztecAddress) { // ... } #[external("public")] fn my_public_function(parameter_a: u128, parameter_b: AztecAddress) { // ... } #[external("utility")] unconstrained fn my_utility_function(parameter_a: u128, parameter_b: AztecAddress) { // ... } } ``` Contracts can also define `internal` functions, which cannot be called by other contracts (like any `internal` Solidity function). These exist to help organize your code, reuse functionality, etc. ### Current Limitations[​](#current-limitations "Direct link to Current Limitations") All `#[external]` contract functions must be defined *directly inside the `contract` block*, that is, in the same file. It is possible to define `#[internal]` and helper functions in `mod`s in other files, but not `#[external]` functions. **Noir does not feature inheritance** nor is there currently any other mechanism to extend and reuse contract logic. For example, you cannot take a token contract and extend it to add minting functionality, or reuse it in a liquidity pool. Like Vyper, the entire logic must live in a single file. We expect to lift some of these restrictions sometime after the release of Noir 1.0. ## Next steps[​](#next-steps "Direct link to Next steps") * [Define functions](/developers/testnet/docs/aztec-nr/framework-description/functions.md) - Learn about private, public, and utility functions * [Define storage](/developers/testnet/docs/aztec-nr/framework-description/state_variables.md) - Work with persistent state variables * [Compile your contract](/developers/testnet/docs/aztec-nr/compiling_contracts.md) - Build your contract artifact --- # Contract Upgrades Each contract instance refers to a contract class ID for its code. Upgrading a contract's implementation involves updating its current class ID to a new class ID, while retaining the original class ID for address verification. ## Original class ID[​](#original-class-id "Direct link to Original class ID") A contract stores the original contract class it was instantiated with. This original class ID is used when calculating and verifying the contract's [address](/developers/testnet/docs/foundational-topics/contract_creation.md#instance-address) and remains unchanged even if a contract is upgraded. ## Current class ID[​](#current-class-id "Direct link to Current class ID") When a contract is first deployed, its current class ID equals its original class ID. The current class ID determines which code implementation the contract executes. During an upgrade: * The original class ID remains unchanged * The current class ID is updated to the new implementation * All contract state and data are preserved ## How to upgrade[​](#how-to-upgrade "Direct link to How to upgrade") Contract upgrades must be initiated by the contract itself calling the `ContractInstanceRegistry`: ``` use aztec::protocol::{ constants::CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS, contract_class_id::ContractClassId, }; use contract_instance_registry::ContractInstanceRegistry; #[external("private")] fn update_to(new_class_id: ContractClassId) { self.enqueue( ContractInstanceRegistry::at(CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS) .update(new_class_id) ); } ``` info To use the `ContractInstanceRegistry`, add this dependency to your `Nargo.toml`: ``` contract_instance_registry = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v5.0.0-rc.2", directory="noir-projects/noir-contracts/contracts/protocol_interface/contract_instance_registry_interface" } ``` The `update` function in the registry is a public function, so you can enqueue it from a private function (as shown above) or call it directly from a public function. Access Control The example `update_to` function above has no access control, meaning anyone could call it to upgrade your contract. Production contracts should implement proper authorization checks to secure against malicious upgrades. Contract upgrades use a `DelayedPublicMutable` storage variable in the `ContractInstanceRegistry`, applying to both public and private functions. Upgrades have a delay before taking effect. The default delay is `86400` seconds (one day) but can be configured: ``` #[external("private")] fn set_update_delay(new_delay: u64) { self.enqueue( ContractInstanceRegistry::at(CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS) .set_update_delay(new_delay) ); } ``` The `new_delay` parameter is in seconds. Changing the update delay is also subject to the previous delay, so the first delay change takes `86400` seconds to take effect. info The minimum update delay is `600` seconds. ### Transaction expiration[​](#transaction-expiration "Direct link to Transaction expiration") When sending a transaction, the expiration timestamp is calculated as the current block timestamp plus the minimum update delay of all contracts you interact with. For example: * If you interact with contracts having delays of 1000 and 10000 seconds, expiration is current timestamp + 1000 seconds * If a contract has a pending upgrade in 100 seconds, expiration would be current timestamp + 99 seconds Other `DelayedPublicMutable` storage variables in your transaction may reduce the expiration timestamp further. note Only deployed contract instances can upgrade or change their upgrade delay. This restriction may be lifted in the future. ### Upgrade process[​](#upgrade-process "Direct link to Upgrade process") 1. **Register the new implementation**: Register the new contract class if it contains public functions. The new implementation must maintain state variable compatibility with the original contract. 2. **Perform the upgrade**: Call the update function with the new contract class ID. The contract's original class ID remains unchanged while the current class ID updates to the new implementation. 3. **Wait for the delay**: The upgrade takes effect after the configured delay period. 4. **Verify the upgrade**: After the delay, the contract executes functions from the new implementation. The contract address remains the same since it's based on the original class ID. ### Interacting with an upgraded contract[​](#interacting-with-an-upgraded-contract "Direct link to Interacting with an upgraded contract") The PXE stores contract instances and classes locally. After a contract upgrades, you must register the new artifact with the wallet before interacting with it: ``` import { getContractClassFromArtifact } from '@aztec/aztec.js/contracts'; import { publishContractClass } from '@aztec/aztec.js/deployment'; // Deploy the original contract (use .wait() to get both contract and instance) const { contract, instance } = await UpdatableContract.deploy(wallet, ...args) .send({ from: accountAddress }) .wait(); // Publish the new contract class (required before upgrading) await (await publishContractClass(wallet, UpdatedContractArtifact)) .send({ from: accountAddress }) .wait(); // Get the new contract class ID const updatedContractClassId = ( await getContractClassFromArtifact(UpdatedContractArtifact) ).id; // Trigger the upgrade await contract.methods .update_to(updatedContractClassId) .send({ from: accountAddress }) .wait(); // Wait for the upgrade delay to pass... // Register the new artifact with the wallet await wallet.registerContract(instance, UpdatedContract.artifact); // Create a contract instance with the new artifact const updatedContract = UpdatedContract.at(contract.address, wallet); ``` If you try to register a contract artifact that doesn't match the current contract class, the registration will fail. ### Security considerations[​](#security-considerations "Direct link to Security considerations") 1. **Access control**: Implement proper access controls for upgrade functions. Consider using `set_update_delay` to customize the delay for your security requirements. 2. **State compatibility**: Ensure the new implementation is compatible with existing state. Maintain the same storage layout to prevent data corruption. 3. **Testing**: Test upgrades thoroughly in a development environment. Verify all existing functionality works with the new implementation. --- # Custom notes This guide shows you how to create custom note types for storing specialized private data in your Aztec contracts. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Basic understanding of [Aztec private state and notes](/developers/testnet/docs/foundational-topics/state_management.md) * Aztec development environment set up ## When to create custom notes[​](#when-to-create-custom-notes "Direct link to When to create custom notes") You may want to create your own note type if you need to: * Store specific data types not provided by built-in note libraries * Combine multiple fields into a single note (e.g., game cards with multiple attributes) * Implement custom nullifier schemes for advanced use cases Built-in Note Types Aztec.nr provides pre-built note types for common use cases: **UintNote** - For numeric values like token balances (supports partial notes): ``` # In Nargo.toml uint_note = { git="https://github.com/AztecProtocol/aztec-nr", tag="v5.0.0-rc.2", directory="uint-note" } ``` **FieldNote** - For storing single Field values: ``` # In Nargo.toml field_note = { git="https://github.com/AztecProtocol/aztec-nr", tag="v5.0.0-rc.2", directory="field-note" } ``` **AddressNote** - For storing Aztec addresses: ``` # In Nargo.toml address_note = { git="https://github.com/AztecProtocol/aztec-nr", tag="v5.0.0-rc.2", directory="address-note" } ``` ## Creating a custom note[​](#creating-a-custom-note "Direct link to Creating a custom note") Define your custom note with the `#[note]` macro: nft\_note\_struct ``` use aztec::{macros::notes::note, protocol::traits::Packable}; #[derive(Eq, Packable)] #[note] pub struct NFTNote { pub token_id: Field, } ``` > [Source code: docs/examples/contracts/nft/src/nft.nr#L1-L9](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/nft/src/nft.nr#L1-L9) The `#[note]` macro generates the following for your struct: * `NoteType` trait - Provides a unique type ID for the note * `NoteHash` trait - Handles note hash and nullifier computation * `NoteProperties` - Enables field selection when querying notes ### Required traits[​](#required-traits "Direct link to Required traits") Your note struct must derive: * `Packable` - Required by the `#[note]` macro for note hash computation. You can manually implement this for tighter packing — see [Data Packing and Serialization](/developers/testnet/docs/aztec-nr/framework-description/data_packing.md). * `Eq` - Required by storage types like `PrivateSet` for note comparisons The `#[note]` macro handles the `NoteType`, `NoteHash`, and `NoteProperties` traits automatically. ### How note hashing works[​](#how-note-hashing-works "Direct link to How note hashing works") When a note is inserted, the `#[note]` macro generates code that computes the note hash by combining: 1. **Your packed note data** - The fields you define in your struct 2. **Owner address** - Provided by the storage variable 3. **Storage slot** - Determined by the storage layout 4. **Randomness** - Generated automatically to prevent brute-force attacks This happens automatically - you don't need to include owner or randomness fields in your struct. ## Using notes in storage[​](#using-notes-in-storage "Direct link to Using notes in storage") Notes are stored using `Owned>` which manages note ownership: ``` use aztec::{ macros::storage::storage, state_vars::{Owned, PrivateSet}, }; #[storage] struct Storage { // Collection of notes, indexed by owner nfts: Owned, Context>, } ``` ### Inserting notes[​](#inserting-notes "Direct link to Inserting notes") mint ``` #[external("private")] fn mint(to: AztecAddress, token_id: Field) { assert( self.storage.minter.read().eq(self.msg_sender()), "caller is not the authorized minter", ); // we create an NFT note and insert it to the PrivateSet - a collection of notes meant to be read in private let new_nft = NFTNote { token_id }; self.storage.owners.at(to).insert(new_nft).deliver(MessageDelivery::onchain_constrained()); // calling the internal public function above to indicate that the NFT is taken self.enqueue_self._mark_nft_exists(token_id, true); } ``` > [Source code: docs/examples/contracts/nft/src/main.nr#L50-L65](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/nft/src/main.nr#L50-L65) ### Reading and removing notes[​](#reading-and-removing-notes "Direct link to Reading and removing notes") Use `pop_notes` to read and nullify notes atomically. This is the recommended pattern for most use cases: burn ``` #[external("private")] fn burn(from: AztecAddress, token_id: Field) { assert( self.storage.minter.read().eq(self.msg_sender()), "caller is not the authorized minter", ); // from the NFTNote properties, selects token_id and compares it against the token_id to be burned let options = NoteGetterOptions::new() .select(NFTNote::properties().token_id, Comparator.EQ, token_id) .set_limit(1); let notes = self.storage.owners.at(from).pop_notes(options); assert(notes.len() == 1, "NFT not found"); self.enqueue_self._mark_nft_exists(token_id, false); } ``` > [Source code: docs/examples/contracts/nft/src/main.nr#L75-L92](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/nft/src/main.nr#L75-L92) warning There's also a `get_notes` function that reads without nullifying, but use it with caution - the returned notes may have already been spent in another transaction. ## Custom note hashing[​](#custom-note-hashing "Direct link to Custom note hashing") Most notes should use the standard `#[note]` macro. Use `#[custom_note]` only when you need: * Custom nullifier schemes (e.g., notes spendable by anyone with a secret, not tied to an owner) * Partial notes that can be completed in public execution * Non-standard hash computation for specific security requirements With `#[custom_note]`, you must implement the `NoteHash` trait yourself: ``` use aztec::{ context::PrivateContext, keys::getters::{get_nhk_app, get_public_keys, try_get_public_keys}, macros::notes::custom_note, note::note_interface::NoteHash, protocol::{ address::AztecAddress, constants::{DOM_SEP__NOTE_HASH, DOM_SEP__NOTE_NULLIFIER}, hash::poseidon2_hash_with_separator, traits::Packable, }, }; #[derive(Eq, Packable)] #[custom_note] pub struct CustomHashNote { pub data: Field, } impl NoteHash for CustomHashNote { fn compute_note_hash( self, owner: AztecAddress, storage_slot: Field, randomness: Field, ) -> Field { // Custom hash computation poseidon2_hash_with_separator( [self.data, owner.to_field(), storage_slot, randomness], DOM_SEP__NOTE_HASH, ) } fn compute_nullifier( self, context: &mut PrivateContext, owner: AztecAddress, note_hash_for_nullification: Field, ) -> Field { // Standard nullifier using owner's nullifier hiding key let owner_npk_m_hash = get_public_keys(owner).npk_m_hash; let secret = context.request_nhk_app(owner_npk_m_hash); poseidon2_hash_with_separator( [note_hash_for_nullification, secret], DOM_SEP__NOTE_NULLIFIER, ) } unconstrained fn compute_nullifier_unconstrained( self, owner: AztecAddress, note_hash_for_nullification: Field, ) -> Option { try_get_public_keys(owner).map(|public_keys| { let secret = get_nhk_app(public_keys.npk_m_hash); poseidon2_hash_with_separator( [note_hash_for_nullification, secret], DOM_SEP__NOTE_NULLIFIER, ) }) } } ``` Naming note The secret returned by `request_nhk_app` is the **nullifier hiding key** (abbreviated `nhk`). Older docs and code comments may call it the "nullifier secret key" (`nsk`) — these refer to the same key. Always use `request_nhk_app()` rather than computing this key yourself. ## Viewing notes (unconstrained)[​](#viewing-notes-unconstrained "Direct link to Viewing notes (unconstrained)") For read-only queries without constraints: view\_notes ``` #[external("utility")] unconstrained fn get_private_nfts(owner: AztecAddress, page_index: u32) -> ([Field; MAX_NOTES_PER_PAGE], bool) { let offset = page_index * MAX_NOTES_PER_PAGE; let options = NoteViewerOptions::new().set_offset(offset); let notes = self.storage.private_nfts.at(owner).view_notes(options); let mut owned_nft_ids = [0; MAX_NOTES_PER_PAGE]; for i in 0..options.limit { if i < notes.len() { owned_nft_ids[i] = notes.get_unchecked(i).token_id; } } let page_limit_reached = notes.len() == options.limit; (owned_nft_ids, page_limit_reached) } ``` > [Source code: noir-projects/noir-contracts/contracts/app/nft\_contract/src/main.nr#L255-L272](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr#L255-L272) ## Further reading[​](#further-reading "Direct link to Further reading") * [What the `#[note]` macro does](/developers/testnet/docs/aztec-nr/framework-description/functions/attributes.md#implementing-notes) * [Note getter options](/developers/testnet/docs/aztec-nr/framework-description/advanced/how_to_retrieve_filter_notes.md) * [Storage types](/developers/testnet/docs/foundational-topics/state_management.md) * [Macros reference](/developers/testnet/docs/aztec-nr/framework-description/macros.md) --- # Data Packing and Serialization Aztec contracts use two separate encoding schemes to convert structs into `Field` arrays. This page tells you which one to reach for and when. * **`Serialize` / `Deserialize`**: ABI encoding. Used anywhere a value crosses the contract boundary (function arguments, return values, events). The layout must match Noir's intrinsic format, so you almost never hand-roll it. * **`Packable`**: Storage encoding. Used wherever data is written to state or hashed into notes. The format is internal to your contract, so you can pack multiple small values into a single `Field` to save gas and proving time. Picking the wrong trait, or missing a chance to pack, can waste gas, storage slots, and proving time. ## When to use Serialize and Deserialize[​](#when-to-use-serialize-and-deserialize "Direct link to When to use Serialize and Deserialize") `Deserialize` is needed for any struct accepted as a function argument; `Serialize` is needed for any struct returned from a function or emitted as an event. Because args and returns often share the same struct, `#[derive(Serialize, Deserialize)]` is the convenient default. The encoding must follow Noir's intrinsic serialization: each struct member becomes one or more `Field` values, with no packing or compression. When a transaction calls a public function, TypeScript serializes the arguments into an initial witness using Noir's built-in format. If your Noir-side implementation produces a different layout, you get an "arguments hash mismatch" error. ``` // Matches Noir's intrinsic format automatically. #[derive(Serialize, Deserialize)] struct MyArgs { amount: u128, // 1 Field enabled: bool, // 1 Field owner: Field, // 1 Field } // Serialize::N = 3 (one Field per member) ``` For events, `#[event]` auto-derives `Serialize` for you. You typically do not need `Deserialize` or `Packable` on events. warning Do not hand-roll `Serialize` or `Deserialize` for types passed as function arguments. The encoding must match what TypeScript sends, and the derive macro ensures this automatically. ## When to use Packable[​](#when-to-use-packable "Direct link to When to use Packable") Use `Packable` for note `structs` when creating custom notes or as the data type of a state variable (`PublicMutable`, `PublicImmutable`, `DelayedPublicMutable`). `Packable` defines how the value is encoded when written to storage or hashed into a note. It never needs to match any external format; it only needs to roundtrip: `unpack(pack(x)) == x`. There are two cases. ### Case 1: a custom note[​](#case-1-a-custom-note "Direct link to Case 1: a custom note") `#[note]` *requires* the struct to implement `Packable` but does **not** add it for you. Place `#[derive(Packable)]` on the struct before applying `#[note]`, or the macro will fail compilation with an explicit message. When a note's members are already `Field`-sized, deriving is enough: field\_like\_note ``` // A note whose members are already Field-sized. // #[note] requires Packable; derive is sufficient here -- N = 1. #[derive(Deserialize, Eq, Packable, Serialize)] #[note] pub struct OwnerNote { pub owner: AztecAddress, } ``` > [Source code: docs/examples/contracts/packing\_example/src/types.nr#L20-L28](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/packing_example/src/types.nr#L20-L28) `Eq` is a Noir standard trait for equality comparisons (see [Noir's `Eq` trait](https://noir-lang.org/docs/noir/concepts/data_types/traits)). `#[note]` does not require it, but deriving it is idiomatic because it enables `assert_eq` in tests and note-equality checks. `Serialize` and `Deserialize` are similarly optional here, and useful when a note type crosses a function boundary. This is how the built-in note types work too: [`AddressNote`](/aztec-nr-api/testnet/address_note/struct.AddressNote) and [`FieldNote`](/aztec-nr-api/testnet/field_note/struct.FieldNote) both derive `Packable` directly because their members are already `Field` or `AztecAddress`. When a note has members smaller than a `Field` (`bool`, `u8`, `u32`, `u64`), you can skip the `Packable` derive and write a custom implementation that packs multiple values into a single `Field`: card\_note ``` // A note with two u32 members. Derived Packable would give N = 2; // a custom impl halves that to N = 1, reducing note-hash inputs. #[derive(Eq)] #[note] pub struct CardNote { pub strength: u32, pub points: u32, } impl Packable for CardNote { let N: u32 = 1; fn pack(self) -> [Field; Self::N] { [(self.strength as Field) * 2.pow_32(32) + (self.points as Field)] } fn unpack(packed: [Field; Self::N]) -> Self { let points = packed[0] as u32; let strength = ((packed[0] - points as Field) / 2.pow_32(32)) as u32; Self { strength, points } } } ``` > [Source code: docs/examples/contracts/packing\_example/src/types.nr#L30-L53](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/packing_example/src/types.nr#L30-L53) Derived `Packable` would give `CardNote` an `N = 2`; the custom impl halves that to `N = 1`. Smaller `N` means fewer inputs to the note hash, which directly reduces the gate count of private functions. ### Case 2: a struct used as a state variable's data type[​](#case-2-a-struct-used-as-a-state-variables-data-type "Direct link to Case 2: a struct used as a state variable's data type") Primitive types (`bool`, `u8` through `u128`, `Field`, `AztecAddress`) already implement `Packable`, so `PublicMutable` or `PublicImmutable` works out of the box. This section applies when the data type is a user-defined struct, for example `PublicMutable`. `#[storage]` requires every state variable's data type to implement `Packable`, but it does not add it for you. Put `#[derive(Packable)]` on the struct yourself. note `PublicImmutable` and `DelayedPublicMutable` also require `T: Eq`, because they verify stored values against a hash. Add `Eq` to the derive list (`#[derive(Eq, Packable)]`) for structs used in these state variables. `PublicMutable` only needs `Packable`. When all members are already `Field`-sized (`Field`, `AztecAddress`, and types built from them), deriving is sufficient: derived\_packable ``` // When all members are Field-sized, derive(Packable) is sufficient. // Each member gets its own Field -- no packing benefit, but no manual work needed. #[derive(Eq, Packable, Serialize)] pub struct ServerConfig { pub admin: AztecAddress, pub token: AztecAddress, pub max_supply: Field, } // N = 3 (one Field per member) ``` > [Source code: docs/examples/contracts/packing\_example/src/types.nr#L8-L18](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/packing_example/src/types.nr#L8-L18) For [`PublicMutable`](/aztec-nr-api/testnet/noir_aztec/state_vars/struct.PublicMutable), each element of the packed `[Field; N]` array maps directly to one `SLOAD` on read and one `SSTORE` on write, so a smaller `N` is a direct gas saving. [`PublicImmutable`](/aztec-nr-api/testnet/noir_aztec/state_vars/struct.PublicImmutable) and [`DelayedPublicMutable`](/aztec-nr-api/testnet/noir_aztec/state_vars/struct.DelayedPublicMutable) have additional overhead on top (see [Cost impact](#cost-impact) below), but also benefit from a smaller `N`. When your struct has members smaller than a `Field`, deriving still uses one whole `Field` per member, which is wasteful. The next section shows how to write a manual implementation that collapses them. ## Writing a custom Packable implementation[​](#writing-a-custom-packable-implementation "Direct link to Writing a custom Packable implementation") The technique is bit-packing with powers of 2, which maps efficiently to both AVM opcodes (public functions) and proving-backend primitives (private functions). Take a `GameState` struct with a `bool` and two `u32`s. Derived `Packable` gives `N = 3`. A manual implementation collapses it to `N = 1`: ``` // Derived: N = 3 (one Field per member, no packing benefit) #[derive(Packable)] struct GameState { started: bool, // 1 bit, but uses 1 whole Field round: u32, // 32 bits, but uses 1 whole Field score: u32, // 32 bits, but uses 1 whole Field } ``` game\_state\_manual\_packable ``` // Mixed-width with a bool: started (1 bit) + round (32 bits) + score (32 bits). // Derived Packable would give N = 3; manual packing gives N = 1. #[derive(Eq, Serialize)] pub struct GameState { pub started: bool, pub round: u32, pub score: u32, } impl Packable for GameState { let N: u32 = 1; fn pack(self) -> [Field; Self::N] { // Layout within a single Field: // [ started (1 bit) | round (32 bits) | score (32 bits) ] // bit 64 bits 32..63 bits 0..31 [ (self.started as Field) * 2.pow_32(64) // shift left by 64 bits + (self.round as Field) * 2.pow_32(32) // shift left by 32 bits + (self.score as Field), // lowest bits ] } fn unpack(packed: [Field; Self::N]) -> Self { // 1. Extract lowest value via truncating cast let score = packed[0] as u32; // 2. Subtract and shift right to get next value let round = ((packed[0] - score as Field) / 2.pow_32(32)) as u32; // 3. Subtract and shift right to get highest value. // Bools are extracted by comparing the resulting Field to 0. let started = ((packed[0] - score as Field - (round as Field) * 2.pow_32(32)) / 2.pow_32(64)) != 0; Self { started, round, score } } } ``` > [Source code: docs/examples/contracts/packing\_example/src/types.nr#L112-L156](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/packing_example/src/types.nr#L112-L156) `N = 1` means every storage read or write uses one `SLOAD` / `SSTORE` instead of three. The rest of this section walks through how the implementation was built. ### Step 1: Determine bit widths[​](#step-1-determine-bit-widths "Direct link to Step 1: Determine bit widths") For each member, determine how many bits it needs: | Type | Bit width | | -------------- | ---------------------------------------------- | | `bool` | 1 | | `u8` | 8 | | `u16` | 16 | | `u32` | 32 | | `u64` | 64 | | `u128` | 128 | | `Field` | up to 254 (cannot be packed with other values) | | `AztecAddress` | up to 254 (wraps a `Field`) | A `Field` element is an integer modulo the BN254 scalar field prime: ``` p = 21888242871839275222246405745257275088548364400416034343698204186575808495617 ``` This prime is \~253.58 bits (slightly less than 2^254), so not every 254-bit value is a valid `Field`. To avoid modular wrap-around when packing, keep the sum of all member bit widths **≤ 253 bits**. For example, `u128 + u64 + u32 = 224 bits` packs safely, but `2 × u128 = 256 bits` does not. ### Step 2: Pack by multiplying with powers of 2[​](#step-2-pack-by-multiplying-with-powers-of-2 "Direct link to Step 2: Pack by multiplying with powers of 2") `2.pow_32(k)` computes `2^k`, which is equivalent to a left shift by `k` bits for integer-valued `Field`s. Add the shifted values together to concatenate them inside a single `Field`: game\_state\_pack ``` fn pack(self) -> [Field; Self::N] { // Layout within a single Field: // [ started (1 bit) | round (32 bits) | score (32 bits) ] // bit 64 bits 32..63 bits 0..31 [ (self.started as Field) * 2.pow_32(64) // shift left by 64 bits + (self.round as Field) * 2.pow_32(32) // shift left by 32 bits + (self.score as Field), // lowest bits ] } ``` > [Source code: docs/examples/contracts/packing\_example/src/types.nr#L125-L136](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/packing_example/src/types.nr#L125-L136) The bit layout you choose is arbitrary; the only requirement is that `pack` and `unpack` agree. The example above places `score` (a `u32`) at the lowest bits because a truncating cast (`packed[0] as u32`) then extracts it for free, with no subtraction or division. When you have a member whose width matches a standard `uN` type, putting it in the lowest position makes `unpack` cleaner. The remaining members can be placed in any order above it. ### Step 3: Unpack by extracting from lowest bits upward[​](#step-3-unpack-by-extracting-from-lowest-bits-upward "Direct link to Step 3: Unpack by extracting from lowest bits upward") Extract values from lowest bits first, to match how we packed them, subtracting each extracted value before extracting the next: game\_state\_unpack ``` fn unpack(packed: [Field; Self::N]) -> Self { // 1. Extract lowest value via truncating cast let score = packed[0] as u32; // 2. Subtract and shift right to get next value let round = ((packed[0] - score as Field) / 2.pow_32(32)) as u32; // 3. Subtract and shift right to get highest value. // Bools are extracted by comparing the resulting Field to 0. let started = ((packed[0] - score as Field - (round as Field) * 2.pow_32(32)) / 2.pow_32(64)) != 0; Self { started, round, score } } ``` > [Source code: docs/examples/contracts/packing\_example/src/types.nr#L138-L154](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/packing_example/src/types.nr#L138-L154) ### Step 4: Write roundtrip tests[​](#step-4-write-roundtrip-tests "Direct link to Step 4: Write roundtrip tests") Always test that `unpack(pack(x)) == x` for boundary values: game\_state\_tests ``` #[test] fn test_game_state_pack_unpack() { let state = GameState { started: true, round: 42, score: 1000 }; let unpacked = GameState::unpack(state.pack()); assert_eq(unpacked.started, state.started); assert_eq(unpacked.round, state.round); assert_eq(unpacked.score, state.score); } #[test] fn test_game_state_pack_unpack_max() { let state = GameState { started: true, round: 0xffffffff, score: 0xffffffff }; let unpacked = GameState::unpack(state.pack()); assert_eq(unpacked.started, state.started); assert_eq(unpacked.round, state.round); assert_eq(unpacked.score, state.score); } ``` > [Source code: docs/examples/contracts/packing\_example/src/types.nr#L204-L223](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/packing_example/src/types.nr#L204-L223) ## More custom Packable examples[​](#more-custom-packable-examples "Direct link to More custom Packable examples") ### Packing two u32 values into one Field[​](#packing-two-u32-values-into-one-field "Direct link to Packing two u32 values into one Field") card\_custom\_packable ``` // Two u32 values packed into a single Field. // Derived Packable would give N = 2; manual packing gives N = 1. #[derive(Deserialize, Eq, Serialize)] pub struct Card { pub strength: u32, pub points: u32, } impl Packable for Card { let N: u32 = 1; fn pack(self) -> [Field; Self::N] { [(self.strength as Field) * 2.pow_32(32) + (self.points as Field)] } fn unpack(packed: [Field; Self::N]) -> Self { let points = packed[0] as u32; let strength = ((packed[0] - points as Field) / 2.pow_32(32)) as u32; Self { strength, points } } } ``` > [Source code: docs/examples/contracts/packing\_example/src/types.nr#L55-L77](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/packing_example/src/types.nr#L55-L77) With derived `Packable`, `Card` would have `N = 2`. The manual implementation achieves `N = 1`, halving the storage cost. ### Packing mixed-width integers[​](#packing-mixed-width-integers "Direct link to Packing mixed-width integers") mixed\_width\_packable ``` // Mixed-width integers: a u128 and a u64 packed into one Field, // plus an AztecAddress that takes a full Field on its own. // Derived Packable would give N = 3; manual packing gives N = 2. #[derive(Eq, Serialize)] pub struct GameConfig { pub interest_accumulator: u128, pub last_updated_ts: u64, pub admin: AztecAddress, } impl Packable for GameConfig { let N: u32 = 2; fn pack(self) -> [Field; Self::N] { [ // u128 (128 bits) + u64 (64 bits) = 192 bits, fits in one Field (self.interest_accumulator as Field) * 2.pow_32(64) + (self.last_updated_ts as Field), self.admin.to_field(), ] } fn unpack(packed: [Field; Self::N]) -> Self { let last_updated_ts = packed[0] as u64; let interest_accumulator = ((packed[0] - last_updated_ts as Field) / 2.pow_32(64)) as u128; let admin = AztecAddress::from_field(packed[1]); Self { interest_accumulator, last_updated_ts, admin } } } ``` > [Source code: docs/examples/contracts/packing\_example/src/types.nr#L79-L110](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/packing_example/src/types.nr#L79-L110) A `u128` and a `u64` pack into a single `Field` (128 + 64 = 192 bits, well within the 253-bit safe limit), while the `AztecAddress` occupies a full `Field` on its own. This reduces `N` from 3 to 2. ### Roundtrip tests[​](#roundtrip-tests "Direct link to Roundtrip tests") Always test custom `Packable` implementations at boundary values: pack\_unpack\_tests ``` mod test { use super::{AztecAddress, Card, FromField, GameConfig, GameState, Packable}; #[test] fn test_card_pack_unpack() { let card = Card { strength: 42, points: 100 }; let unpacked = Card::unpack(card.pack()); assert_eq(unpacked.strength, card.strength); assert_eq(unpacked.points, card.points); } #[test] fn test_card_pack_unpack_max() { let card = Card { strength: 0xffffffff, points: 0xffffffff }; let unpacked = Card::unpack(card.pack()); assert_eq(unpacked.strength, card.strength); assert_eq(unpacked.points, card.points); } #[test] fn test_config_pack_unpack() { let config = GameConfig { interest_accumulator: 1000000, last_updated_ts: 1700000000, admin: AztecAddress::from_field(0xabcdef), }; let unpacked = GameConfig::unpack(config.pack()); assert_eq(unpacked.interest_accumulator, config.interest_accumulator); assert_eq(unpacked.last_updated_ts, config.last_updated_ts); assert(unpacked.admin.eq(config.admin)); } #[test] fn test_config_pack_unpack_max() { let config = GameConfig { interest_accumulator: 0xffffffffffffffffffffffffffffffff, last_updated_ts: 0xffffffffffffffff, admin: AztecAddress::from_field(0xabcdef), }; let unpacked = GameConfig::unpack(config.pack()); assert_eq(unpacked.interest_accumulator, config.interest_accumulator); assert_eq(unpacked.last_updated_ts, config.last_updated_ts); assert(unpacked.admin.eq(config.admin)); } #[test] fn test_game_state_pack_unpack() { let state = GameState { started: true, round: 42, score: 1000 }; let unpacked = GameState::unpack(state.pack()); assert_eq(unpacked.started, state.started); assert_eq(unpacked.round, state.round); assert_eq(unpacked.score, state.score); } #[test] fn test_game_state_pack_unpack_max() { let state = GameState { started: true, round: 0xffffffff, score: 0xffffffff }; let unpacked = GameState::unpack(state.pack()); assert_eq(unpacked.started, state.started); assert_eq(unpacked.round, state.round); assert_eq(unpacked.score, state.score); } } ``` > [Source code: docs/examples/contracts/packing\_example/src/types.nr#L158-L225](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/packing_example/src/types.nr#L158-L225) ## Cost impact[​](#cost-impact "Direct link to Cost impact") ### Public storage[​](#public-storage "Direct link to Public storage") Each state variable type has a different storage-op profile, but all of them scale with `N`, the length of the packed `[Field; N]` array for the data type `T`. Reducing `N` reduces cost for every type. If a struct has `Packable::N = 4` with derived packing but could be manually packed to `N = 2`, you halve the public `SLOAD` / `SSTORE` count on every read and write. ### Note hashing (private state)[​](#note-hashing-private-state "Direct link to Note hashing (private state)") Note hashes are computed with Poseidon2 over the packed note data along with the storage slot, owner, and randomness. Fewer packed fields means fewer inputs to the hash, which directly reduces the gate count of private functions. ### Calldata (function arguments)[​](#calldata-function-arguments "Direct link to Calldata (function arguments)") Function arguments use `Serialize`, not `Packable`. The number of fields in calldata affects L2 gas for deserialization. While you cannot change the encoding format (it must match TypeScript), you can reduce calldata size by restructuring your function signatures to pass fewer, larger arguments. ## When is custom packing worth it?[​](#when-is-custom-packing-worth-it "Direct link to When is custom packing worth it?") Custom packing is worth the effort when: * Your struct has **multiple sub-Field members** (bools, small integers) stored in a state variable or used in notes. * The struct is **read or written frequently** (for example, game state updated every turn). * You are hitting **gas limits** due to storage-heavy transactions. Custom packing is not needed when: * All struct members are `Field` or `AztecAddress` (already one Field each, no packing opportunity). * The struct is used only as a function argument (must use `Serialize`, not `Packable`). * The struct is small and accessed rarely. ## Reference: which macros auto-derive which traits[​](#reference-which-macros-auto-derive-which-traits "Direct link to Reference: which macros auto-derive which traits") Aztec's macros only add a derive when the role of the struct strictly requires it. Everything else is on the developer. | Macro | Auto-derives `Serialize` | Auto-derives `Deserialize` | Auto-derives `Packable` | | -------------------------- | ---------------------------- | -------------------------- | ----------------------------------------------------------------------------------------- | | `#[event]` | Yes (if not already present) | No | No | | `#[authorization]` | Yes (if not already present) | No | No | | `#[note]` | No | No | No (but **requires** `Packable` to be implemented; the macro fails compilation otherwise) | | `#[storage]` | No | No | No (state variable data types must implement `Packable` themselves) | | `#[aztec]` / `#[contract]` | No | No | No | Implications: * **Function argument types** (both `#[public]` and `#[private]`): add `#[derive(Serialize, Deserialize)]` yourself. * **Event types**: `#[event]` covers `Serialize`. You typically do not need `Deserialize` or `Packable` on events. * **Note types**: place `#[derive(Packable)]` on the struct *before* `#[note]`, unless you are providing a manual `impl Packable`. * **State variable data types**: every type used inside `PublicMutable`, `PublicImmutable`, `DelayedPublicMutable`, notes, etc. must implement `Packable`. `#[storage]` does not add this for you. ## Summary[​](#summary "Direct link to Summary") * Use `#[derive(Serialize, Deserialize)]` for function arguments and return values. `#[event]` handles events. * Use `#[derive(Packable)]` (or a manual impl) for note structs and state variable data types. `#[note]` requires `Packable` and will not add it for you. * When a struct has multiple sub-`Field` members and is accessed frequently, manually implement `Packable` to pack values together using `2.pow_32()`. * Keep the total packed bit width at or below 253 bits to stay within the BN254 field modulus. * Always write roundtrip tests (`unpack(pack(x)) == x`) for custom implementations. * Gas savings scale linearly with the reduction in `N`: halving `N` halves your storage operations. --- # Aztec.nr Dependencies This page lists the available Aztec.nr libraries. Add dependencies to the `[dependencies]` section of your `Nargo.toml`: ``` [dependencies] aztec = { git="https://github.com/AztecProtocol/aztec-nr/", tag="v5.0.0-rc.2", directory="aztec" } # Add other libraries as needed ``` ## Core[​](#core "Direct link to Core") ### Aztec (required)[​](#aztec-required "Direct link to Aztec (required)") ``` aztec = { git="https://github.com/AztecProtocol/aztec-nr/", tag="v5.0.0-rc.2", directory="aztec" } ``` The core Aztec library required for every Aztec.nr smart contract. ## Note Types[​](#note-types "Direct link to Note Types") ### Address Note[​](#address-note "Direct link to Address Note") ``` address_note = { git="https://github.com/AztecProtocol/aztec-nr/", tag="v5.0.0-rc.2", directory="address-note" } ``` Provides `AddressNote`, a note type for storing `AztecAddress` values. ### Field Note[​](#field-note "Direct link to Field Note") ``` field_note = { git="https://github.com/AztecProtocol/aztec-nr/", tag="v5.0.0-rc.2", directory="field-note" } ``` Provides `FieldNote`, a note type for storing a single `Field` value. ### Uint Note[​](#uint-note "Direct link to Uint Note") ``` uint_note = { git="https://github.com/AztecProtocol/aztec-nr/", tag="v5.0.0-rc.2", directory="uint-note" } ``` Provides `UintNote`, a note type for storing `u128` values. Also includes `PartialUintNote` for partial note workflows where the value is completed in public execution. ## State Variables[​](#state-variables "Direct link to State Variables") ### Balance Set[​](#balance-set "Direct link to Balance Set") ``` balance_set = { git="https://github.com/AztecProtocol/aztec-nr/", tag="v5.0.0-rc.2", directory="balance-set" } ``` Provides `BalanceSet`, a state variable for managing private balances. Includes helper functions for adding, subtracting, and querying balances. ## Utilities[​](#utilities "Direct link to Utilities") ### Compressed String[​](#compressed-string "Direct link to Compressed String") ``` compressed_string = { git="https://github.com/AztecProtocol/aztec-nr/", tag="v5.0.0-rc.2", directory="compressed-string" } ``` Provides `CompressedString` and `FieldCompressedString` utilities for working with compressed string data. ## Updating your aztec dependencies[​](#updating-your-aztec-dependencies "Direct link to Updating your aztec dependencies") When `aztec compile` warns that your aztec dependency tag does not match the CLI version, update the `tag` field in every Aztec.nr entry in your `Nargo.toml` to match the CLI version you are running. For example, if your CLI is `vv5.0.0-rc.2`, change: ``` aztec = { git="https://github.com/AztecProtocol/aztec-nr/", tag="v", directory="aztec" } ``` to: ``` aztec = { git="https://github.com/AztecProtocol/aztec-nr/", tag="vv5.0.0-rc.2", directory="aztec" } ``` Repeat for every other Aztec.nr dependency in your `Nargo.toml` (e.g. `address_note`, `balance_set`, etc.). You can check your current CLI version with `aztec --version`. --- # Ethereum<>Aztec Messaging This guide covers cross-chain communication between Ethereum (L1) and Aztec (L2) using portal contracts. Aztec uses an Inbox/Outbox pattern for cross-chain messaging. Messages sent from L1 are inserted into the `Inbox` contract and later consumed on L2. Messages sent from L2 are inserted into the `Outbox` contract and later consumed on L1. Portal contracts are L1 contracts that facilitate this communication for your application. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * An Aztec contract project with `aztec-nr` dependency * Access to Ethereum development environment for L1 contracts * Deployed portal contract on L1 (see [token bridge tutorial](/developers/testnet/docs/tutorials/js_tutorials/token_bridge.md)) ## L1 to L2 messaging[​](#l1-to-l2-messaging "Direct link to L1 to L2 messaging") ### Send a message from L1[​](#send-a-message-from-l1 "Direct link to Send a message from L1") Use the `Inbox` contract's `sendL2Message` function: | Parameter | Type | Description | | ------------- | --------- | -------------------------------------------------- | | `_recipient` | `L2Actor` | L2 contract address and rollup version | | `_content` | `bytes32` | Hash of message content (use `Hash.sha256ToField`) | | `_secretHash` | `bytes32` | Hash of secret for message consumption | deposit\_public ``` /** * @notice Deposit funds into the portal and adds an L2 message which can only be consumed publicly on Aztec * @param _to - The aztec address of the recipient * @param _amount - The amount to deposit * @param _secretHash - The hash of the secret consumable message. The hash should be 254 bits (so it can fit in a * Field element) * @return The key of the entry in the Inbox and its leaf index */ function depositToAztecPublic(bytes32 _to, uint256 _amount, bytes32 _secretHash) external returns (bytes32, uint256) ``` > [Source code: l1-contracts/test/portals/TokenPortal.sol#L48-L60](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/l1-contracts/test/portals/TokenPortal.sol#L48-L60) Message availability L1 to L2 messages are not available immediately. The proposer batches messages from the Inbox and includes them in the next L2 block. You must wait for this before consuming the message on L2. ### Consume the message on L2[​](#consume-the-message-on-l2 "Direct link to Consume the message on L2") Call `consume_l1_to_l2_message` on the context. The `content` must match the hash sent from L1, and the `secret` must be the pre-image of the `secretHash`. Consuming a message emits a nullifier to prevent double-spending. The content hash must be computed identically on both L1 and L2. Create a shared library for your content hash functions—see [`token_portal_content_hash_lib`](https://github.com/AztecProtocol/aztec-packages/tree/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/token_portal_content_hash_lib) for an example. claim\_public ``` // Consumes a L1->L2 message and calls the token contract to mint the appropriate amount publicly #[external("public")] fn claim_public(to: AztecAddress, amount: u128, secret: Field, message_leaf_index: Field) { let content_hash = get_mint_to_public_content_hash(to, amount); let config = self.storage.config.read(); // Consume message and emit nullifier self.context.consume_l1_to_l2_message(content_hash, secret, config.portal, message_leaf_index); // Mint tokens self.call(Token::at(config.token).mint_to_public(to, amount)); } ``` > [Source code: noir-projects/noir-contracts/contracts/app/token\_bridge\_contract/src/main.nr#L49-L63](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/token_bridge_contract/src/main.nr#L49-L63) This function works in both public and private contexts. ## L2 to L1 messaging[​](#l2-to-l1-messaging "Direct link to L2 to L1 messaging") ### Send a message from L2[​](#send-a-message-from-l2 "Direct link to Send a message from L2") Call `message_portal` on the context to send messages to your L1 portal: exit\_to\_l1\_public ``` // Burns the appropriate amount of tokens and creates a L2 to L1 withdraw message publicly // Requires `msg.sender` to give approval to the bridge to burn tokens on their behalf using witness signatures #[external("public")] fn exit_to_l1_public( recipient: EthAddress, // ethereum address to withdraw to amount: u128, caller_on_l1: EthAddress, // ethereum address that can call this function on the L1 portal (0x0 if anyone can // call) authwit_nonce: Field, // nonce used in the approval message by `msg.sender` to let bridge burn their tokens on // L2 ) { let config = self.storage.config.read(); // Send an L2 to L1 message let content = get_withdraw_content_hash(recipient, amount, caller_on_l1); self.context.message_portal(config.portal, content); // Burn tokens self.call(Token::at(config.token).burn_public(self.msg_sender(), amount, authwit_nonce)); } ``` > [Source code: noir-projects/noir-contracts/contracts/app/token\_bridge\_contract/src/main.nr#L65-L86](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/token_bridge_contract/src/main.nr#L65-L86) This function works in both public and private contexts. ### Consume the message on L1[​](#consume-the-message-on-l1 "Direct link to Consume the message on L1") Use the `Outbox` contract to consume L2 messages. Message availability L2 to L1 messages are only available after the epoch proof is submitted to L1. Since multiple L2 blocks fit within an epoch, there may be a delay—especially if the message was sent near the start of an epoch. token\_portal\_withdraw ``` /** * @notice Withdraw funds from the portal * @dev Second part of withdraw, must be initiated from L2 first as it will consume a message from outbox * @param _recipient - The address to send the funds to * @param _amount - The amount to withdraw * @param _withCaller - Flag to use `msg.sender` as caller, otherwise address(0) * @param _epoch - The epoch the message is in * @param _numCheckpointsInEpoch - The number of checkpoints in the partial proof whose root this * consume verifies against * @param _leafIndex - The index of the leaf in the epoch message tree * @param _path - The sibling path proving inclusion of the message in the epoch's root * Must match the caller of the message (specified from L2) to consume it. */ function withdraw( address _recipient, uint256 _amount, bool _withCaller, Epoch _epoch, uint256 _numCheckpointsInEpoch, uint256 _leafIndex, bytes32[] calldata _path ) external { // The purpose of including the function selector is to make the message unique to that specific call. Note that // it has nothing to do with calling the function. DataStructures.L2ToL1Msg memory message = DataStructures.L2ToL1Msg({ sender: DataStructures.L2Actor(l2Bridge, rollupVersion), recipient: DataStructures.L1Actor(address(this), block.chainid), content: Hash.sha256ToField( abi.encodeWithSignature( "withdraw(address,uint256,address)", _recipient, _amount, _withCaller ? msg.sender : address(0) ) ) }); outbox.consume(message, _epoch, _numCheckpointsInEpoch, _leafIndex, _path); underlying.safeTransfer(_recipient, _amount); } ``` > [Source code: l1-contracts/test/portals/TokenPortal.sol#L112-L151](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/l1-contracts/test/portals/TokenPortal.sol#L112-L151) Getting the membership witness Compute the witness for the L2 to L1 message in TypeScript: ``` import { computeL2ToL1MessageHash } from "@aztec/stdlib/hash"; const l2ToL1Message = computeL2ToL1MessageHash({ l2Sender: l2BridgeAddress, l1Recipient: EthAddress.fromString(portalAddress), content: withdrawContentHash, rollupVersion: new Fr(version), chainId: new Fr(chainId), }); const witness = await aztecNode.getL2ToL1MembershipWitness( txReceipt.txHash, l2ToL1Message ); // Use witness.leafIndex and witness.siblingPath for the L1 consume call ``` ## Example implementations[​](#example-implementations "Direct link to Example implementations") * [Token Portal (L1)](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/l1-contracts/test/portals/TokenPortal.sol) * [Token Bridge (L2)](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/token_bridge_contract/src/main.nr) ## Next steps[​](#next-steps "Direct link to Next steps") Follow the [token bridge tutorial](/developers/testnet/docs/tutorials/js_tutorials/token_bridge.md) for a complete implementation example. --- # Events and Logs Events allow contracts to communicate with offchain applications. Private events are encrypted and delivered to specific recipients, while public events are visible to everyone. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * An Aztec contract project set up with `aztec-nr` dependency * Understanding of private vs public functions in Aztec ## Define an event[​](#define-an-event "Direct link to Define an event") Declare events using the `#[event]` attribute: ``` #[event] struct Transfer { from: AztecAddress, to: AztecAddress, amount: u128, } ``` ## Emit private events[​](#emit-private-events "Direct link to Emit private events") In private functions, emit events using `self.emit()` and deliver them to recipients: ``` use aztec::messages::delivery::MessageDelivery; #[external("private")] fn transfer(to: AztecAddress, amount: u128) { let from = self.msg_sender(); // ... transfer logic ... self.emit(Transfer { from, to, amount }).deliver_to( to, MessageDelivery::onchain_unconstrained(), ); } ``` warning You **must** call `deliver_to()` on the returned `EventMessage`. If you don't, the event information is lost forever. The compiler will warn you about unused `EventMessage` values. ### Deliver to multiple recipients[​](#deliver-to-multiple-recipients "Direct link to Deliver to multiple recipients") You can deliver the same event to multiple recipients with different delivery modes: ``` let message = self.emit(Transfer { from, to, amount }); message.deliver_to(from, MessageDelivery::offchain()); message.deliver_to(to, MessageDelivery::onchain_constrained()); ``` The `MessageDelivery` options are: * **`onchain_constrained()`** - Constrained encryption with onchain delivery. Slowest proving but provides cryptographic guarantees that recipients can decrypt messages. * **`onchain_unconstrained()`** - Unconstrained encryption with onchain delivery. Faster proving, but trusts the sender to encrypt correctly. * **`offchain()`** - Unconstrained encryption with offchain delivery. Lowest cost, but requires custom infrastructure to deliver messages to recipients. note Emitting private events is optional. Onchain delivery publishes encrypted data to Ethereum blobs, inheriting Ethereum's data availability guarantees. You can choose to share information offchain instead. ## Emit public events[​](#emit-public-events "Direct link to Emit public events") In public functions, emit events using `self.emit()`: ``` #[external("public")] fn update_value(value: Field) { // ... update logic ... self.emit(ValueUpdated { value }); } ``` Public events are emitted as plaintext logs, similar to Solidity events. ## Emit unstructured public logs[​](#emit-unstructured-public-logs "Direct link to Emit unstructured public logs") For unstructured data, use `emit_public_log_unsafe` directly on the context. It takes a tag (placed at the first field of the emitted log, which nodes use to index logs) followed by the data: ``` self.context.emit_public_log_unsafe(0, "My message"); self.context.emit_public_log_unsafe(0, [1, 2, 3]); ``` The tag should be domain-separated to prevent collisions with unrelated log types. Prefer `self.emit(event)` where possible, which handles tagging automatically. ## Query public logs[​](#query-public-logs "Direct link to Query public logs") Query public logs from offchain applications using the Aztec node. Raw public logs are attached to each block's transaction effects — fetch a block with `includeTransactions: true` and read `body.txEffects[*].publicLogs`: ``` const blockNumber = await node.getBlockNumber(); const block = await node.getBlock(blockNumber, { includeTransactions: true }); const publicLogs = block?.body.txEffects.flatMap(tx => tx.publicLogs) ?? []; ``` ## Cost considerations[​](#cost-considerations "Direct link to Cost considerations") Event data published onchain is stored in Ethereum blobs, which incurs costs. Consider: * Use `OFFCHAIN` delivery for lower costs when you have custom delivery infrastructure * Only emit events when necessary for your application's functionality ## Next steps[​](#next-steps "Direct link to Next steps") * Learn about [storage](/developers/testnet/docs/aztec-nr/framework-description/state_variables.md) to persist data in your contracts * Explore [calling other contracts](/developers/testnet/docs/aztec-nr/framework-description/calling_contracts.md) for cross-contract interactions * Understand [cross-chain communication](/developers/testnet/docs/aztec-nr/framework-description/ethereum_aztec_messaging.md) between Ethereum and Aztec --- # Defining Functions Functions serve as the building blocks of smart contracts. Functions can be either **public**, ie they are publicly available for anyone to see and can directly interact with public state, or **private**, meaning they are executed completely client-side in the [PXE](/developers/testnet/docs/foundational-topics/pxe.md). Read more about how private functions work [here](/developers/testnet/docs/aztec-nr/framework-description/functions/attributes.md#private-functions-externalprivate). Currently, any function is "mutable" in the sense that it might alter state. However, we also support static calls, similarly to EVM. A static call is essentially a call that does not alter state (it keeps state static). ## Initializer functions[​](#initializer-functions "Direct link to Initializer functions") Smart contracts may have one, or many, initializer functions which are called when the contract is deployed. Initializers are regular functions that set an "initialized" flag (a nullifier) for the contract. A contract can only be initialized once, and contract functions can only be called after the contract has been initialized, much like a constructor. However, if a contract defines no initializers, it can be called at any time. Additionally, you can define as many initializer functions in a contract as you want, both private and public. ## Oracles[​](#oracles "Direct link to Oracles") There are also special oracle functions, which can get data from outside of the smart contract. In the context of Aztec, oracles are often used to get user-provided inputs. ## Learn more about functions[​](#learn-more-about-functions "Direct link to Learn more about functions") * [How function visibility works in Aztec](/developers/testnet/docs/aztec-nr/framework-description/functions/visibility.md) * How to write an [initializer function](/developers/testnet/docs/aztec-nr/framework-description/functions/how_to_define_functions.md#define-initializer-functions) * [Oracles](/developers/testnet/docs/aztec-nr/framework-description/advanced/protocol_oracles.md) and how Aztec smart contracts might use them * [How functions work under the hood](/developers/testnet/docs/aztec-nr/framework-description/functions/attributes.md) Find a function macros reference [here](/developers/testnet/docs/aztec-nr/framework-description/macros.md) --- # Attributes and Macros This page documents the attributes (macros) available in Aztec.nr for defining contract functions, storage, and notes. ## Quick reference[​](#quick-reference "Direct link to Quick reference") | Attribute | Applies to | Purpose | | ------------------------ | ---------- | ----------------------------------------------------------------- | | `#[external("private")]` | functions | Client-side private execution with proofs | | `#[external("public")]` | functions | Sequencer-side public execution | | `#[external("utility")]` | functions | Unconstrained queries, not included in transactions | | `#[internal("private")]` | functions | Private helper functions, inlined at call sites | | `#[internal("public")]` | functions | Public helper functions, inlined at call sites | | `#[view]` | functions | Prevents state modification | | `#[initializer]` | functions | Contract constructor | | `#[noinitcheck]` | functions | Callable before contract initialization | | `#[allow_phase_change]` | functions | Allows for phase change to happen during the function's execution | | `#[only_self]` | functions | Only callable by the same contract | | `#[authorize_once]` | functions | Requires authwit authorization with replay protection | | `#[note]` | structs | Defines a private note type | | `#[custom_note]` | structs | Defines a note with custom hash/nullifier logic | | `#[storage]` | structs | Defines contract storage layout | | `#[storage_no_init]` | structs | Storage with manual slot allocation | For macro internals, see the [macros reference](/developers/testnet/docs/aztec-nr/framework-description/macros.md). # External functions #\[external("...")] Like in Solidity, external functions can be called from outside the contract. There are 3 types of external functions differing in the execution environment they are executed in: private, public, and utility. We will describe each type in the following sections. ## Private functions #\[external("private")][​](#private-functions-externalprivate "Direct link to Private functions #\[external(\"private\")]") A private function operates on private information, and is executed by the user on their device. Annotate the function with the `#[external("private")]` attribute to tell the compiler it's a private function. This will make the [private context](/developers/testnet/docs/aztec-nr/framework-description/functions/context.md#the-private-context) available within the function's execution scope. The compiler will create a circuit to define this function. `#[external("private")]` is just syntactic sugar. At compile time, the Aztec.nr framework inserts code that allows the function to interact with the [kernel](/developers/testnet/docs/foundational-topics/advanced/circuits/private_kernel.md). If you are interested in what exactly the macros are doing we encourage you to run `aztec-nargo expand` on your contract. This will display your contract's code after the transformations are performed. (If you are using VSCode you can display the expanded code by pressing `CMD + Shift + P` and typing `nargo expand` and selecting `Noir: nargo expand on current package`. Make sure the Noir extension's `Nargo Path` is set to `aztec-nargo` — see the [Noir VSCode extension guide](/developers/testnet/docs/aztec-nr/installation.md) for setup.) Under the hood, the macro: * Creates a `PrivateContext` from kernel-provided inputs (chain ID, block data, etc.) * Initializes the `self` object with context and storage * Hashes function inputs for the kernel (enabling variable argument counts) * Returns execution results via `PrivateCircuitPublicInputs` (nullifiers, messages, return values) ## Utility functions #\[external("utility")][​](#utility-functions-externalutility "Direct link to Utility functions #\[external(\"utility\")]") Utility functions perform state queries from an offchain client and are never included in transactions. They can access both private and public state, and can modify local PXE state (e.g., processing logs). Since execution is unconstrained and relies on [oracle calls](https://noir-lang.org/docs/explainers/explainer-oracle), no guarantees are made on result correctness. A reasonable mental model is a Solidity `view` function that can only be invoked via `eth_call`, never in a transaction. Unlike Solidity `view` functions, utility functions can also modify local offchain PXE state. balance\_of\_private ``` #[external("utility")] unconstrained fn balance_of_private(owner: AztecAddress) -> u128 { self.storage.balances.at(owner).balance_of() } ``` > [Source code: noir-projects/noir-contracts/contracts/app/token\_contract/src/main.nr#L489-L494](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L489-L494) info Utility functions can access both private and historical public data since they're not part of transactions—there's no risk of using stale or unverified state. ## Public functions #\[external("public")][​](#public-functions-externalpublic "Direct link to Public functions #\[external(\"public\")]") A public function is executed by the sequencer and has access to a state model that is very similar to that of the EVM and Ethereum. Even though they work in an EVM-like model for public transactions, they are able to write data into private storage that can be consumed later by a private function. note All data inserted into private storage from a public function will be publicly viewable (not private). To create a public function you can annotate it with the `#[external("public")]` attribute. This will make the public context available within the function's execution scope. set\_minter ``` #[external("public")] fn set_minter(minter: AztecAddress, approve: bool) { assert(self.storage.admin.read().eq(self.msg_sender()), "caller is not admin"); self.storage.minters.at(minter).write(approve); } ``` > [Source code: noir-projects/noir-contracts/contracts/app/token\_contract/src/main.nr#L139-L145](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L139-L145) Under the hood, the macro: * Creates a `PublicContext` object that provides access to public state and transaction information * Initializes the storage struct if one is defined * Wraps the function body in a scope that handles context setup and return values * Marks the function as `pub` and `unconstrained`, meaning it doesn't generate proofs and is executed directly by the sequencer To see the exact generated code, run `aztec-nargo expand` on your contract. ## Constrained `view` Functions #\[view][​](#constrained-view-functions-view "Direct link to constrained-view-functions-view") The `#[view]` attribute can be applied to a `#[external("private")]` or a `#[external("public")]` function and it guarantees that the function cannot modify any contract state (just like `view` functions in Solidity). ## `Initializer` Functions #\[initializer][​](#initializer-functions-initializer "Direct link to initializer-functions-initializer") This is used to designate functions as initializers (or constructors) for an Aztec contract. These functions are responsible for setting up the initial state of the contract when it is first deployed. The macro does two important things: * `assert_initialization_matches_address_preimage(context)`: This checks that the arguments and sender to the initializer match the commitments from the address preimage * `mark_as_initialized(&mut context)`: This is called at the end of the function to emit the initialization nullifier, marking the contract as fully initialized and ensuring this function cannot be called again Key things to keep in mind: * A contract can have multiple initializer functions defined, but only one initializer function should be called for the lifetime of a contract instance * Other functions in the contract will have an initialization check inserted, ie they cannot be called until the contract is initialized, unless they are marked with [`#[noinitcheck]`](#noinitcheck) ## #\[noinitcheck][​](#noinitcheck "Direct link to #\[noinitcheck]") In normal circumstances, all functions in an Aztec contract (except initializers) have an initialization check inserted at the beginning of the function body. This check ensures that the contract has been initialized before any other function can be called. However, there may be scenarios where you want a function to be callable regardless of the contract's initialization state. This is when you would use `#[noinitcheck]`. When a function is annotated with `#[noinitcheck]`: * The Aztec macro processor skips the [insertion of the initialization check](#initializer-functions-initializer) for this specific function * The function can be called at any time, even if the contract hasn't been initialized yet ## #\[only\_self][​](#only_self "Direct link to #\[only_self]") External functions marked with #\[only\_self] attribute can only be called by the contract itself - if other contracts try to make the call it will fail. This attribute is commonly used when an action starts in private but needs to be completed in public. The public function must be marked with #\[only\_self] to restrict access to only the contract itself. A typical example is a private token mint operation that needs to enqueue a call to a public function to update the publicly tracked total token supply. It is also useful in private functions when dealing with tasks of an unknown size but with a large upper bound (e.g. when needing to process an unknown amount of notes or nullifiers) as they allow splitting the work in multiple circuits, possibly resulting in performance improvements for low-load scenarios. This macro inserts a check at the beginning of the function to ensure that the caller is the contract itself. This is done by adding the following assertion: ``` assert(self.msg_sender() == self.address, "Function can only be called internally"); ``` ## #\[allow\_phase\_change][​](#allow_phase_change "Direct link to #\[allow_phase_change]") Private functions normally include a check to validate the current transaction phase. The `#[allow_phase_change]` attribute skips this validation, allowing the function to handle phase transitions internally. This is primarily used in account contract entrypoints that need to handle fee payment methods spanning multiple phases: ``` #[external("private")] #[allow_phase_change] fn entrypoint(app_payload: AppPayload, fee_payment_method: u8, cancellable: bool) { // Handle different fee payment methods that may span phases } ``` ## #\[authorize\_once][​](#authorize_once "Direct link to #\[authorize_once]") The `#[authorize_once]` attribute enables authorization checks via the [authwit mechanism](/developers/testnet/docs/foundational-topics/advanced/authwit.md) with replay protection. Use this when a function performs actions on behalf of someone who is not the caller. ``` #[authorize_once("from", "authwit_nonce")] #[external("public")] fn transfer_in_public(from: AztecAddress, to: AztecAddress, amount: u128, authwit_nonce: Field) { // Transfer tokens from 'from' to 'to' } ``` The macro: * Verifies the caller is authorized to act on behalf of the `from` address * Emits the authorization request as an offchain effect for wallet verification * Consumes a nullifier with the provided nonce, preventing replay attacks ## Internal functions #\[internal("...")][​](#internal-functions-internal "Direct link to Internal functions #\[internal(\"...\")]") Internal functions are callable only from within the same contract and are inlined at call sites (like Solidity's internal functions). Unlike `#[only_self]`, they don't create a separate call—the code is directly inserted where called. ``` #[internal("private")] fn _prepare_private_balance_increase(to: AztecAddress) -> PartialNote { // Helper logic for private balance operations } #[internal("public")] fn _finalize_transfer(from: AztecAddress, amount: u128) { // Helper logic for public finalization } ``` Call internal functions via `self.internal`: ``` let partial = self.internal._prepare_private_balance_increase(recipient); ``` Key differences from `#[only_self]`: * **Inlined**: Code is inserted at call site, not a separate circuit/call * **Private internal**: Can only be called from private external or internal functions * **Public internal**: Can only be called from public external or internal functions ## Implementing notes[​](#implementing-notes "Direct link to Implementing notes") The `#[note]` attribute is used to define notes in Aztec contracts. When a struct is annotated with `#[note]`, the Aztec macro applies a series of transformations and generates implementations to turn it into a note that can be used in contracts to store private data. 1. **NoteType trait**: Provides a unique identifier for the note type via `get_id()` 2. **NoteHash trait**: Implements note hash and nullifier computation: * `compute_note_hash(self, owner, storage_slot, randomness)` - computes the note's hash * `compute_nullifier(self, context, owner, note_hash_for_nullification)` - computes the nullifier using the owner's nullifying key * `compute_nullifier_unconstrained(self, owner, note_hash_for_nullification)` - unconstrained version for use outside circuits 3. **NoteProperties struct**: A separate struct is generated to describe the note's fields, which is used for efficient retrieval of note data ### Example[​](#example "Direct link to Example") ``` #[note] struct CustomNote { value: Field, } ``` The `owner` is passed as a runtime parameter to the `compute_note_hash` and `compute_nullifier` functions, not stored as a field on the note. To see the exact generated code, run `aztec-nargo expand` on your contract. Key things to keep in mind: * The note struct must implement or derive the `Packable` trait * Developers can use `#[custom_note]` instead of `#[note]` to provide their own `NoteHash` implementation * The note's fields are automatically serialized and deserialized in the order they are defined in the struct ## Storage struct #\[storage][​](#storage-struct-storage "Direct link to Storage struct #\[storage]") The `#[storage]` attribute is used to define the storage structure for an Aztec contract. When a struct is annotated with `#[storage]`, the macro: 1. **Context Injection**: Injects a `Context` generic parameter into the storage struct and all its fields, allowing storage to interact with the Aztec context 2. **Storage Implementation Generation**: Generates an `impl` block with an `init` function that initializes each storage variable with its assigned slot 3. **Storage Slot Assignment**: Automatically assigns storage slots to each field based on their serialized length 4. **Storage Layout Generation**: Creates a `StorageLayout` struct exported via `#[abi(storage)]` for use in the contract artifact ### Example[​](#example-1 "Direct link to Example") ``` #[storage] struct Storage { balance: PublicMutable, owner: PublicMutable, token_map: Map, } ``` To see the exact generated code, run `aztec-nargo expand` on your contract. Alternatively, use `#[storage_no_init]` if you need manual control over storage slot allocation. Key things to keep in mind: * Only one storage struct can be defined per contract, and it must be named `Storage` * `Map` types and private `Note` types always occupy a single storage slot ## #\[storage\_no\_init][​](#storage_no_init "Direct link to #\[storage_no_init]") The `#[storage_no_init]` attribute is an alternative to `#[storage]` that gives you manual control over storage slot allocation. Use this when you need custom slot assignments or want to maintain compatibility with existing storage layouts. With `#[storage_no_init]`, you must provide your own `init` function: ``` #[storage_no_init] struct Storage { balance: PublicMutable, owner: PublicMutable, } impl Storage { fn init(context: Context) -> Self { Storage { balance: PublicMutable::new(context, 1), // Explicit slot assignment owner: PublicMutable::new(context, 5), // Non-sequential slot } } } ``` Unlike `#[storage]`, this macro does not generate: * The `init` function (you must implement it) * The `StorageLayout` struct for the contract artifact ## Further reading[​](#further-reading "Direct link to Further reading") * [Macros reference](/developers/testnet/docs/aztec-nr/framework-description/macros.md) --- # Understanding Function Context ## What is the context[​](#what-is-the-context "Direct link to What is the context") The context is an object that is made available within every function in `Aztec.nr`. As mentioned in the [kernel circuit documentation](/developers/testnet/docs/foundational-topics/advanced/circuits/private_kernel.md). At the beginning of a function's execution, the context contains all of the kernel information that application needs to execute. During the lifecycle of a transaction, the function will update the context with each of its side effects (created notes, nullifiers etc.). At the end of a function's execution the mutated context is returned to the kernel to be checked for validity. Behind the scenes, Aztec.nr will pass data the kernel needs to and from a circuit, this is abstracted away from the developer. In a developer's eyes, the context is a useful structure that allows you to access and mutate the state of the Aztec blockchain. On this page, you'll learn * The details and functionalities of the private context in Aztec.nr * Difference between the private and public contexts and their unified APIs * Components of the private context, such as inputs and block header. * Elements like return values, read requests, new note hashes, and nullifiers in transaction processing * Differences between the private and public contexts, especially the unique features and variables in the public context ## Two contexts, one API[​](#two-contexts-one-api "Direct link to Two contexts, one API") The `Aztec` blockchain contains two environments - public and private. * Private, for private transactions taking place on user's devices. * Public, for public transactions taking place on the network's sequencers. As there are two distinct execution environments, they both require slightly differing execution contexts. Despite their differences, the APIs for interacting with each are unified. Leading to minimal context switch when working between the two environments. The following section will cover both contexts. ## The Private Context[​](#the-private-context "Direct link to The Private Context") The code snippet below shows what is contained within the private context. private-context ``` pub inputs: PrivateContextInputs, pub side_effect_counter: u32, pub min_revertible_side_effect_counter: u32, pub is_fee_payer: bool, pub args_hash: Field, pub return_hash: Field, pub expiration_timestamp: u64, pub(crate) note_hash_read_requests: BoundedVec>, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL>, pub(crate) nullifier_read_requests: BoundedVec>, MAX_NULLIFIER_READ_REQUESTS_PER_CALL>, key_validation_requests_and_separators: BoundedVec, pub note_hashes: BoundedVec, MAX_NOTE_HASHES_PER_CALL>, pub nullifiers: BoundedVec, MAX_NULLIFIERS_PER_CALL>, pub private_call_requests: BoundedVec, pub public_call_requests: BoundedVec, MAX_ENQUEUED_CALLS_PER_CALL>, pub public_teardown_call_request: PublicCallRequest, pub l2_to_l1_msgs: BoundedVec, MAX_L2_TO_L1_MSGS_PER_CALL>, ``` > [Source code: noir-projects/aztec-nr/aztec/src/context/private\_context.nr#L140-L163](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/aztec-nr/aztec/src/context/private_context.nr#L140-L163) ### Private Context Broken Down[​](#private-context-broken-down "Direct link to Private Context Broken Down") #### Inputs[​](#inputs "Direct link to Inputs") The context inputs includes all of the information that is passed from the kernel circuit into the application circuit. It contains the following values. private-context-inputs ``` #[derive(Eq)] pub struct PrivateContextInputs { pub call_context: CallContext, pub anchor_block_header: BlockHeader, pub tx_context: TxContext, pub start_side_effect_counter: u32, } ``` > [Source code: noir-projects/aztec-nr/aztec/src/context/inputs/private\_context\_inputs.nr#L7-L15](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/aztec-nr/aztec/src/context/inputs/private_context_inputs.nr#L7-L15) As shown in the snippet, the application context is made up of 3 main structures. The call context, the block header, and the private global variables. First of all, the call context. call-context ``` #[derive(Deserialize, Eq, Serialize)] pub struct CallContext { // The address of the contract that is making the call. pub msg_sender: AztecAddress, // The address of the contract being called. pub contract_address: AztecAddress, // The selector of the function being called. pub function_selector: FunctionSelector, // Whether the call will modify the state of the contract. pub is_static_call: bool, } ``` > [Source code: noir-projects/noir-protocol-circuits/crates/types/src/abis/call\_context.nr#L8-L20](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-protocol-circuits/crates/types/src/abis/call_context.nr#L8-L20) The call context contains information about the current call being made: 1. Msg Sender * The message sender is the account (Aztec Contract) that sent the message to the current context. In the first call of the kernel circuit (often the account contract call), this value will be empty. For all subsequent calls the value will be the previous call. > The graphic below illustrates how the message sender changes throughout the kernel circuit iterations. ![](/assets/ideal-img/sender_context_change.7a4633f.640.png) 2. Contract address * This value is the address of the current context's contract address. This value will be the value of the current contract that is being executed. 3. Flags * Furthermore there are a series of flags that are stored within the application context: * is\_static\_call: This will be set if and only if the current call is a static call. In a static call, state changing altering operations are not allowed. ### Block Header[​](#block-header "Direct link to Block Header") Another structure that is contained within the context is the `BlockHeader` object, which is the header of the block used to generate proofs against. block-header ``` #[derive(Deserialize, Eq, Serialize)] pub struct BlockHeader { pub last_archive: AppendOnlyTreeSnapshot, pub state: StateReference, // The hash of the sponge blob for this block, which commits to the tx effects added in this block. // Note: it may also include tx effects from previous blocks within the same checkpoint. // When proving tx effects from this block only, we must refer to the `sponge_blob_hash` in the previous block // header to show that the effect was added after the previous block. // The previous block header can be validated using a membership proof of the last leaf in `last_archive`. pub sponge_blob_hash: Field, pub global_variables: GlobalVariables, pub total_fees: Field, pub total_mana_used: Field, } ``` > [Source code: noir-projects/noir-protocol-circuits/crates/types/src/abis/block\_header.nr#L12-L29](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-protocol-circuits/crates/types/src/abis/block_header.nr#L12-L29) ### Transaction Context[​](#transaction-context "Direct link to Transaction Context") The private context provides access to the transaction context as well, which are user-defined values for the transaction in general that stay constant throughout its execution. tx-context ``` #[derive(Deserialize, Eq, Serialize)] pub struct TxContext { // The chain ID on which this transaction is executed. pub chain_id: Field, // The version of the L1 Rollup contract. pub version: Field, // The gas settings for the transaction. pub gas_settings: GasSettings, } ``` > [Source code: noir-projects/noir-protocol-circuits/crates/types/src/abis/transaction/tx\_context.nr#L8-L18](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-protocol-circuits/crates/types/src/abis/transaction/tx_context.nr#L8-L18) ### Args Hash[​](#args-hash "Direct link to Args Hash") To allow for flexibility in the number of arguments supported by Aztec functions, all function inputs are reduced to a singular value which can be proven from within the application. The `args_hash` is the result of poseidon2 hashing all of a function's inputs. ### Return Values[​](#return-values "Direct link to Return Values") The return values are a set of values that are returned from an applications execution to be passed to other functions through the kernel. Developers do not need to worry about passing their function return values to the `context` directly as `Aztec.nr` takes care of it for you. See the documentation surrounding `Aztec.nr` [macro expansion](/developers/testnet/docs/aztec-nr/framework-description/functions/attributes.md#private-functions-externalprivate) for more details. ``` return_hash: Field, ``` ## Expiration Timestamp[​](#expiration-timestamp "Direct link to Expiration Timestamp") Some data structures impose time constraints, e.g. they may make it so that a value can only be changed after a certain delay. Interacting with these in private involves creating proofs that are only valid as long as they are included before a certain future point in time. To achieve this, the `set_expiration_timestamp` function can be used to set this property: expiration-timestamp ``` pub fn set_expiration_timestamp(&mut self, expiration_timestamp: u64) { ``` > [Source code: noir-projects/aztec-nr/aztec/src/context/private\_context.nr#L609-L611](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/aztec-nr/aztec/src/context/private_context.nr#L609-L611) A transaction that sets this value will never be included in a block with a timestamp larger than the requested value, since it would be considered invalid. This can also be used to make transactions automatically expire after some time if not included. ### Read Requests[​](#read-requests "Direct link to Read Requests") Read requests are used to prove that certain notes existed at a specific point in time. When a private function reads a note, it generates a read request that gets validated by the kernel circuit to ensure the note was valid at the time of the transaction. ### New Note Hashes[​](#new-note-hashes "Direct link to New Note Hashes") New note hashes contains an array of all of the note hashes created in the current execution context. ### New Nullifiers[​](#new-nullifiers "Direct link to New Nullifiers") New nullifiers contains an array of the new nullifiers emitted from the current execution context. ### Nullified Note Hashes[​](#nullified-note-hashes "Direct link to Nullified Note Hashes") Nullified note hashes is an optimization for introduced to help reduce state growth. There are often cases where note hashes are created and nullified within the same transaction. In these cases there is no reason that these note hashes should take up space on the node's commitment/nullifier trees. Keeping track of nullified note hashes allows us to "cancel out" and prove these cases. ### Private Call Stack[​](#private-call-stack "Direct link to Private Call Stack") The private call stack contains all of the external private function calls that have been created within the current context. Any function call objects are hashed and then pushed to the execution stack. The kernel circuit will orchestrate dispatching the calls and returning the values to the current context. ### Public Call Stack[​](#public-call-stack "Direct link to Public Call Stack") The public call stack contains all of the external function calls that are created within the current context. Like the private call stack above, the calls are hashed and pushed to this stack. Unlike the private call stack, these calls are not executed client side. Whenever the function is sent to the network, it will have the public call stack attached to it. At this point the sequencer will take over and execute the transactions. ### New L2 to L1 msgs[​](#new-l2-to-l1-msgs "Direct link to New L2 to L1 msgs") New L2 to L1 messages contains messages that are delivered to the l1 outbox on the execution of each rollup. ## Public Context[​](#public-context "Direct link to Public Context") The Public Context includes all of the information passed from the `Public VM` into the execution environment. Its interface is very similar to the [Private Context](#the-private-context), however it has some minor differences (detailed below). ### Public Global Variables[​](#public-global-variables "Direct link to Public Global Variables") The public global variables are provided by the rollup sequencer and consequently contain some more values than the private global variables. global-variables ``` #[derive(Deserialize, Eq, Serialize)] pub struct GlobalVariables { pub chain_id: Field, pub version: Field, pub block_number: u32, pub slot_number: Field, pub timestamp: u64, pub coinbase: EthAddress, pub fee_recipient: AztecAddress, pub gas_fees: GasFees, } ``` > [Source code: noir-projects/noir-protocol-circuits/crates/types/src/abis/global\_variables.nr#L7-L19](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-protocol-circuits/crates/types/src/abis/global_variables.nr#L7-L19) --- # Inner Workings of Functions This page explains what happens under the hood when you create a function in an Aztec contract. The [next page](/developers/testnet/docs/aztec-nr/framework-description/functions/attributes.md) covers what the function attributes do. ## Overview[​](#overview "Direct link to Overview") Private functions in Aztec compile to standalone circuits that must conform to the protocol's kernel circuit interface. Public functions compile to AVM bytecode. The transformations described below bridge the gap between developer-friendly Aztec.nr syntax and these underlying requirements. Utility functions (marked with `#[external("utility")]`) do not undergo these transformations—they remain as regular Noir functions. ## Function transformation[​](#function-transformation "Direct link to Function transformation") When you define a private or public function in an Aztec contract, it undergoes several transformations during compilation: * [Creating a context for the function](#context-creation) * [Handling function inputs](#private-and-public-input-injection) * [Processing return values](#return-value-handling) ## Context creation[​](#context-creation "Direct link to Context creation") Every function in an Aztec contract operates within a specific context that provides execution information and functionality. This is either a `PrivateContext` or `PublicContext` object, depending on whether it is a private or public function. ### Private functions[​](#private-functions "Direct link to Private functions") For private functions, context creation involves serializing and hashing all input parameters: ``` // Parameters are serialized into an array let serialized_args: [Field; N] = /* serialized parameters */; // Hash the arguments using poseidon2 let args_hash = aztec::hash::hash_args(serialized_args); // Create the context with the inputs and args hash let mut context = PrivateContext::new(inputs, args_hash); ``` This hashing is important because the kernel circuit uses it to verify the function received the correct parameters without exposing the input data. ### Public functions[​](#public-functions "Direct link to Public functions") For public functions, context creation uses a lazy evaluation pattern: ``` let mut context = PublicContext::new(|| { // compute args hash when needed hash_args(serialized_args) }); ``` ### Using the context[​](#using-the-context "Direct link to Using the context") The context object provides methods for interacting with the blockchain. Storage access and contract calls are handled through a `ContractSelf` wrapper that the macros generate automatically. ## Private and public input injection[​](#private-and-public-input-injection "Direct link to Private and public input injection") An additional parameter is automatically added to every private function. The injected input is always the first parameter of the transformed function and is of type `PrivateContextInputs` for private functions. Original function definition: ``` fn my_function(param1: Type1, param2: Type2) { ... } ``` Transformed function with injected input: ``` fn my_function(inputs: PrivateContextInputs, param1: Type1, param2: Type2) { ... } ``` The `PrivateContextInputs` struct contains: * `call_context` - information about how the function was called (msg\_sender, contract\_address, function\_selector, is\_static\_call) * `anchor_block_header` - the historical block header used during private execution * `tx_context` - transaction-level data (chain\_id, version, gas\_settings) * `start_side_effect_counter` - the side effect counter at function entry These inputs are made available through the `PrivateContext` object within your function. Public functions run in the AVM and access their context data through AVM opcodes rather than injected inputs. ## Return value handling[​](#return-value-handling "Direct link to Return value handling") Return values in Aztec contracts are processed differently from traditional smart contracts. ### Private functions[​](#private-functions-1 "Direct link to Private functions") For private functions, the return value is serialized, hashed, and stored in the context: ``` // The original return value is captured let macro__returned__values = original_return_expression; // The return value is serialized and hashed let serialized_return: [Field; N] = /* serialized return value */; self.context.set_return_hash(serialized_return); ``` The function's return type is changed to `PrivateCircuitPublicInputs`, which is returned by calling `context.finish()` at the end of the function. This process allows the return values to be included in the function's computation result while maintaining privacy. The actual return values are stored in the execution cache and can be retrieved by the caller using the hash. ### Public functions[​](#public-functions-1 "Direct link to Public functions") In public functions, the return value is handled directly by the AVM and the function's return type remains as specified by the developer. ## Function signature generation[​](#function-signature-generation "Direct link to Function signature generation") Each contract function has a unique 4-byte function selector. The selector is computed by hashing the function's signature string using Poseidon2: ``` impl FunctionSelector { pub fn from_signature(signature: str) -> Self { let bytes = signature.as_bytes(); let hash = poseidon2_hash_bytes(bytes); // hash is truncated to fit within 32 bits (4 bytes) FunctionSelector::from_field(hash) } } ``` The signature string follows the format `function_name(param_types)`. For example, `transfer(Field,Field)`. This approach is inspired by Solidity's function selector mechanism, but uses Poseidon2 instead of Keccak-256 for compatibility with Aztec's circuit-friendly hash functions. ## Contract artifacts[​](#contract-artifacts "Direct link to Contract artifacts") Contract artifacts are automatically generated structures that describe the contract's interface. They preserve the original function signatures (parameters and return types) before macro transformations are applied. For each function in the contract, an ABI export is generated with: 1. A parameters struct containing all function parameters 2. An ABI struct marked with `#[abi(functions)]` containing the parameters and return type For example, given a function: ``` fn increment(owner: AztecAddress) -> Field { ... } ``` The following structs are generated: ``` pub struct increment_parameters { pub owner: AztecAddress } #[abi(functions)] pub struct increment_abi { parameters: increment_parameters, return_type: Field } ``` The `#[abi(functions)]` attribute marks the struct for inclusion in the contract ABI's `outputs.functions` array. This is important because macro processing changes the actual return type of private functions to `PrivateCircuitPublicInputs`, but the toolchain needs access to the original signatures. Contract artifacts enable: * Machine-readable contract interface descriptions * TypeScript binding generation (see [how to compile contracts](/developers/testnet/docs/aztec-nr/compiling_contracts.md)) * Function return value decoding in the simulator ## Further reading[​](#further-reading "Direct link to Further reading") * [Function attributes and macros](/developers/testnet/docs/aztec-nr/framework-description/functions/attributes.md) * [Aztec.nr macro source code](https://github.com/AztecProtocol/aztec-packages/tree/v5.0.0-rc.2/noir-projects/aztec-nr/aztec/src/macros) - for those who want to see the actual transformation implementation --- # How to Define Functions ## Overview[​](#overview "Direct link to Overview") This guide shows you how to define different types of functions in your Aztec contracts, each serving specific purposes and execution environments. ## Quick reference[​](#quick-reference "Direct link to Quick reference") | Annotation | Execution | State access | | ------------------------ | ----------------- | ------------------------------------------------------------ | | `#[external("private")]` | User device | Private state (and selected public values via storage types) | | `#[external("public")]` | Sequencer | Public state | | `#[external("utility")]` | Offchain client | Public + private (unconstrained) | | `#[internal("private")]` | N/A | Inlined private helper (non-entrypoint) | | `#[internal("public")]` | N/A | Inlined public helper (non-entrypoint) | | `#[view]` | Private or public | Read-only (no state mutation) | | `#[only_self]` | Private or public | Callable only by the same contract | | `#[initializer]` | Private or public | One-time initialization | ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * An Aztec contract project set up with the `aztec-nr` dependency * Basic understanding of [Noir programming language](https://noir-lang.org/docs) * Familiarity with Aztec Protocol's [call types](/developers/testnet/docs/foundational-topics/call_types.md) (private vs public) ## Define private functions[​](#define-private-functions "Direct link to Define private functions") Use `#[external("private")]` to create functions that execute privately on user devices. For example: increment ``` #[external("private")] fn increment(owner: AztecAddress) { debug_log_format("Incrementing counter for owner {0}", [owner.to_field()]); self.storage.counters.at(owner).add(1).deliver(MessageDelivery::onchain_constrained()); } ``` > [Source code: docs/examples/contracts/counter\_contract/src/main.nr#L36-L42](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/counter_contract/src/main.nr#L36-L42) Private functions run in a private context, can access private state, and can read certain public values through storage types like [`DelayedPublicMutable`](/developers/testnet/docs/aztec-nr/framework-description/state_variables.md#delayedpublicmutable). ## Define public functions[​](#define-public-functions "Direct link to Define public functions") Use `#[external("public")]` to create functions that execute on the sequencer: mint\_public ``` #[external("public")] fn mint_public(employee: AztecAddress, amount: u64) { // Only Giggle can mint tokens assert_eq(self.msg_sender(), self.storage.owner.read(), "Only Giggle can mint BOB tokens"); // Add tokens to employee's public balance let current_balance = self.storage.public_balances.at(employee).read(); self.storage.public_balances.at(employee).write(current_balance + amount); } ``` > [Source code: docs/examples/contracts/bob\_token\_contract/src/main.nr#L41-L51](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/bob_token_contract/src/main.nr#L41-L51) Public functions operate on public state, similar to EVM contracts. They can write to private storage, but any data written from a public function is publicly visible. ## Define utility functions[​](#define-utility-functions "Direct link to Define utility functions") Create offchain query functions using the `#[external("utility")]` annotation with `unconstrained`. Utility functions are standalone unconstrained functions that cannot be called from private or public functions. They are meant to be called by *applications* to perform auxiliary tasks like querying contract state or processing offchain messages. Example: get\_counter ``` #[external("utility")] unconstrained fn get_counter(owner: AztecAddress) -> pub u128 { self.storage.counters.at(owner).balance_of() } ``` > [Source code: docs/examples/contracts/counter\_contract/src/main.nr#L44-L49](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/counter_contract/src/main.nr#L44-L49) Use `aztec.js` `simulate` to execute utility functions and read their return values. For details, see [Call Types](/developers/testnet/docs/foundational-topics/call_types.md#simulate). ## Define view functions[​](#define-view-functions "Direct link to Define view functions") Create read-only functions using the `#[view]` annotation combined with `#[external("private")]` or `#[external("public")]`: ``` #[external("public")] #[view] fn get_config_value() -> Field { // logic } ``` View functions cannot modify contract state. They're akin to Ethereum's `view` functions. `#[view]` only applies to `#[external("private")]` and `#[external("public")]` functions. ## Define only-self functions[​](#define-only-self-functions "Direct link to Define only-self functions") Create contract-only functions using the `#[only_self]` annotation: \_assert\_is\_owner ``` #[external("public")] #[only_self] fn _assert_is_owner(address: AztecAddress) { assert_eq(address, self.storage.owner.read(), "Only Giggle can mint BOB tokens"); } ``` > [Source code: docs/examples/contracts/bob\_token\_contract/src/main.nr#L131-L137](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/bob_token_contract/src/main.nr#L131-L137) Only-self functions are only callable by the same contract, which is useful when a private function enqueues a public call that should only be callable internally. ## Define initializer functions[​](#define-initializer-functions "Direct link to Define initializer functions") Create constructor-like functions using the `#[initializer]` annotation: constructor ``` #[initializer] #[external("private")] // We can name our initializer anything we want as long as it's marked as #[initializer] fn constructor(initial_value: u128, owner: AztecAddress) { self.storage.counters.at(owner).add(initial_value).deliver( MessageDelivery::onchain_constrained(), ); } ``` > [Source code: docs/examples/contracts/counter\_contract/src/main.nr#L25-L34](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/counter_contract/src/main.nr#L25-L34) ### Use multiple initializers[​](#use-multiple-initializers "Direct link to Use multiple initializers") Define multiple initialization options: 1. Mark each function with `#[initializer]` 2. Choose which one to call during deployment 3. Any initializer marks the contract as initialized ## Define internal functions[​](#define-internal-functions "Direct link to Define internal functions") Create helper functions using `#[internal("private")]` or `#[internal("public")]`. Internal functions are inlined at call sites and do not create separate entrypoints: ``` #[internal("private")] fn _prepare_transfer(to: AztecAddress, amount: u128) -> Field { // helper logic for private functions } #[internal("public")] fn _update_balance(owner: AztecAddress, amount: u128) { // helper logic for public functions } ``` Call internal functions via `self.internal`: ``` let result = self.internal._prepare_transfer(recipient, amount); ``` Key constraints: * Private internal functions can only be called from private external or internal functions * Public internal functions can only be called from public external or internal functions ## Next steps[​](#next-steps "Direct link to Next steps") * [Attributes and Macros](/developers/testnet/docs/aztec-nr/framework-description/functions/attributes.md) * [Call Types](/developers/testnet/docs/foundational-topics/call_types.md) --- # Visibility In Aztec there are multiple different types of visibility that can be applied to functions. Namely we have `data visibility` and `function visibility`. This page explains these types of visibility. ## Data visibility[​](#data-visibility "Direct link to Data visibility") Data visibility describes whether the data (or state) used in a function is generally accessible (public) or on a need-to-know basis (private). ## Function visibility[​](#function-visibility "Direct link to Function visibility") Function visibility describes whether a function is callable from other contracts, or only from within the same contract. This is similar to the visibility modifiers you may be familiar with from Solidity. ### The `#[external(...)]` attribute[​](#the-external-attribute "Direct link to the-external-attribute") In Aztec.nr, the `#[external(...)]` attribute marks a function as externally callable - meaning it can be invoked via a transaction or by other contracts. The attribute takes a parameter specifying the execution context: * `#[external("private")]` - The function executes in a private context with access to private state * `#[external("public")]` - The function executes in a public context with access to public state ### The `#[only_self]` attribute[​](#the-only_self-attribute "Direct link to the-only_self-attribute") By default, all external functions are callable from other contracts, similar to Solidity's `public` visibility. To restrict a function so it can only be called by the same contract, use the `#[only_self]` attribute: ``` #[external("public")] #[only_self] fn _increase_public_balance(to: AztecAddress, amount: u128) { // This function can only be called by this contract let new_balance = self.storage.public_balances.at(to).read().add(amount); self.storage.public_balances.at(to).write(new_balance); } ``` A common use case for `#[only_self]` is when a private function needs to modify public state. Since private functions cannot directly modify public state, they enqueue calls to public functions. By marking the public function with `#[only_self]`, you ensure that only your contract can call it - preventing external parties from manipulating the public state directly. danger Note that functions without `#[only_self]` can be used directly as an entry-point, which currently means that the `msg_sender` would be `0`. For this reason, using address `0` as a burn address is not recommended. You can learn more about this in the [Accounts concept page](/developers/testnet/docs/foundational-topics/accounts/keys.md). ### The `#[internal]` attribute[​](#the-internal-attribute "Direct link to the-internal-attribute") The `#[internal]` attribute is different from `#[only_self]`. While `#[only_self]` restricts *who* can call a function (only the same contract, but still via an external call), `#[internal]` functions are **inlined** into the calling function. This is similar to how Solidity's `internal` functions use EVM's `JUMP` instruction rather than `CALL`. Internal functions: * Cannot be called externally (no transaction can invoke them directly) * Are inlined at compile time into the functions that call them * Have access to the calling function's context To understand how visibility works under the hood, check out the [Inner Workings page](/developers/testnet/docs/aztec-nr/framework-description/functions/attributes.md). --- # Global Variables Similar to Solidity's global `block` variable, Aztec exposes contextual values within each function via the `context` object. Aztec has two execution environments—Private and Public—each with different available globals. ## Private Global Variables[​](#private-global-variables "Direct link to Private Global Variables") Private functions access transaction context via `TxContext`: tx-context ``` #[derive(Deserialize, Eq, Serialize)] pub struct TxContext { // The chain ID on which this transaction is executed. pub chain_id: Field, // The version of the L1 Rollup contract. pub version: Field, // The gas settings for the transaction. pub gas_settings: GasSettings, } ``` > [Source code: noir-projects/noir-protocol-circuits/crates/types/src/abis/transaction/tx\_context.nr#L8-L18](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-protocol-circuits/crates/types/src/abis/transaction/tx_context.nr#L8-L18) The following fields are accessible via `context` methods: ### Chain Id[​](#chain-id "Direct link to Chain Id") The unique identifier for the Aztec network instance (not the Ethereum chain the rollup settles to). ``` self.context.chain_id(); ``` ### Version[​](#version "Direct link to Version") The Aztec protocol version number. The genesis block has version 1. ``` self.context.version(); ``` ### Gas Settings[​](#gas-settings "Direct link to Gas Settings") The gas limits, max fees per gas, and inclusion fee set by the user for the transaction. ``` self.context.gas_settings(); ``` ## Public Global Variables[​](#public-global-variables "Direct link to Public Global Variables") Public functions access block-level context via `GlobalVariables`: global-variables ``` #[derive(Deserialize, Eq, Serialize)] pub struct GlobalVariables { pub chain_id: Field, pub version: Field, pub block_number: u32, pub slot_number: Field, pub timestamp: u64, pub coinbase: EthAddress, pub fee_recipient: AztecAddress, pub gas_fees: GasFees, } ``` > [Source code: noir-projects/noir-protocol-circuits/crates/types/src/abis/global\_variables.nr#L7-L19](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-protocol-circuits/crates/types/src/abis/global_variables.nr#L7-L19) note Not all fields in `GlobalVariables` are exposed via context methods. The `coinbase`, `fee_recipient`, and `slot_number` fields are used internally by the protocol. Public functions have access to `chain_id()` and `version()` (same syntax as private), plus the following block-level values: ### Timestamp[​](#timestamp "Direct link to Timestamp") The unix timestamp when the block is executed. Provided by the block proposer, so it may have slight variance. Always increases monotonically. ``` self.context.timestamp(); ``` ### Block Number[​](#block-number "Direct link to Block Number") The sequential block identifier. Genesis block is 1, incrementing by 1 for each subsequent block. ``` self.context.block_number(); ``` ### Gas Fees[​](#gas-fees "Direct link to Gas Fees") The current L2 and DA gas prices for the block. You can access gas-related information via: ``` self.context.l2_gas_left(); // Remaining L2 gas self.context.da_gas_left(); // Remaining DA gas self.context.min_fee_per_l2_gas(); // L2 gas price self.context.min_fee_per_da_gas(); // DA gas price self.context.transaction_fee(); // Final tx fee (only available in teardown phase) ``` Why do available globals differ between environments? Private functions execute on the user's device before the transaction is submitted, so they cannot know which block will include the transaction. Therefore, `timestamp` and `block_number` are unavailable in private context. Public functions execute on a sequencer who knows the current block's timestamp and number, making these values accessible. --- # Immutables via Salt Aztec contracts can commit immutable values directly into the contract's address by encoding them into the deployment salt, removing the need for a separate initialization transaction. ## Overview[​](#overview "Direct link to Overview") Rather than storing immutables in private storage (which requires an initializer function and an extra transaction), the [aztec-immutables-macro](https://github.com/defi-wonderland/aztec-immutables-macro/tree/dev) library encodes them into the contract's salt: ``` salt = poseidon2_hash([actual_salt, constant_0, constant_1, ...]) ``` Since the salt is part of the address derivation, the immutable values become cryptographically bound to the contract's address itself. ## Key benefits[​](#key-benefits "Direct link to Key benefits") * **No initialization transaction** — immutables are committed at deployment time, not in a separate setup call * **Runtime verification** — at execution time, capsule data is loaded and verified against the stored salt, ensuring data integrity * **Persistent storage** — immutables are persisted to the PXE's [CapsuleStore](/developers/testnet/docs/aztec-nr/framework-description/advanced/how_to_use_capsules.md) after deployment, so capsules don't need to be attached to every transaction * **Compatible with standard storage** — works alongside `#[storage]` and initializers when needed ## Performance[​](#performance "Direct link to Performance") Initialization cost is completely eliminated (no constructor transaction). The per-transaction overhead is approximately 1,098 gates (+0.2%) in the account entrypoint. ## Getting started[​](#getting-started "Direct link to Getting started") For installation instructions, usage examples, and a reference implementation of an initializerless Schnorr account contract, see the [aztec-immutables-macro README](https://github.com/defi-wonderland/aztec-immutables-macro/tree/dev). --- # Aztec Macros Aztec.nr provides macros (attributes) that transform your code during compilation to handle the complexities of private execution, proof generation, and state management. ## Quick reference[​](#quick-reference "Direct link to Quick reference") ### Contract[​](#contract "Direct link to Contract") | Attribute | Purpose | | ---------- | ----------------------------------- | | `#[aztec]` | Marks a module as an Aztec contract | ### Functions[​](#functions "Direct link to Functions") | Attribute | Purpose | | ------------------------ | ----------------------------------------------------------------- | | `#[external("private")]` | Client-side private execution with proofs | | `#[external("public")]` | Sequencer-side public execution | | `#[external("utility")]` | Unconstrained queries, not included in transactions | | `#[internal("private")]` | Private helper, only callable within the same contract | | `#[internal("public")]` | Public helper, only callable within the same contract | | `#[view]` | Prevents state modification | | `#[initializer]` | Contract constructor | | `#[noinitcheck]` | Callable before contract initialization | | `#[allow_phase_change]` | Allows for phase change to happen during the function's execution | | `#[only_self]` | Only callable by the same contract | | `#[authorize_once]` | Requires authwit authorization with replay protection | Functions can have multiple attributes (e.g., `#[external("public")]` with `#[view]` and `#[only_self]`). ### Structs[​](#structs "Direct link to Structs") | Attribute | Purpose | | -------------------- | ------------------------------------- | | `#[note]` | Defines a private note type | | `#[custom_note]` | Note with custom hash/nullifier logic | | `#[storage]` | Defines contract storage layout | | `#[storage_no_init]` | Storage with manual slot allocation | For detailed explanations and examples, see the [Attributes and Macros reference](/developers/testnet/docs/aztec-nr/framework-description/functions/attributes.md). ## Further reading[​](#further-reading "Direct link to Further reading") * [Attributes and Macros reference](/developers/testnet/docs/aztec-nr/framework-description/functions/attributes.md) - detailed documentation for each macro * [Inner workings of functions](/developers/testnet/docs/aztec-nr/framework-description/functions/function_transforms.md) - how macros transform your code --- # Note Delivery When you create a note in an Aztec smart contract, you must deliver it to the recipient so they can use it. This page explains how note delivery works and how to choose the right delivery mode for your use case. ## Overview[​](#overview "Direct link to Overview") In Aztec, creating a note involves two steps: 1. **Creating the note** - Adding the note hash to the note hash tree 2. **Delivering the note** - Sending the note contents to the recipient so they can decrypt and use it Without delivery, the recipient won't know the note exists or be able to access its contents, even though the note hash is onchain. ## The `.deliver()` Method[​](#the-deliver-method "Direct link to the-deliver-method") When you create a note using state variables like `PrivateMutable`, `PrivateSet`, `BalanceSet`, or `SinglePrivateMutable`, the creation methods return a `NoteMessage` or `MaybeNoteMessage` object. A message contains arbitrary information emitted from a contract - currently this includes notes and private events, though developers may define other message types in the future. You must call `.deliver()` on this object to send the message (containing the note) to the recipient. ``` #[aztec] pub contract PrivateToken { use aztec::messages::delivery::MessageDelivery; #[external("private")] fn mint(amount: u128, recipient: AztecAddress) { // Adding to the balance returns a MaybeNoteMessage self.storage.balances.at(recipient).add(amount) .deliver(MessageDelivery::onchain_constrained()); } } ``` ## Delivery Modes[​](#delivery-modes "Direct link to Delivery Modes") Aztec provides three delivery modes that offer different tradeoffs between cost, proving time, and guarantees: ### `MessageDelivery::offchain()`[​](#messagedeliveryoffchain "Direct link to messagedeliveryoffchain") **Fully offchain delivery with no guarantees.** This delivery method encrypts messages without constraints and emits them via an oracle call as offchain effects, rather than through the protocol's log stream (which would post data to Ethereum blobs). With offchain delivery, you must manually handle both message transmission and processing. #### How It Works[​](#how-it-works "Direct link to How It Works") Offchain messages bypass Aztec's default private log infrastructure entirely: 1. **Message emission**: The contract encrypts the message (without constraints) and emits it via an oracle call. This creates an "offchain effect" that is included in the transaction but not posted to L1. 2. **Manual extraction**: When the transaction is sent, you must extract the offchain message from the transaction's offchain effects (available via `provenTx.offchainEffects` in aztec.js). 3. **Manual delivery**: You deliver the message through your own channel - Signal, cloud storage, QR codes, peer-to-peer networks, etc. 4. **Manual processing**: The recipient calls `process_message` on the target contract (as an unconstrained function), passing the ciphertext and message context. This decrypts the message and processes it (e.g., adding notes to the PXE database). The PXE cannot automatically discover offchain messages during private state sync because they are not in the log stream that nodes load from Ethereum blobs. **You are responsible for implementing both the delivery mechanism and ensuring the recipient processes the message.** #### When to Use[​](#when-to-use "Direct link to When to Use") * **Use when:** The sender is incentivized to deliver correctly (e.g., sending to yourself, payment for goods/services where recipient must receive the note to complete the transaction) * **Costs:** Zero delivery fees (no blob space), zero proving time overhead * **Guarantees:** None. The sender can fail to deliver or deliver incorrect content * **Privacy:** Maximum. No onchain data is emitted This is expected to be the most common delivery method when you don't need constrained delivery guarantees, as it completely eliminates blob space costs. #### Example Use Cases[​](#example-use-cases "Direct link to Example Use Cases") * Change notes when transferring tokens (you're sending to yourself) * Payments where the recipient won't provide goods/services without the note * Messages to local accounts controlled by the sender * Low-value use-cases like delivering game state updates to a game server ``` // Change note - sender is motivated to deliver to themselves self.storage.balances.at(sender).add(change_amount) .deliver(MessageDelivery::offchain()); ``` TODO This section will be updated with a complete TypeScript example showing how to extract offchain messages from transaction effects and manually deliver them once the API in Aztec.js is finalized. The full workflow example will make the offchain delivery pattern clearer. #### JavaScript Implementation[​](#javascript-implementation "Direct link to JavaScript Implementation") When using offchain delivery, extract and manually deliver messages in your application: ``` import { MessageContext } from "@aztec/stdlib/logs" // Prove transaction and get offchain effects const txProvingResult = await wallet.pxe.proveTx(txRequest); const provenTx = new ProvenTx( wallet.node, await txProvingResult.toTx(), txProvingResult.getOffchainEffects(), txProvingResult.stats, ); // Extract offchain message const offchainEffects = provenTx.offchainEffects; const ciphertext = offchainEffects[0].data.slice(2); // Send tx const sentTx = provenTx.send() const tx = await sentTx.wait() const txHash = await sentTx.getTxHash() // Deliver via your chosen channel (e.g., send to recipient via Signal, cloud storage, etc.). This is what you'd have to implement await deliverViaMyChannel(ciphertext, recipient); // Recipient processes the message const txEffect = await aztecNode.getTxEffect(txHash); const messageContext = MessageContext.fromTxEffectAndRecipient(txEffect, recipient); await contract.methods.process_message(ciphertext, messageContext.toNoirStruct()).simulate(); ``` See the [aztec.js documentation](/developers/testnet/docs/aztec-js.md) for more details on accessing transaction effects. ### `MessageDelivery::onchain_unconstrained()`[​](#messagedeliveryonchain_unconstrained "Direct link to messagedeliveryonchain_unconstrained") **Onchain delivery with no content guarantees.** This mode provides the same low proving time as `OFFCHAIN` while avoiding the need to implement custom delivery infrastructure. The tradeoff: you pay for DA (blob space) without gaining additional guarantees. If you're willing to build offchain delivery, use `OFFCHAIN` instead - it's strictly cheaper with the same guarantees. * **Use when:** The sender is incentivized to deliver correctly but you don't want to implement offchain delivery infrastructure * **Costs:** DA gas fees for the encrypted log, zero proving time overhead * **Guarantees:** Message stored onchain and retrievable, but sender can deliver incorrect content or wrong tag * **Privacy:** High - encrypted log reveals minimal information ``` // Minting to an admin who controls the contract self.storage.balances.at(admin).add(amount) .deliver(MessageDelivery::onchain_unconstrained()); ``` ### `MessageDelivery::onchain_constrained()`[​](#messagedeliveryonchain_constrained "Direct link to messagedeliveryonchain_constrained") **Onchain delivery with guaranteed correct content.** **WARNING**: This mode is [currently NOT fully constrained](https://github.com/AztecProtocol/aztec-packages/issues/14565). The log's tag is unconstrained, meaning a malicious sender could prevent the recipient from finding the message. * **Use when:** The sender cannot be trusted to deliver correctly (e.g., paying fees, creating notes for others, multisig configuration changes). Use this when you need to prove to a contract that the delivery has been done correctly. You can imagine a private NFT sale escrow contract where the escrow would be holding the NFT (the contract itself would be the NFT note owner) and then the escrow would release the NFT to the buyer once the NFT buyer pays the seller. In this case the `NFTSale::buy(...)` function would trigger the payment token transfer from the buyer to the seller and it would need to use `ONCHAIN_CONSTRAINED` delivery otherwise the escrow contract would be willing to transfer the NFT without the NFT seller actually being able to then spend the money. Note that for the transfer of the NFT from the escrow contract to the buyer you could use `OFFCHAIN` delivery because the delivery and encryption would be done in the buyer's PXE and hence there is alignment. * **Costs:** DA gas fees for the encrypted log, proving time overhead for encryption and tagging * **Guarantees:** Recipient receives correctly encrypted content (once tag constraining is implemented, recipient will be able to find it) * **Privacy:** High - encrypted log reveals minimal information ``` // Minting to an arbitrary recipient - must guarantee delivery self.storage.balances.at(recipient).add(amount) .deliver(MessageDelivery::onchain_constrained()); ``` ## Choosing a Delivery Mode[​](#choosing-a-delivery-mode "Direct link to Choosing a Delivery Mode") Ask yourself: **"Is the sender incentivized to deliver this note correctly?"** * **Yes, and they can contact the recipient offchain** Use `OFFCHAIN` * **Yes, but they cannot or prefer not to contact them offchain or you don't want to implement offchain delivery** Use `ONCHAIN_UNCONSTRAINED` * **No, the sender might not deliver correctly** Use `ONCHAIN_CONSTRAINED` ## Tagging secret strategy[​](#tagging-secret-strategy "Direct link to Tagging secret strategy") Onchain delivery tags every message so the recipient can find it efficiently (see [note discovery](#note-discovery-and-the-sender) below). Computing a tag requires a secret shared between sender and recipient, and there is more than one way for the two parties to come to share it. When an onchain handshake has been registered for the pair, the secret derived from it is reused directly. Otherwise the wallet decides how to proceed, since it knows which secrets it holds and how it wants to reach the recipient. The wallet's answer is a **tagging secret strategy**: it expresses *which* secret to use, and if necessary, PXE performs a [Diffie-Hellman key exchange](https://www.geeksforgeeks.org/computer-networks/diffie-hellman-key-exchange-and-perfect-forward-secrecy/) and/or app-siloing before handing the ready-to-use secret to the contract. Wallets therefore never reimplement that derivation. There are three strategies today: * **Non-interactive handshake**: the secret comes from a handshake published onchain that the recipient can derive. A non-recipient can at most learn that a sender did a handshake with the recipient, not the message itself, but the recipient discovers it without any prior coordination. Works for both constrained and unconstrained delivery. * **Address-derived secret**: the PXE derives the secret from the sender's and recipient's address keys via Diffie-Hellman. The wallet supplies no material, only the choice. It leaves no onchain trace, but the recipient only finds the message if they registered the sender in their PXE. Unconstrained delivery only. * **Arbitrary secret**: a raw secret point the two parties already share offchain, having coordinated out of band to agree on it. The wallet supplies the point and the PXE app-silos it. It leaves no onchain trace, but no onchain handshake backs the secret. Unconstrained delivery only. | | Non-interactive handshake | Address-derived secret | Arbitrary secret | | ----------------------------------- | ----------------------------------------------------- | --------------------------------------------- | --------------------------------------------- | | Onchain footprint when establishing | A handshake revealing information about the recipient | None | None | | Who provides the material | The onchain registry | Nobody (PXE computes it) | The wallet (a raw point) | | Constrained delivery | Supported | Not sound: not backed by an onchain handshake | Not sound: not backed by an onchain handshake | ### Defaults[​](#defaults "Direct link to Defaults") When no `resolveTaggingSecretStrategy` hook is configured, the PXE applies a privacy-safe default: * **Unconstrained delivery**: an address-derived (Diffie-Hellman) shared secret. It leaves no onchain trace, but the recipient only finds the message if they registered the sender in their PXE. * **Constrained delivery**: fails, rather than silently revealing the recipient through a non-interactive handshake. ### Configuring the strategy[​](#configuring-the-strategy "Direct link to Configuring the strategy") Wallets provide the strategy through the `resolveTaggingSecretStrategy` [execution hook](/developers/testnet/docs/foundational-topics/pxe/execution_hooks.md) when creating their PXE. The hook receives the message context (executing contract, sender, recipient and delivery mode), so a wallet can answer per message instead of with a fixed value. That page also covers how to configure a strategy in Noir tests. ## Note Discovery and the Sender[​](#note-discovery-and-the-sender "Direct link to Note Discovery and the Sender") When a note is delivered, recipients need to discover it among all the encrypted logs on the network. Aztec.nr uses a **tagging system** that requires computing a shared secret between the sender and recipient. ### Who is the "Sender"?[​](#who-is-the-sender "Direct link to Who is the \"Sender\"?") The "sender" for note discovery is **not the contract calling `.deliver()`**. Instead, it's the **account contract** that initiated the transaction. When your wallet submits a transaction, it tells PXE which address to use as the sender for tags (typically the originating account). Recipients compute the tag to find their notes from a secret shared between the sender and recipient, and there is [more than one way to establish that secret](#tagging-secret-strategy), chosen by the wallet. Contracts can override the sender at message delivery via the `with_sender` builder method, which works for both constrained and unconstrained delivery, e.g. `MessageDelivery::onchain_constrained().with_sender(address)`. **Example:** If Alice uses her account contract to call a token contract that mints tokens to Bob, the "sender for tags" is Alice's account contract address, not the token contract address. ### Discovering Notes from Unknown Senders[​](#discovering-notes-from-unknown-senders "Direct link to Discovering Notes from Unknown Senders") When the tag is derived from an address-based shared secret, you cannot compute it for a sender you haven't registered in advance, so you cannot receive those notes from an unknown sender. Handshake protocols let the two parties agree on the secret another way and lift this restriction. See [You cannot receive address-derived tagged notes from an unknown sender](/developers/testnet/docs/foundational-topics/advanced/storage/note_discovery.md#you-cannot-receive-address-derived-tagged-notes-from-an-unknown-sender) in the note discovery documentation for the approaches and workarounds. ## Delivering to Someone Other Than the Note Owner[​](#delivering-to-someone-other-than-the-note-owner "Direct link to Delivering to Someone Other Than the Note Owner") You can deliver a note to an address other than the note's owner using `.deliver_to()`: ``` // Create a note owned by `owner` but deliver it to `auditor` self.storage.balances.at(owner).add(amount) .deliver_to(auditor, MessageDelivery::onchain_constrained()); ``` **Important:** The recipient (e.g. an `auditor`) can see the note was created but **cannot use it** - only the owner can spend the note (this is authorized by the contract logic). The recipient also cannot see when/if the note is nullified. **Use cases:** * Traditional finance model of compliance where the third party sees all the activity (e.g. a bank) * Game servers that track all note creation and then quickly serve you the game state (results in better UX) * Analytics or monitoring services ## Code Examples[​](#code-examples "Direct link to Code Examples") ### Private Token Transfer[​](#private-token-transfer "Direct link to Private Token Transfer") ``` #[external("private")] fn transfer(amount: u128, sender: AztecAddress, recipient: AztecAddress) { // Subtract from sender - unconstrained since sender is the caller self.storage.balances.at(sender) .sub(amount) .deliver(MessageDelivery::onchain_unconstrained()); // Add to recipient - constrained delivery for untrusted sender self.storage.balances.at(recipient) .add(amount) .deliver(MessageDelivery::onchain_constrained()); } ``` ### Admin Initialization[​](#admin-initialization "Direct link to Admin Initialization") ``` #[external("private")] #[initializer] fn constructor(admin: AztecAddress) { // Admin is the owner of the note and is motivated to receive it // Use unconstrained delivery since we don't know if deployer is incentivized self.storage.admin .initialize(AddressNote { address: admin }, admin) .deliver(MessageDelivery::onchain_constrained()); } ``` --- # State Variables A contract's state is defined by multiple values. For example, in a token contract, these include the total supply, user balances, outstanding approvals, accounts with minting permission, etc. Each of these persisting values is called a *state variable*. One of the first design considerations for any smart contract is how it'll store its state. This is doubly true in Aztec due to there being **both public and private state** - the tradeoff space is large, so there's room for lots of decisions. ## Choosing the right storage type[​](#choosing-the-right-storage-type "Direct link to Choosing the right storage type") | Need | Use | | ----------------------------------------------- | ------------------------------ | | Public value anyone can read/write | `PublicMutable` | | Public value set once (contract name, decimals) | `PublicImmutable` | | Public key-value mapping | `Map>` | | Private collection per user (token balances) | `Owned>` | | Single private value per user | `Owned>` | | Immutable private value per user | `Owned>` | | Contract-wide private singleton (admin key) | `SinglePrivateMutable` | | Public value readable in private execution | `DelayedPublicMutable` | ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * An Aztec contract project set up with `aztec-nr` dependency * Understanding of Aztec's private and public state model * Familiarity with Noir struct syntax For storage concepts, see [storage overview](/developers/testnet/docs/foundational-topics/state_management.md). ## The Storage Struct[​](#the-storage-struct "Direct link to The Storage Struct") State variables are declared in Solidity by simply listing them inside of the contract, like so: ``` contract MyContract { uint128 public my_public_state_variable; } ``` In Aztec.nr, we define a [`struct`](https://noir-lang.org/docs/noir/concepts/data_types/structs) that holds *all* state variables. This struct is called **the storage struct**, and it is identified by having the [`#[storage]` macro](/aztec-nr-api/testnet/noir_aztec/macros/storage/fn.storage) applied to it. ``` use aztec::macros::aztec; #[aztec] contract MyContract { use aztec::macros::storage; #[storage] struct Storage { // state variables go here e.g, the admin of the contract admin: PublicMutable, } } ``` This struct must also have a generic type called `C` or `Context` - an unfortunate boilerplate parameter that provides execution mode information. The `#[storage]` macro can only be used once, so all contract state must be in a **single** struct. ### Accessing Storage[​](#accessing-storage "Direct link to Accessing Storage") The contract's storage is accessed via `self.storage` in any contract function. It will automatically be tailored to the execution context of that function, hiding all methods that cannot be invoked there. Consider, for example, a `PublicMutable` state variable, which is a value that is fully accessible in public functions, read-only in utility functions, and not accessible in private functions: ``` #[storage] struct Storage { my_public_variable: PublicMutable, } #[external("public")] fn my_public_function() { let current = self.storage.my_public_variable.read(); self.storage.my_public_variable.write(current + 1); } #[external("private")] fn my_private_function() { let current = self.storage.my_public_variable.read(); // compilation error - 'read' is not available in private self.storage.my_public_variable.write(current + 1); // compilation error - 'write' is not available in private } #[external("utility")] fn my_utility_function() { let current = self.storage.my_public_variable.read(); self.storage.my_public_variable.write(current + 1); // compilation error - 'write' is not available in utility } ``` ## Public State Variables[​](#public-state-variables "Direct link to Public State Variables") These are state variables that have *public* content: everyone on the network can see the values they store. They can be considered to be equivalent to Solidity state variables. ### Choosing a Public State Variable[​](#choosing-a-public-state-variable "Direct link to Choosing a Public State Variable") Public state variables are stored in the network's public storage tree and can only be written to by public contract functions. You can read *historic* values of a public state variable in a private contract function, but the current values in the network's public state tree are not accessible in private functions. This means that most public state variables cannot be read from a private function, though there are some exceptions documented in the table below. Below is a table comparing the key properties of the different public state variables that Aztec.nr offers: | State variable | Mutable? | Readable in private? | Writable in private? | Example use case | | ------------------------------------------------------------------------------------------------- | ------------------- | -------------------- | -------------------- | ---------------------------------------------------------------------------------- | | [`PublicMutable`](/aztec-nr-api/testnet/noir_aztec/state_vars/struct.PublicMutable) | yes | no | no | Configuration of admins, global state (e.g. token total supply, total votes) | | [`PublicImmutable`](/aztec-nr-api/testnet/noir_aztec/state_vars/struct.PublicImmutable) | no | yes | no | Fixed configuration, one-way actions (e.g. initialization settings for a proposal) | | [`DelayedPublicMutable`](/aztec-nr-api/testnet/noir_aztec/state_vars/struct.DelayedPublicMutable) | yes (after a delay) | yes | no | Non time sensitive system configuration | ### PublicMutable[​](#publicmutable "Direct link to PublicMutable") `PublicMutable` is the simplest kind of public state variable: a value that can be read and written. It is essentially the same as a non-`immutable` or `constant` Solidity state variable. It **cannot be read or written to privately**, but it is possible to have private functions enqueue a public call in which a `PublicMutable` is accessed. For example, a voting contract may allow private submission of votes which then enqueue a public call in which the vote count, represented as a `PublicMutable`, is incremented. This would let anyone see how many votes have been cast, while preserving the privacy of the account that cast the vote. #### Declaration[​](#declaration "Direct link to Declaration") Store mutable public state using `PublicMutable` for values that need to be updated throughout the contract's lifecycle. For example, storing the address of the collateral asset in a lending contract: public\_mutable ``` collateral_asset: PublicMutable, ``` > [Source code: noir-projects/noir-contracts/contracts/app/lending\_contract/src/main.nr#L33-L35](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/lending_contract/src/main.nr#L33-L35) #### `read`[​](#read "Direct link to read") `PublicMutable` variables have a `read` method to read the value at the location in storage: public\_mutable\_read ``` #[external("public")] #[view] fn get_assets() -> pub [AztecAddress; 2] { [self.storage.collateral_asset.read(), self.storage.stable_coin.read()] } ``` > [Source code: noir-projects/noir-contracts/contracts/app/lending\_contract/src/main.nr#L260-L266](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/lending_contract/src/main.nr#L260-L266) #### `write`[​](#write "Direct link to write") The `write` method on `PublicMutable` variables takes the value to write as an input and saves this in storage: public\_mutable\_write ``` self.storage.collateral_asset.write(collateral_asset); ``` > [Source code: noir-projects/noir-contracts/contracts/app/lending\_contract/src/main.nr#L61-L63](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/lending_contract/src/main.nr#L61-L63) ### PublicImmutable[​](#publicimmutable "Direct link to PublicImmutable") `PublicImmutable` is a simplified version of `PublicMutable`: it's a public state variable that can only be written (initialized) once, at which point it can only be read. Unlike Solidity `immutable` state variables, which must be set in the contract's constructor, a `PublicImmutable` can be initialized *at any point in time* during the contract's lifecycle. Attempts to read it prior to initialization will revert. Due to the value being immutable, you can also read it during private execution - once a circuit proves that the value was set in the past, it knows it cannot have possibly changed. This makes this state variable suitable for immutable public contract configuration or one-off public actions, such as user registration status. #### Declaration[​](#declaration-1 "Direct link to Declaration") For example, in the `Storage` struct in a simple token contract, the name, symbol, and decimals are `PublicImmutable` variables: public\_immutable ``` symbol: PublicImmutable, name: PublicImmutable, decimals: PublicImmutable, ``` > [Source code: noir-projects/noir-contracts/contracts/app/simple\_token\_contract/src/main.nr#L45-L49](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/simple_token_contract/src/main.nr#L45-L49) #### `initialize`[​](#initialize "Direct link to initialize") This function sets the immutable value. It can only be called once. public\_immutable\_initialize ``` self.storage.name.initialize(FieldCompressedString::from_string(name)); self.storage.symbol.initialize(FieldCompressedString::from_string(symbol)); self.storage.decimals.initialize(decimals); ``` > [Source code: noir-projects/noir-contracts/contracts/app/simple\_token\_contract/src/main.nr#L55-L59](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/simple_token_contract/src/main.nr#L55-L59) warning A `PublicImmutable`'s storage **must** only be set once via `initialize`. Attempting to override this by manually accessing the underlying storage slots breaks all properties of the data structure, rendering it useless. #### `read`[​](#read-1 "Direct link to read-1") Returns the stored immutable value. This function is available in public, private and utility contexts. public\_immutable\_read ``` #[external("public")] #[view] fn public_get_name() -> FieldCompressedString { self.storage.name.read() } ``` > [Source code: noir-projects/noir-contracts/contracts/app/simple\_token\_contract/src/main.nr#L62-L68](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/simple_token_contract/src/main.nr#L62-L68) ### DelayedPublicMutable[​](#delayedpublicmutable "Direct link to DelayedPublicMutable") It is sometimes necessary to read public mutable state in private. For example, a decentralized exchange might have a configurable swap fee that some admin sets, but which needs to be read by users in their private swaps. This is where `DelayedPublicMutable` comes in. `DelayedPublicMutable` is the same as a `PublicMutable` in that it is a public value that can be read and written, but with a caveat: writes only take effect *after some time delay*. These delays are configurable, but they're typically on the order of a couple hours, if not days, making this state variable unsuitable for actions that must be executed immediately - such as an emergency shutdown. It is these very delays that enable private contract functions to *read the current value of a public state variable*, which is otherwise typically impossible. The existence of minimum delays means that a private function that reads a public value at an anchor block has a guarantee that said historical value will remain the current value until *at least* some time in the future - before the delay elapses. As long as the transaction gets included in a block before that time (by using the `expiration_timestamp` tx property), the read value is valid. #### Declaration[​](#declaration-2 "Direct link to Declaration") Unlike other state variables, `DelayedPublicMutable` receives not only a type parameter for the underlying datatype, but also a `DELAY` type parameter with the value change delay as a number of seconds. delayed\_public\_mutable\_storage ``` // Authorizing a new address has a certain delay before it goes into effect. Set to 360 seconds which is 5 slots. pub(crate) global CHANGE_AUTHORIZED_DELAY: u64 = 360; #[storage] struct Storage { // Admin can change the value of the authorized address via set_authorized() admin: PublicImmutable, authorized: DelayedPublicMutable, } ``` > [Source code: noir-projects/noir-contracts/contracts/app/auth\_contract/src/main.nr#L16-L26](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/auth_contract/src/main.nr#L16-L26) #### `schedule_value_change`[​](#schedule_value_change "Direct link to schedule_value_change") This is the means by which a `DelayedPublicMutable` variable mutates its contents. It schedules a value change for the variable at a future timestamp after the `DELAY` has elapsed. schedule\_value\_change ``` #[external("public")] fn set_authorized(authorized: AztecAddress) { assert_eq(self.storage.admin.read(), self.msg_sender(), "caller is not admin"); self.storage.authorized.schedule_value_change(authorized); } ``` > [Source code: noir-projects/noir-contracts/contracts/app/auth\_contract/src/main.nr#L35-L41](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/auth_contract/src/main.nr#L35-L41) #### `get_current_value`[​](#get_current_value "Direct link to get_current_value") Returns the current value in a public, private or utility execution context. get\_current\_value ``` #[external("public")] #[view] fn get_authorized() -> AztecAddress { self.storage.authorized.get_current_value() } ``` > [Source code: noir-projects/noir-contracts/contracts/app/auth\_contract/src/main.nr#L43-L49](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/auth_contract/src/main.nr#L43-L49) Privacy Consideration Reading `DelayedPublicMutable` in private sets the `expiration_timestamp` property, which may reveal timing information. Choose delays that align with common values to maximize privacy sets. #### `get_scheduled_value`[​](#get_scheduled_value "Direct link to get_scheduled_value") Returns the scheduled value and when it takes effect: get\_scheduled\_value ``` #[external("public")] #[view] fn get_scheduled_authorized() -> (AztecAddress, u64) { self.storage.authorized.get_scheduled_value() } ``` > [Source code: noir-projects/noir-contracts/contracts/app/auth\_contract/src/main.nr#L51-L57](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/auth_contract/src/main.nr#L51-L57) ## Private State Variables[​](#private-state-variables "Direct link to Private State Variables") Private state variables have *private* content meaning that only some people know what is stored in them. These work *very* differently from public state variables and are unlike anything in languages such as Solidity, since they are built from fundamentally different primitives (UTXO-based notes and nullifiers instead of a key-value updatable public database). Aztec.nr provides three private state variable types: * `Owned, Context>`: Single mutable private value * `Owned, Context>`: Single immutable private value * `Owned, Context>`: Collection of private notes These private state variables are "owned" and must be wrapped in the `Owned<>` container, which enables owner-specific access via the `.at(owner)` method. Each also requires a `NoteType`. To understand this, let's go through notes and nullifiers and how they can be used so we can understand how private state works. ### Notes and Nullifiers[​](#notes-and-nullifiers "Direct link to Notes and Nullifiers") Just as public state is stored in a single public data tree (equivalent to the `key-value` store used for state on the EVM), private state is managed using two separate trees: * **The note hash tree**: stores hashes of the private data, called notes, which are just structs containing private data with some methods. * **The nullifier tree**: the nullifier for a certain note is deterministic, and the presence of the nullifier in the nullifier tree determines that the note has been spent/used. Understanding these primitives and how they can be used is key to understanding how private state works. #### Notes[​](#notes "Direct link to Notes") Notes are user-defined data that can be stored privately on the blockchain. A note can represent any private data, such as an amount (e.g., some token balance), an ID (e.g., a vote proposal ID), or an address (e.g., an authorized account). They also have some metadata, including a storage slot to avoid collisions with other notes, a `randomness` value that helps hide the content, and an `owner` who can nullify the note. The note content plus the metadata are all hashed together, and it is this hash that gets stored onchain in the note hash tree. This hash is called a commitment. The underlying note content (the note hash preimage) is not stored anywhere onchain, so third parties cannot access it and it remains private. The note hash tree is append-only - if it wasn't, when a note was spent, external observers would notice that the tree leaf inserted in some transaction was modified in a second transaction, linking them together and leaking privacy. For example, when a user made a payment to a third party, the recipient would be able to know when they spent the received funds. Nullifiers exist to solve this issue. Note: Aztec.nr comes with some prebuilt note types, including [`UintNote`](https://github.com/AztecProtocol/aztec-packages/tree/v5.0.0-rc.2/noir-projects/aztec-nr/uint-note) and [`AddressNote`](https://github.com/AztecProtocol/aztec-packages/tree/v5.0.0-rc.2/noir-projects/aztec-nr/address-note), but users are also free to create their own with the `#[note]` macro. ##### Note Lifecycle[​](#note-lifecycle "Direct link to Note Lifecycle") Notes are more complicated than public state, and so it helps to see the different stages one goes through, and when and where each stage happens: * **Creation**: an account executing a private contract function creates a new note according to contract logic, e.g., transferring tokens to a recipient. Note values (e.g., a token amount) and metadata are set, the note hash is computed, and inserted as one of the effects of the transaction. * **Encryption**: the content of the note is encrypted with a key only the sender and intended recipient know - no other account can decrypt this message. * **Delivery**: the encrypted message is delivered to the recipient via some means. Options include storing it onchain as a transaction log, or sending it offchain, e.g., via email or by having the recipient scan a QR code on the sender's device. * **Insertion**: the transaction is sent to the network and gets included in a block. The note hash is inserted into the note hash tree - this is visible to the entire network, but the content of the note remains private. * **Discovery**: the recipient processes the encrypted message they were sent, decrypting it and finding the note's content (i.e., the hash preimage). They verify that the note's hash exists onchain in the note hash tree. They store the note's content in their own private database and can now spend the note. * **Reading**: while executing a private contract function, the recipient fetches the note's content and metadata from their private database (in their PXE) and shows that its hash exists in the note hash tree as part of the zero-knowledge proof. * **Nullification**: the recipient computes the note's nullifier and inserts it as one of the effects of the transaction, preventing the note from being read again. #### Nullifiers[​](#nullifiers "Direct link to Nullifiers") A nullifier is a value which indicates a resource has been spent. Nullifiers are unique and stored onchain in the nullifier tree. The protocol forbids the same nullifier from being inserted into the tree twice. Spending the same resource therefore results in a duplicate nullifier, which invalidates the transaction. The nullifier tree is **append-only** for the same reason that the note hash tree is append-only. Most often, nullifiers are used to mark a note as being spent, which prevents note double spends. This requires two properties from the function that computes a note's nullifier: * **Deterministic**: the nullifier **must** be deterministic given a note, so that the same nullifier value is computed every time the note is attempted to be spent. A non-deterministic nullifier would result in a note being spendable more than once because the nullifiers would not be duplicates. * **Secret**: the nullifier **must** not be computable by anyone except the owner, *even by someone who knows the full note content*. This is because some third parties *do* know the note content: when paying someone and creating a note for them, the payer creates the note on their device and thus has access to all of its data and metadata. There are multiple ways to compute nullifiers that fulfill this property, but typically they are computed as a **hash of the note contents concatenated with a private key of the note's owner**. These values are **immutable**, and only the owner knows their private keys, ensuring both determinism and secrecy. These nullifiers are sometimes called 'zcash-style nullifiers' because this is the format ZCash uses for their note nullifiers. ### Note Messages and Discovery[​](#note-messages-and-discovery "Direct link to Note Messages and Discovery") Because notes are private, not even the intended recipient is aware of their existence, and therefore they must be somehow notified. For example, when making a payment and creating a note for the payee with the intended amount, they must be shown the preimage of the note that was inserted in the note hash tree in a given transaction in order to acknowledge the payment. Recipients learning about notes created for them is known as 'note discovery', which is a process Aztec.nr handles efficiently and automatically. However, it does mean that when a note is created, a *message* with the content of the note is created and needs to be delivered to a recipient via one of multiple means detailed below. When working with private state variables, many operations return a `NoteMessage` type rather than the note directly. This is a type-safe wrapper that ensures you explicitly decide how to deliver the note to its recipient. #### Delivery Methods[​](#delivery-methods "Direct link to Delivery Methods") Private notes need to be communicated to their recipients so they know the note exists and can use it. The [`NoteMessage`](/aztec-nr-api/testnet/noir_aztec/note/struct.NoteMessage) wrapper forces you to make an explicit choice about how this happens: * [`MessageDelivery::onchain_constrained()`](/aztec-nr-api/testnet/noir_aztec/messages/delivery/global.MessageDelivery): Verified in the circuit (most secure, but highest cost) - Use when the sender cannot be trusted to deliver correctly (e.g., protocol fees, multisig config updates). * [`MessageDelivery::onchain_unconstrained()`](/aztec-nr-api/testnet/noir_aztec/messages/delivery/global.MessageDelivery): Message stored onchain but no guarantees on content - Use when the sender is incentivized to deliver correctly but may not have an offchain channel to the recipient. * [`MessageDelivery::offchain()`](/aztec-nr-api/testnet/noir_aztec/messages/delivery/global.MessageDelivery): Lowest cost, no onchain data - Use when the sender and recipient can communicate and the sender is incentivized to deliver correctly. note\_delivery ``` #[external("private")] fn mint(amount: u128, recipient: AztecAddress) { let replacement_note_message = self.storage.admin.get_note(); let admin = replacement_note_message.get_note().address; assert(admin == self.msg_sender(), "Only admin can mint"); // We deliver the new note message to the admin using unconstrained delivery, since the admin is motivated to // deliver the message to themselves (hence no need to constrain it). replacement_note_message.deliver(MessageDelivery::onchain_unconstrained()); // We increase the total supply and once again use unconstrained delivery, since the admin is motivated to // deliver the message (he's the owner of the new note as well). self.storage.total_supply.replace(|current| UintNote { value: current.value + amount }, admin).deliver( MessageDelivery::onchain_unconstrained(), ); // At last we mint the tokens to the recipient. self.storage.balances.at(recipient).add(amount).deliver(MessageDelivery::onchain_constrained()); } ``` > [Source code: noir-projects/noir-contracts/contracts/app/private\_token\_contract/src/main.nr#L48-L67](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/private_token_contract/src/main.nr#L48-L67) Methods that return `NoteMessage` include `initialize()`, `get_note()`, and `replace()` on `PrivateMutable`, `initialize()` on `PrivateImmutable`, and `insert()` on `PrivateSet` (more on these methods and private state variable types shortly). ### How Aztec.nr Abstracts Private State Variables[​](#how-aztecnr-abstracts-private-state-variables "Direct link to How Aztec.nr Abstracts Private State Variables") Implementing a private state variable requires careful coordination of multiple primitives and concepts (creating notes, encrypting, delivering, discovering and processing messages, reading notes, and computing their nullifiers). Aztec.nr provides convenient types and functions that handle all of these low-level details to allow developers to write safe code without having to understand the nitty-gritty. By applying the `#[note]` [macro](/aztec-nr-api/testnet/noir_aztec/macros/notes/fn.note) to a [noir struct](https://noir-lang.org/docs/noir/concepts/data_types/structs), users can define values that will be storable in notes. Private state variables can then hold these notes and be used to read, write, and deliver note messages to the intended recipient. note Advanced users can change this default behavior by either defining their [own custom note](/developers/testnet/docs/aztec-nr/framework-description/custom_notes.md) hash and nullifier functions, implementing their own state variables, or even accessing the note hash and nullifiers tree directly. The snippet below shows a contract with two private state variables: an admin address (stored in an `AddressNote`) and a counter of how many calls the admin has made (stored in a `UintNote`). These values will be private and therefore not known except by the accounts that own these notes (the admin). In the `perform_admin_action` private function, the contract checks that it is being called by the correct admin and updates the call count by incrementing it by one. (Note that this is not a real snippet, it's missing some small irrelevant details - but the gist of it is correct) ``` #[note] struct AddressNote { value: AztecAddress, } #[note] struct UintNote { value: u128, } #[storage] struct Storage { admin: Owned, Context>, admin_call_count: Owned, Context>, } #[external("private")] fn perform_admin_action() { // Read the contract's admin address and check against the caller let admin = self.storage.admin.get_note().value; assert(self.msg_sender() == admin); // Update the call count by replacing (updating - rename soon) the current note with a new one that equals the // current value + 1 - this requires knowing what the current value is in the first place, i.e., reading the variable. // // We then deliver the encrypted message with the note's content to the admin so that they become aware of the new // value of the counter and can update it again in the future. self.storage.admin_call_count .replace(|current| UintNote{ value: current.value + 1 }) // wouldn't it be great if we didn't have to deal with this wrapping and unwrapping? .deliver(MessageDelivery::onchain_constrained()); // ... } ``` ### Choosing a Private State Variable[​](#choosing-a-private-state-variable "Direct link to Choosing a Private State Variable") Due to the complexities of Aztec's private state model, private state variables do not map 1:1 with public state variables. Understanding these differences between the different private state variables is important when it comes to designing private smart contracts. Below is a table comparing certain key properties of the different private state variables Aztec.nr offers: | State variable | Mutable? | Cost to read? | Writable by third parties? | Example use case | | ----------------------------------------------------------------------------------------- | -------- | ------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------- | | [`PrivateMutable`](/aztec-nr-api/testnet/noir_aztec/state_vars/struct.PrivateMutable) | yes | yes | no | Mutable user state only accessible by them (e.g. user settings or keys) | | [`PrivateImmutable`](/aztec-nr-api/testnet/noir_aztec/state_vars/struct.PrivateImmutable) | no | no | no | Fixed configuration, one-way actions (e.g. initialization settings for a proposal) | | [`PrivateSet`](/aztec-nr-api/testnet/noir_aztec/state_vars/struct.PrivateSet) | yes | yes | yes | Aggregated state others can add to, e.g. token balance (set of amount notes), nft collections (set of nft ids) | ### Owned State Variables[​](#owned-state-variables "Direct link to Owned State Variables") Private state variables like `PrivateMutable`, `PrivateImmutable`, and `PrivateSet` implement the `OwnedStateVariable` trait. You must wrap them in `Owned`. Access the underlying state variable for a specific owner using `.at(owner)` ### PrivateMutable[​](#privatemutable "Direct link to PrivateMutable") `PrivateMutable` is conceptually similar to `PublicMutable` and regular Solidity state variables in that it is a variable that has exactly one value at any point in time that can be read and written. However, for `PrivateMutable`: * The value is, of course, *private*, meaning only the account the value belongs to can read it. * *Only ONE account can read and write the state variable*. It is not possible, for example, to use a `PrivateMutable` to store user settings and then have some admin account alter these settings. * Reading the current value results in the state variable being updated, increasing tx costs and requiring delivery of a note message. * There is no `write` function - the current value is instead `replace`d. #### Declaration[​](#declaration-3 "Direct link to Declaration") owned\_private\_mutable ``` subscriptions: Owned, Context>, ``` > [Source code: noir-projects/noir-contracts/contracts/app/app\_subscription\_contract/src/main.nr#L60-L62](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/app_subscription_contract/src/main.nr#L60-L62) #### `is_initialized`[​](#is_initialized "Direct link to is_initialized") An unconstrained method to check whether the `PrivateMutable` has been initialized or not: owned\_private\_mutable\_is\_initialized ``` #[external("utility")] unconstrained fn is_initialized(subscriber: AztecAddress) -> bool { self.storage.subscriptions.at(subscriber).is_initialized() } ``` > [Source code: noir-projects/noir-contracts/contracts/app/app\_subscription\_contract/src/main.nr#L157-L162](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/app_subscription_contract/src/main.nr#L157-L162) #### `initialize` and `initialize_or_replace`[​](#initialize-and-initialize_or_replace "Direct link to initialize-and-initialize_or_replace") The `PrivateMutable` should be initialized to create the first note and value. This can be done with either `initialize` or `initialize_or_replace`: owned\_private\_mutable\_initialize ``` self .storage .subscriptions .at(subscriber) .initialize_or_replace(|_| SubscriptionNote { expiry_block_number, remaining_txs: tx_count }) .deliver(MessageDelivery::onchain_constrained()); ``` > [Source code: noir-projects/noir-contracts/contracts/app/app\_subscription\_contract/src/main.nr#L147-L154](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/app_subscription_contract/src/main.nr#L147-L154) #### `get_note`[​](#get_note "Direct link to get_note") This function allows us to get the note of a `PrivateMutable`, essentially reading the value: ``` #[external("private")] fn read_settings() { let owner = self.msg_sender(); self.storage.user_settings.at(owner).get_note().deliver(MessageDelivery::onchain_constrained()); } ``` info To ensure that a user's private execution always uses the latest value of a `PrivateMutable`, the `get_note` function will nullify the note that it is reading. This means that if two people are trying to use this function with the same note, only one will succeed. Reading a `PrivateMutable` nullifies and recreates the note. This makes reads indistinguishable from writes and ensures the sequencer cannot learn the note's value. #### `replace`[​](#replace "Direct link to replace") To update the value of a `PrivateMutable`, we can use the `replace` method: owned\_single\_private\_mutable\_replace ``` #[external("private")] fn transfer_admin(new_admin: AztecAddress) { self .storage .admin .replace( |old| { assert(old.address == self.msg_sender(), "Only admin can transfer admin privileges"); AddressNote { address: new_admin } }, new_admin, ) .deliver(MessageDelivery::onchain_constrained()); } ``` > [Source code: noir-projects/noir-contracts/contracts/app/private\_token\_contract/src/main.nr#L70-L85](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/private_token_contract/src/main.nr#L70-L85) ### PrivateImmutable[​](#privateimmutable "Direct link to PrivateImmutable") `PrivateImmutable` represents a unique private state variable that, as the name suggests, is immutable. Once initialized, its value cannot be altered. This is the private equivalent of `PublicImmutable`, except the value is only known to its owner. Unlike `PrivateMutable`, the `get_note` function for a `PrivateImmutable` doesn't nullify the current note and returns the `Note` directly (not wrapped in `NoteMessage`). This means that multiple accounts can concurrently call this function to read the value. #### Declaration[​](#declaration-4 "Direct link to Declaration") private\_immutable ``` note_in_private_immutable: Owned, Context>, ``` > [Source code: noir-projects/noir-contracts/contracts/test/test\_contract/src/main.nr#L81-L83](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/test/test_contract/src/main.nr#L81-L83) `PrivateImmutable` variables also have the `initialize` and `get_note` functions on them but no `initialize_or_replace` since they cannot be modified. ### PrivateSet[​](#privateset "Direct link to PrivateSet") `PrivateSet` is used for managing a collection of notes. Like `PrivateMutable`, this is a private state variable that can be modified. There are two key differences: * A `PrivateSet` is not a single value but a *set* (a collection) of values (represented by notes) * Any account can insert values into someone else's set. The set's current value is the collection of notes in the set that have not yet been nullified. These notes can have any type: they could be NFT IDs representing a user's NFT collection, or they might be token amounts, in which case *the sum* of all values in the set would be the user's current balance. #### Declaration[​](#declaration-5 "Direct link to Declaration") For example, to add private token balances to storage: private\_set ``` #[storage] struct Storage { balances: Owned, Context>, } ``` > [Source code: noir-projects/noir-contracts/contracts/test/pending\_note\_hashes\_contract/src/main.nr#L27-L32](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/test/pending_note_hashes_contract/src/main.nr#L27-L32) #### `insert`[​](#insert "Direct link to insert") Allows us to modify the storage by inserting a note into the `PrivateSet`: private\_set\_insert ``` owner_balance.insert(note).deliver(MessageDelivery::onchain_unconstrained()); ``` > [Source code: noir-projects/noir-contracts/contracts/test/pending\_note\_hashes\_contract/src/main.nr#L47-L49](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/test/pending_note_hashes_contract/src/main.nr#L47-L49) Note: The `Owned` wrapper requires calling `.at(owner)` to access the underlying `PrivateSet` for a specific owner. This binds the owner to the state variable instance. #### `get_notes`[​](#get_notes "Direct link to get_notes") Retrieves notes the account has access to. You can optionally provide filtering options. Returns `ConfirmedNote` instances: private\_set\_get\_notes ``` let options = NoteGetterOptions::with_filter(filter_notes_min_sum, amount); // get note (note inserted at bottom of function shouldn't exist yet) let notes = owner_balance.get_notes(options); ``` > [Source code: noir-projects/noir-contracts/contracts/test/pending\_note\_hashes\_contract/src/main.nr#L66-L70](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/test/pending_note_hashes_contract/src/main.nr#L66-L70) #### `pop_notes`[​](#pop_notes "Direct link to pop_notes") This function pops (gets, removes and returns) the notes the account has access to. Unlike `get_notes`, this immediately nullifies the notes and returns them directly (not wrapped in `ConfirmedNote`): private\_set\_pop\_notes ``` let options = NoteGetterOptions::new().set_limit(1); let note = owner_balance.pop_notes(options).get(0); ``` > [Source code: noir-projects/noir-contracts/contracts/test/pending\_note\_hashes\_contract/src/main.nr#L146-L149](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/test/pending_note_hashes_contract/src/main.nr#L146-L149) #### `remove`[​](#remove "Direct link to remove") Will remove a note from the `PrivateSet` if it previously has been read from storage. Takes a `ConfirmedNote` as returned by `get_notes`: ``` let options = NoteGetterOptions::new(); let confirmed_notes = self.storage.balances.at(owner).get_notes(options); // ... select a note to remove ... self.storage.balances.at(owner).remove(confirmed_notes.get(0)); ``` Note that if you obtained the note via `get_notes`, it's much better to use `pop_notes`, as `pop_notes` results in significantly fewer constraints due to avoiding an extra hash and read request check. ### SinglePrivateMutable and SinglePrivateImmutable[​](#singleprivatemutable-and-singleprivateimmutable "Direct link to SinglePrivateMutable and SinglePrivateImmutable") For contract-wide private values (not per-owner), use `SinglePrivateMutable` or `SinglePrivateImmutable`. These store exactly one value for the entire contract - a global singleton - rather than separate values per owner. | Type | Use Case | Access Pattern | | ---------------------------- | --------------------------------------- | ----------------------- | | `Owned>` | Per-owner private state (like balances) | `.at(owner).get_note()` | | `SinglePrivateMutable` | Contract-wide singleton (like admin) | `.get_note()` directly | Since there's only one value at the storage slot, there's no need to specify an owner to look it up: ``` #[storage] struct Storage { admin: SinglePrivateMutable, config: SinglePrivateImmutable, } // Access directly without .at(owner) let note_message = self.storage.admin.get_note(); let config = self.storage.config.get_note(); ``` When initializing, you still pass an owner address, but this specifies who can decrypt the note, not the storage location: ``` // owner_address determines who can see the note, not where it's stored self.storage.admin.initialize(note, owner_address).deliver(MessageDelivery::onchain_constrained()); ``` warning `SinglePrivateMutable` uses a nullify-and-recreate pattern when reading. Unless the caller is incentivized to deliver the note message correctly, you should use `MessageDelivery::onchain_constrained()` to prevent malicious actors from bricking the contract by failing to deliver the note. ## Containers[​](#containers "Direct link to Containers") ### Map[​](#map "Direct link to Map") A `Map` is a key-value container that maps keys to state variables - just like Solidity's `mapping`. It can be used with any state variable to create independent instances for each key. For example, a `Map>` can be accessed with an address to obtain the `PublicMutable` that corresponds to it. This is exactly equivalent to a Solidity `mapping (address => uint)`. #### Declaration[​](#declaration-6 "Direct link to Declaration") ``` #[storage] struct Storage { // Map of addresses to public balances public_balances: Map, Context>, // Map of addresses to authorized users authorized_users: Map, Context>, } ``` #### Usage[​](#usage "Direct link to Usage") Use the `.at()` method to access values by key: ``` #[external("public")] fn increase_balance(account: AztecAddress, amount: u128) { let current = self.storage.public_balances.at(account).read(); self.storage.public_balances.at(account).write(current + amount); } ``` note Maps can only be used with public state variables (`PublicMutable`, `PublicImmutable`, `DelayedPublicMutable`) or other `Map`s. For private state, use the `Owned` wrapper described above. ### Owned[​](#owned "Direct link to Owned") The `Owned` wrapper is used with private state variables (`PrivateMutable`, `PrivateImmutable`, and `PrivateSet`) to associate them with a specific owner. This is necessary because private state variables need to know which address owns the notes they manage. #### Declaration[​](#declaration-7 "Direct link to Declaration") ``` #[storage] struct Storage { // Single owner's private balance balances: Owned, Context>, // Single owner's private settings user_settings: Owned, Context>, } ``` #### Usage[​](#usage-1 "Direct link to Usage") Use the `.at(owner)` method to access the underlying state variable for a specific owner: ``` #[external("private")] fn transfer(from: AztecAddress, to: AztecAddress, amount: u128) { // Access the balance for the 'from' address let options = NoteGetterOptions::new(); let notes = self.storage.balances.at(from).pop_notes(options); // Access the balance for the 'to' address let new_note = UintNote { value: amount }; self.storage.balances.at(to).insert(new_note).deliver(MessageDelivery::onchain_unconstrained()); } ``` The `Owned` wrapper is essential for private state variables because it binds the owner's address to the state variable instance, enabling proper note encryption, nullifier computation, and access control. ## Custom Structs in Public Storage[​](#custom-structs-in-public-storage "Direct link to Custom Structs in Public Storage") Both `PublicMutable` and `PublicImmutable` are generic over any serializable type, which means you can store custom structs in public storage. ### Define a Custom Struct[​](#define-a-custom-struct "Direct link to Define a Custom Struct") To use a custom struct in public storage, it must implement the `Packable` trait: ``` use aztec::protocol::{ address::AztecAddress, traits::{Deserialize, Packable, Serialize} }; #[derive(Deserialize, Packable, Serialize)] pub struct Asset { pub interest_accumulator: u128, pub last_updated_ts: u64, pub loan_to_value: u128, pub oracle: AztecAddress, } ``` ### Store and Use Custom Structs[​](#store-and-use-custom-structs "Direct link to Store and Use Custom Structs") ``` #[storage] struct Storage { assets: Map, Context>, } #[external("public")] fn update_asset(asset_id: Field, new_accumulator: u128) { let mut asset = self.storage.assets.at(asset_id).read(); asset.interest_accumulator = new_accumulator; self.storage.assets.at(asset_id).write(asset); } ``` ## Storage Slots[​](#storage-slots "Direct link to Storage Slots") Each state variable gets assigned a different numerical value for their **storage slot**. How they are used depends on the kind of state variable: * For public state variables, storage slots are related to slots in the public data tree * For private state variables, storage slots are metadata that gets included in the note hash The purpose of slots is the same for both domains: they keep the values of different state variables *separate* so that they do not interfere with one another. Storage slots are a low-level detail that developers don't typically need to concern themselves with. They are automatically allocated to each state variable by Aztec.nr. Utilizing storage slots directly can be dangerous as it may accidentally result in data collisions across state variables or invariants being broken. In some advanced use cases, it can be useful to have access to these low-level details, such as when implementing [contract upgrades](/developers/testnet/docs/aztec-nr/framework-description/contract_upgrades.md) or when interacting with protocol contracts. --- # Noir VSCode Extension Install the [Noir Language Support extension](https://marketplace.visualstudio.com/items?itemName=noir-lang.vscode-noir) to get syntax highlighting, syntax error detection, and go-to definitions for your Aztec contracts. The extension drives its language server with `nargo`. The Aztec installer ships a bundled `nargo` and exposes it as the `aztec-nargo` symlink on your `PATH`. Bare `nargo` is intentionally not provided so it does not shadow your own install (if any). Verify the symlink is on your `PATH`: ``` which aztec-nargo # expected: $HOME/.aztec/current/bin/aztec-nargo ``` If you have not installed the Aztec toolchain yet, follow [Getting Started on Local Network](/developers/testnet/getting_started_on_local_network.md) first. ## Configure the extension[​](#configure-the-extension "Direct link to Configure the extension") Set the extension's `Noir: Nargo Path` setting to the absolute path printed by `which aztec-nargo` (for example `$HOME/.aztec/current/bin/aztec-nargo`), then reload the window. `aztec-nargo` is a symlink to the bundled `nargo`, so any tool that invokes it speaks plain `nargo` (LSP included). To confirm the extension is using the bundled toolchain, hover over **Nargo** in the VSCode status bar in the bottom right corner: it should show the path you set. If you have your own `nargo` install and want the extension to use that instead, leave `Noir: Nargo Path` empty so the extension auto-discovers `nargo` from your `PATH`. ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") * **LSP reports `startFailed` after setting a custom path**: confirm `aztec-nargo` is executable and that the path is correct, reload the window, and check the **Output** panel for the language server log. * **Extension picks up the wrong `nargo`**: the Aztec installer no longer puts bare `nargo` on `PATH`. Set `Noir: Nargo Path` explicitly to `aztec-nargo` (for the bundled version) or to your own install (for any other version). --- # Logging from Contracts Aztec contracts can emit log messages at seven severity levels. Private function logs appear immediately during local simulation in the Private eXecution Environment (PXE), while public function logs are collected and displayed in test mode. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * An Aztec contract project set up with the `aztec-nr` dependency * Basic understanding of [private, public, and utility functions](/developers/testnet/docs/aztec-nr/framework-description/functions/visibility.md) ## Import the logging functions[​](#import-the-logging-functions "Direct link to Import the logging functions") Logging functions live under `aztec::oracle::logging`. Import the specific functions you need: logging\_imports ``` use aztec::oracle::logging::{ debug_log, debug_log_format, error_log, error_log_format, fatal_log, fatal_log_format, info_log, info_log_format, trace_log, trace_log_format, verbose_log, verbose_log_format, warn_log, warn_log_format, }; ``` > [Source code: docs/examples/contracts/logging\_example/src/main.nr#L5-L11](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/logging_example/src/main.nr#L5-L11) Or import only what you need: ``` use aztec::oracle::logging::{info_log, debug_log, debug_log_format}; ``` Old import path removed The previous import path `dep::aztec::oracle::debug_log` has been removed. Update your imports to use `aztec::oracle::logging` instead. ## Log levels[​](#log-levels "Direct link to Log levels") Aztec supports seven log levels, ordered from least to most verbose: | Level | Value | When to use | | --------- | ----- | -------------------------------------------------- | | `fatal` | 1 | Unrecoverable errors that should always be visible | | `error` | 2 | Recoverable errors or unexpected conditions | | `warn` | 3 | Potential issues worth investigating | | `info` | 4 | General operational information | | `verbose` | 5 | Detailed information for troubleshooting | | `debug` | 6 | Development-time debugging output | | `trace` | 7 | Fine-grained tracing of execution flow | When you set `LOG_LEVEL=info`, you see `fatal`, `error`, `warn`, and `info` messages, but `verbose`, `debug`, and `trace` are hidden. Here is an example using all seven levels: log\_all\_levels ``` #[external("private")] fn log_all_levels(value: Field) { fatal_log("fatal level message"); fatal_log_format("fatal: {0}", [value]); error_log("error level message"); error_log_format("error: {0}", [value]); warn_log("warn level message"); warn_log_format("warn: {0}", [value]); info_log("info level message"); info_log_format("info: {0}", [value]); verbose_log("verbose level message"); verbose_log_format("verbose: {0}", [value]); debug_log("debug level message"); debug_log_format("debug: {0}", [value]); trace_log("trace level message"); trace_log_format("trace: {0}", [value]); } ``` > [Source code: docs/examples/contracts/logging\_example/src/main.nr#L48-L66](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/logging_example/src/main.nr#L48-L66) ## Simple log messages[​](#simple-log-messages "Direct link to Simple log messages") Each level has a function that accepts a plain string with no format arguments: log\_simple ``` // Simple messages (no arguments) info_log("Private function called"); debug_log("Checkpoint reached in private function"); ``` > [Source code: docs/examples/contracts/logging\_example/src/main.nr#L23-L27](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/logging_example/src/main.nr#L23-L27) ## Log messages with format arguments[​](#log-messages-with-format-arguments "Direct link to Log messages with format arguments") Each level also has a `_format` variant that accepts a format string and an array of `Field` values. Use `{0}`, `{1}`, etc. to insert individual arguments by index, or `{}` to print the entire array: log\_format\_patterns ``` #[external("private")] fn log_format_patterns(a: Field, b: Field, c: Field) { // Single indexed argument debug_log_format("First value: {0}", [a]); // Multiple indexed arguments info_log_format("Values: {0}, {1}, {2}", [a, b, c]); // Whole array dump debug_log_format("All values: {}", [a, b, c]); } ``` > [Source code: docs/examples/contracts/logging\_example/src/main.nr#L68-L80](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/logging_example/src/main.nr#L68-L80) note Format arguments must be `Field` values. Use `.to_field()` to convert addresses and other types: log\_address ``` #[external("private")] fn log_with_address(sender: AztecAddress) { info_log_format("Sender: {0}", [sender.to_field()]); } ``` > [Source code: docs/examples/contracts/logging\_example/src/main.nr#L33-L38](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/logging_example/src/main.nr#L33-L38) ## Viewing logs[​](#viewing-logs "Direct link to Viewing logs") ### In `aztec test` (Noir tests)[​](#in-aztec-test-noir-tests "Direct link to in-aztec-test-noir-tests") To see contract logs, set `LOG_LEVEL` to include the `debug_log` module: ``` LOG_LEVEL="error;trace:debug_log" aztec test ``` tip Use different log levels strategically: add `info_log` calls for key state transitions you always want to see, and `debug_log` or `trace_log` calls for detailed inspection. ### In TypeScript tests (jest, vitest)[​](#in-typescript-tests-jest-vitest "Direct link to In TypeScript tests (jest, vitest)") TypeScript test environments do not enable contract logs by default. Set the `LOG_LEVEL` environment variable to include the `contract_log` module: ``` # Show contract logs at debug level and above LOG_LEVEL="info;debug:contract_log" yarn test # Show all contract log levels (most verbose) LOG_LEVEL="error;trace:contract_log" yarn test ``` ### With a local network[​](#with-a-local-network "Direct link to With a local network") Contract logs appear in the process that runs the PXE — your test process, not the network process. The network window only shows system-level infrastructure logs (archiver, world-state, etc.), which are generally not useful for contract debugging. When running TypeScript tests against a local network, set `LOG_LEVEL` on the **test command**: ``` # Your test process sees contract logs LOG_LEVEL="error;trace:contract_log" yarn test ``` You do not need to change the `LOG_LEVEL` on `aztec start --local-network` to see contract logs. ## `LOG_LEVEL` syntax reference[​](#log_level-syntax-reference "Direct link to log_level-syntax-reference") The `LOG_LEVEL` environment variable uses a semicolon-delimited format: ``` ;:,;: ``` * **First segment (required)**: the default log level for all modules. A bare `level:module` with no preceding default (e.g. `LOG_LEVEL="warn:simulator"`) is invalid and throws `Invalid log level` — the parser always reads the segment before the first `;` as the default level. To filter only specific modules, start with `silent` (e.g. `LOG_LEVEL="silent;debug:simulator"`) * **Remaining segments**: `level:module` pairs that override the default for specific modules * Modules are comma-separated within a segment * The `aztec:` prefix is automatically stripped from module names * Module names support regex or prefix matching ### Common configurations[​](#common-configurations "Direct link to Common configurations") | Scenario | `LOG_LEVEL` value | | --------------------------------------- | -------------------------------------------------- | | Contract logs in `aztec test` (TXE) | `error;trace:debug_log` | | Contract logs in TypeScript tests (PXE) | `error;trace:contract_log` | | Contract debug+ logs with system info | `info;debug:contract_log` | | Only contract warnings and errors | `warn;warn:contract_log` | | Everything verbose | `verbose` | | Debug a specific system module | `info;debug:sequencer` | | Multiple module overrides | `warn;debug:sequencer,archiver;trace:contract_log` | ## How contract logs are displayed[​](#how-contract-logs-are-displayed "Direct link to How contract logs are displayed") ### In `aztec test` (TXE)[​](#in-aztec-test-txe "Direct link to in-aztec-test-txe") Contract logs appear under the `debug_log` module: ``` [07:40:29.947] DEBUG: txe:top_level_context:debug_log your message here ``` ### In TypeScript tests (PXE)[​](#in-typescript-tests-pxe "Direct link to In TypeScript tests (PXE)") When running through the PXE, the output includes the contract name with an abbreviated address: ``` [07:40:29.937] INFO: contract_log::Counter(0x1234abcd) 164f9c87bca0cf8c Transfer completed [07:40:29.947] DEBUG: contract_log::Counter(0x1234abcd) 164f9c87bca0cf8c Processing value: 0x2a ``` The hex value after the address (`164f9c87bca0cf8c`) is an internal request identifier. If the contract name cannot be resolved, you see `Unknown` in its place. ## Logging in public functions[​](#logging-in-public-functions "Direct link to Logging in public functions") Private and public functions handle logging differently: * **Private functions** execute locally in the PXE. You see logs immediately during simulation. * **Public functions** execute on the sequencer. You see logs during `.simulate()` calls and after `.send().wait()` completes in test mode. The logging API is the same in public functions: log\_public ``` #[external("public")] fn log_public(value: Field) { info_log("Public function called"); debug_log_format("Public value: {0}", [value]); } ``` > [Source code: docs/examples/contracts/logging\_example/src/main.nr#L40-L46](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/logging_example/src/main.nr#L40-L46) ### Accessing logs programmatically[​](#accessing-logs-programmatically "Direct link to Accessing logs programmatically") In test mode (when not using real proofs), you can access public function debug logs on the `TxReceipt`: ``` import { applyStringFormatting } from '@aztec/foundation/log'; const { receipt } = await contract.methods.myPublicFunction(args).send({ from: address, fee: { paymentMethod }, wait: { timeout: 600 }, }); // Logs are automatically printed to your console. // You can also access them programmatically: if (receipt.debugLogs) { for (const log of receipt.debugLogs) { console.log(`[${log.level}] ${applyStringFormatting(log.message, log.fields)}`); } } ``` Each entry contains: * `contractAddress` - the contract that emitted the log * `level` - the log level (`info`, `debug`, etc.) * `message` - the unformatted message string * `fields` - the raw `Field` values passed as arguments warning `receipt.debugLogs` is only available in test mode (when not using real proofs). In production, debug log collection is disabled. ## Verify logging works[​](#verify-logging-works "Direct link to Verify logging works") Add a `debug_log` call to any contract function, then run with logging enabled: ``` LOG_LEVEL="error;trace:debug_log" aztec test ``` You should see output like: ``` [07:40:29.947] DEBUG: txe:top_level_context:debug_log your message here ``` If no output appears, check the troubleshooting section below. ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") | Problem | Solution | | ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | No contract logs appear in `aztec test` | Set `LOG_LEVEL` to include `debug_log`, e.g., `LOG_LEVEL="error;trace:debug_log" aztec test`. Also verify you are calling a log function inside the contract function being tested. | | No contract logs in TypeScript tests | Set `LOG_LEVEL` to include `contract_log`, e.g., `LOG_LEVEL="error;trace:contract_log" yarn test`. | | Import error on `dep::aztec::oracle::debug_log` | This path was removed. Update to `use aztec::oracle::logging::{debug_log, debug_log_format};`. | | `receipt.debugLogs` is `undefined` | Debug logs are only collected in test mode (non-real-proofs). They are not available in production. | | Too much noise in log output | Narrow the default level and use module filters, e.g., `LOG_LEVEL="error;debug:contract_log"`. | ## Quick reference[​](#quick-reference "Direct link to Quick reference") | Task | Code or command | | --------------------------- | ------------------------------------------------------------ | | Import logging | `use aztec::oracle::logging::{debug_log, debug_log_format};` | | Simple log | `debug_log("message");` | | Log with values | `debug_log_format("val: {0}", [my_field]);` | | Run Noir tests with logs | `LOG_LEVEL="error;trace:debug_log" aztec test` | | JS tests with contract logs | `LOG_LEVEL="error;trace:contract_log" yarn test` | ## Next steps[​](#next-steps "Direct link to Next steps") * [Debugging Aztec Code](/developers/testnet/docs/aztec-nr/debugging.md) for error codes, profiling, and common issues * [Events and Logs](/developers/testnet/docs/aztec-nr/framework-description/events_and_logs.md) for emitting events that offchain applications can consume * [Testing Contracts](/developers/testnet/docs/aztec-nr/testing_contracts.md) for writing and running contract tests --- # Aztec Contract Standards Aztec contract standards define shared interfaces and behaviors for common onchain primitives. They serve the same role that ERC standards play on Ethereum: establishing conventions that allow contracts, wallets, and tooling to interoperate without prior coordination. The standards described in this section are maintained by [DeFi Wonderland](https://github.com/defi-wonderland/aztec-standards) in the `aztec-standards` repository. Each standard is identified by an **Aztec Improvement Proposal (AIP)** number that mirrors its Ethereum counterpart where applicable (AIP-20 corresponds to ERC-20, AIP-721 to ERC-721, AIP-4626 to ERC-4626). Because Aztec contracts have both private and public execution contexts, the standards are more involved than their Ethereum equivalents. Transfers can move value between private notes and public balances, and many operations require coordination between encrypted state and transparent state within a single transaction. note The code examples in this section are taken from the [aztec-standards repository](https://github.com/defi-wonderland/aztec-standards) maintained by DeFi Wonderland. They will differ from the reference contract implementations shipped in the [aztec-packages repo](https://github.com/AztecProtocol/aztec-packages) under `noir-projects/noir-contracts/contracts/`. When in doubt, consult the aztec-standards github repo for the canonical standard interfaces. ## Standards[​](#standards "Direct link to Standards") * [AIP-20: Fungible Token](/developers/testnet/docs/aztec-nr/standards/aip-20.md) — private and public balances, partial-note transfers, recursive note consumption * [AIP-721: Non-Fungible Token](/developers/testnet/docs/aztec-nr/standards/aip-721.md) — private NFT ownership, partial-note support, commitment-based transfers * [AIP-4626: Tokenized Vault](/developers/testnet/docs/aztec-nr/standards/aip-4626.md) — yield-bearing vaults with share conversion across private and public contexts * [Escrow](/developers/testnet/docs/aztec-nr/standards/escrow.md) — minimal token/NFT custody with salt-based authorization * [Generic Proxy](/developers/testnet/docs/aztec-nr/standards/generic-proxy.md) — forwarding layer for account abstraction patterns * [Dripper](/developers/testnet/docs/aztec-nr/standards/dripper.md) — development faucet for testing ## Related tutorials[​](#related-tutorials "Direct link to Related tutorials") * [Private Token Contract](/developers/testnet/docs/tutorials/contract_tutorials/token_contract.md) — build a privacy-preserving fungible token that closely parallels AIP-20 * [NFT Bridge](/developers/testnet/docs/tutorials/js_tutorials/token_bridge.md) — build a private NFT with custom `NFTNote` and `PrivateSet`, covering patterns extended by AIP-721 * [Deploying a Token Contract](/developers/testnet/docs/tutorials/js_tutorials/aztecjs-getting-started.md) — deploy and interact with the reference token contract using Aztec.js * [Counter Contract](/developers/testnet/docs/tutorials/contract_tutorials/counter_contract.md) — introduces private state, notes, and balance management For the canonical implementations and latest interface specifications, refer to the [aztec-standards repository](https://github.com/defi-wonderland/aztec-standards) maintained by DeFi Wonderland. --- # AIP-20: Fungible Token [Source](https://github.com/defi-wonderland/aztec-standards/tree/dev/src/token_contract) AIP-20 defines a fungible token with support for private balances (stored as notes in the note hash tree), public balances (stored in contract public storage), and a hybrid transfer path between the two. ## Storage layout[​](#storage-layout "Direct link to Storage layout") The token contract stores its name, symbol, and decimals as immutable public fields. Private balances are held in an `Owned` that restricts note access to the balance owner. Public balances use a simple `Map` keyed by address. ``` #[storage] struct Storage { name: PublicImmutable, symbol: PublicImmutable, decimals: PublicImmutable, private_balances: Owned, Context>, total_supply: PublicMutable, public_balances: Map, Context>, minter: PublicImmutable, upgrade_authority: PublicImmutable, asset: PublicImmutable, vault_offset: PublicImmutable, } ``` The `asset` and `vault_offset` fields exist to support the [AIP-4626 vault pattern](/developers/testnet/docs/aztec-nr/standards/aip-4626.md). A standalone AIP-20 token that is not used as a vault underlying asset does not need to populate these fields. ## Note count constants[​](#note-count-constants "Direct link to Note count constants") In Aztec, every time a user receives private tokens, a new encrypted note is added to their balance. Over time, a user's balance can be spread across dozens of small notes. A transfer must consume enough of these notes to cover the amount, but each note consumed adds computational overhead (gates) to the zero-knowledge proof the user's device must generate. Without a bound, a single transfer could take minutes to prove. Two constants cap how many notes a single proof handles, keeping proving times practical: ``` global INITIAL_TRANSFER_CALL_MAX_NOTES: u32 = 2; global RECURSIVE_TRANSFER_CALL_MAX_NOTES: u32 = 8; ``` The initial call attempts to settle the transfer with at most two notes. If that is not enough to cover the amount, the contract recurses into itself and tries up to eight notes per recursive call. A **placeholder address** is used in partial-note flows to signal that a transfer destination is not yet known at the time the sender initiates the operation. This distinguishes "recipient not yet determined" from "recipient is the zero address," and allows offchain indexers to detect [partial-note transfers](#partial-note-transfers) in event logs without decrypting the note contents: ``` global PRIVATE_ADDRESS_MAGIC_VALUE: AztecAddress = AztecAddress::from_field(0x1ea7e01501975545617c2e694d931cb576b691a4a867fed81ebd3264); ``` ## Partial-note transfers[​](#partial-note-transfers "Direct link to Partial-note transfers") AIP-20 supports partial-note (or "commitment-based") transfers. In Aztec, private functions execute on the user's device before the transaction reaches the network, so they cannot read public state (like a DEX order book or auction result). Partial notes solve this by splitting the operation: the sender privately locks funds into a commitment, and later a public function — which *can* read public state — completes the transfer to the correct recipient. This is what makes private DeFi composability possible. Concretely, the sender locks funds in a note whose destination address is not yet known. A completer — typically a contract acting as a relayer or settlement layer — later fills in the recipient and finalizes the note. The sender calls `initialize_transfer_commitment` to create the commitment: ``` #[external("private")] fn initialize_transfer_commitment(to: AztecAddress, completer: AztecAddress) -> Field { let commitment = self.internal._initialize_transfer_commitment(to, completer); commitment.to_field() } ``` The returned `Field` is an opaque commitment to the destination and completer. A separate call then subtracts the balance and completes the note: ``` #[external("private")] fn transfer_private_to_commitment( from: AztecAddress, commitment: Field, amount: u128, _nonce: Field, ) { _validate_from_private::<4>(self.context, from); self.internal._decrease_private_balance(from, amount, INITIAL_TRANSFER_CALL_MAX_NOTES); let completer = self.msg_sender(); PartialUintNote::from_field(commitment).complete_from_private( self.context, completer, amount, ); } ``` This two-step design is useful in DeFi protocols where the recipient of funds depends on some offchain or asynchronous computation. ## Recursive balance subtraction[​](#recursive-balance-subtraction "Direct link to Recursive balance subtraction") When `INITIAL_TRANSFER_CALL_MAX_NOTES` notes are insufficient to cover a transfer, the contract calls itself recursively until the full amount is consumed: ``` #[internal("private")] fn _subtract_balance(account: AztecAddress, amount: u128, max_notes: u32) -> u128 { let subtracted = self.storage.private_balances.at(account).try_sub(amount, max_notes); if subtracted >= amount { subtracted - amount } else { assert(subtracted > 0, "Balance too low"); let remaining = amount - subtracted; self.call_self.recurse_subtract_balance_internal(account, remaining) } } ``` The recursion terminates when either the full amount has been deducted or the assertion fires. Without recursion, you would have to either size every circuit for the worst-case note count (making the common case expensive to prove) or fail transfers when the note count exceeds a fixed limit. Recursion gives the best of both worlds: the common case (2 notes) proves fast, while larger balances are handled by chaining multiple smaller proofs. Each recursive call is a separate private kernel circuit, so proving cost scales with the actual note count rather than the worst case. --- # AIP-4626: Tokenized Vault [Source](https://github.com/defi-wonderland/aztec-standards/tree/dev/src/vault_contract) (extends the AIP-20 token contract) AIP-4626 extends [AIP-20](/developers/testnet/docs/aztec-nr/standards/aip-20.md) to describe a tokenized vault: a contract that holds an underlying asset and issues shares representing a proportional claim on that asset. It mirrors the design of ERC-4626 but adapts the share conversion arithmetic for Aztec's `u128` integer type. ## Share conversion[​](#share-conversion "Direct link to Share conversion") The vault tracks the total supply of shares and a `vault_offset` that prevents inflation attacks on the initial deposit. The conversion functions use integer arithmetic with configurable rounding direction: ``` #[internal("public")] fn _convert_to_shares(assets: u128, total_assets: u128, rounding: bool) -> u128 { let mul_term = assets * (self.storage.total_supply.read() + self.storage.vault_offset.read()); let denominator = (total_assets + 1); let mut shares = mul_term / denominator; if (rounding == ROUND_UP) & (mul_term % denominator > 0) { shares = shares + 1; } shares } #[internal("public")] fn _convert_to_assets(shares: u128, total_assets: u128, rounding: bool) -> u128 { let mul_term = shares * (total_assets + 1); let denominator = (self.storage.total_supply.read() + self.storage.vault_offset.read()); let mut assets = mul_term / denominator; if (rounding == ROUND_UP) & (mul_term % denominator > 0) { assets = assets + 1; } assets } ``` The `+ 1` in the denominator and the `vault_offset` together implement the "virtual shares" technique that prevents the first depositor from manipulating the exchange rate for subsequent depositors. Without this protection, an attacker could deposit 1 wei, then donate a large amount of the underlying asset directly to the vault, inflating the share price so that the next depositor's deposit rounds down to zero shares. Deposits round shares down (in favor of the vault), while redemptions round assets down (also in favor of the vault). This is consistent with ERC-4626 rounding conventions and prevents rounding-based extraction attacks. ## Deposit flow[​](#deposit-flow "Direct link to Deposit flow") A public-to-public deposit transfers assets from the caller to the vault, computes the shares due, and mints them to the recipient: ``` #[external("public")] fn deposit_public_to_public(from: AztecAddress, to: AztecAddress, assets: u128, _nonce: Field) { self.internal._validate_from_public(from); let total_assets = self.internal._total_assets(); let shares = self.internal._convert_to_shares(assets, total_assets, ROUND_DOWN); // Transfer assets from sender to vault self.call(Token::at(self.storage.asset.read()).transfer_public_to_public( from, self.address, assets, _nonce, )); // Mint shares to the recipient self.internal._mint_to_public(to, shares); } ``` The vault exposes similar entry points for the other combinations of private and public contexts (`deposit_private_to_public`, `deposit_public_to_private`, `deposit_private_to_private`). Each variant transfers assets using the corresponding AIP-20 transfer function and then mints shares into the chosen output context. --- # AIP-721: Non-Fungible Token [Source](https://github.com/defi-wonderland/aztec-standards/tree/dev/src/nft_contract) AIP-721 defines a non-fungible token (NFT). Each token is identified by a unique `token_id` field. Tokens can be held privately in the note hash tree or publicly in a map from `token_id` to owner address. ## Storage layout[​](#storage-layout "Direct link to Storage layout") ``` #[storage] struct Storage { symbol: PublicImmutable, name: PublicImmutable, private_nfts: Owned, Context>, nft_exists: Map, Context>, public_owners: Map, Context>, minter: PublicImmutable, upgrade_authority: PublicImmutable, } ``` `nft_exists` tracks whether a given `token_id` has been minted, while `public_owners` records the current public owner. When an NFT is moved to a private note, the `public_owners` entry is cleared and the NFT is stored as an `NFTNote` in the holder's private set. ## NFTNote and partial-note support[​](#nftnote-and-partial-note-support "Direct link to NFTNote and partial-note support") Each private NFT is represented as an `NFTNote` containing only the `token_id`: ``` #[derive(Eq, Serialize, Packable)] #[custom_note] pub struct NFTNote { pub token_id: Field, } impl NFTNote { pub fn partial( owner: AztecAddress, storage_slot: Field, context: &mut PrivateContext, recipient: AztecAddress, completer: AztecAddress, ) -> PartialNFTNote { let randomness = unsafe { random() }; let commitment = compute_partial_commitment(owner, storage_slot, randomness); // ... creates encrypted log and validity commitment let partial_note = PartialNFTNote { commitment }; let validity_commitment = partial_note.compute_validity_commitment(completer); context.push_nullifier_unsafe(validity_commitment); partial_note } } ``` The `partial` constructor creates a `PartialNFTNote` whose `commitment` field commits to the future owner and storage slot. Without some form of access control, any party could call the completion function and claim the NFT for themselves. The validity commitment prevents this — it is pushed as a nullifier, and only the designated completer can produce the matching preimage needed to finalize the note. This mirrors the partial-note pattern in [AIP-20](/developers/testnet/docs/aztec-nr/standards/aip-20.md) but applies it to NFT transfers. ## Partial-note transfer commitment[​](#partial-note-transfer-commitment "Direct link to Partial-note transfer commitment") The external entry point for initiating a partial NFT transfer is: ``` #[external("private")] fn initialize_transfer_commitment(to: AztecAddress, completer: AztecAddress) -> Field { let commitment = self.internal._initialize_transfer_commitment(to, completer); commitment.commitment() } ``` This function returns `commitment.commitment()` — an opaque `Field` representing the commitment. The AIP-20 equivalent returns `commitment.to_field()` for `PartialUintNote`. --- # Dripper (Development Faucet) [Source](https://github.com/defi-wonderland/aztec-standards/tree/dev/src/dripper) The `aztec-standards` repository also ships a **Dripper** contract — a convenience faucet for minting tokens into private or public balances during development. It is not a formal AIP standard and should not be used in production. --- # Escrow [Source](https://github.com/defi-wonderland/aztec-standards/tree/dev/src/escrow_contract) The Escrow standard provides a minimal contract for holding tokens or NFTs on behalf of a single owner. Rather than storing the owner in mutable private state — which would require note discovery and decryption on every authorization check — the owner is encoded in the contract's own `salt` and thus baked into the contract address at deploy time. This makes authorization a simple field comparison against immutable deployment parameters: cheaper, simpler, and impossible to front-run. ## Escrow contract[​](#escrow-contract "Direct link to Escrow contract") ``` #[aztec] pub contract Escrow { #[external("private")] fn withdraw(token: AztecAddress, amount: u128, recipient: AztecAddress) { self.internal._assert_msg_sender(); self.call(Token::at(token).transfer_private_to_private( self.address, recipient, amount, 0, )); } #[external("private")] fn withdraw_nft(nft: AztecAddress, token_id: Field, recipient: AztecAddress) { self.internal._assert_msg_sender(); self.call(NFT::at(nft).transfer_private_to_private( self.address, recipient, token_id, 0, )); } #[internal("private")] fn _assert_msg_sender() { let msg_sender = self.msg_sender(); let escrow_instance: ContractInstance = get_contract_instance(self.address); assert(AztecAddress::from_field(escrow_instance.salt) == msg_sender, "Not Authorized"); } } ``` The authorization check in `_assert_msg_sender` reads the `salt` field of the escrow's own `ContractInstance` and compares it against `msg_sender`. Because the `ContractInstance` is fixed at deployment time, this check cannot be spoofed by manipulating storage after deployment. ## Escrow logic library[​](#escrow-logic-library "Direct link to Escrow logic library") A DeFi protocol (like a lending market or DEX) often needs to give each user a personal escrow to hold collateral or pending settlements. The standard ships a companion library that lets the parent contract deterministically compute escrow addresses from its own address and the user's keys — no onchain deployment transaction required: ``` #[contract_library_method] pub fn _get_escrow( context: &mut PrivateContext, escrow_class_id: Field, master_secret_keys: MasterSecretKeys, ) -> AztecAddress { let computed_public_keys: PublicKeys = _secret_keys_to_public_keys(master_secret_keys); let escrow_instance = ContractInstance { salt: context.this_address().to_field(), deployer: AztecAddress::from_field(0), original_contract_class_id: ContractClassId::from_field(escrow_class_id), initialization_hash: 0, public_keys: computed_public_keys, }; escrow_instance.to_address() } #[contract_library_method] pub fn _share_escrow( context: &mut PrivateContext, account: AztecAddress, escrow: AztecAddress, master_secret_keys: MasterSecretKeys, ) { let event_struct = EscrowDetailsLogContent { escrow, master_secret_keys }; emit_event_in_private(context, event_struct).deliver_to( account, MessageDelivery::onchain_constrained(), ); } ``` `_get_escrow` reconstructs the escrow address deterministically from the calling contract's address (used as the salt) and a set of master secret keys. `_share_escrow` emits an encrypted log so that the designated `account` can discover the escrow address and the keys needed to access its notes. Without this notification, the user's PXE would have no way to find the escrow or decrypt notes held there. The `ONCHAIN_CONSTRAINED` delivery mode ensures the log is validated against the note hash tree before the recipient's PXE trusts it. --- # Generic Proxy In Aztec, account contracts authorize every transaction the user sends and must be able to forward calls to any contract. However, Noir requires function signatures to be known at compile time, so an account contract cannot call an arbitrary function with an arbitrary number of arguments in a single generic entrypoint. The Generic Proxy contract solves this by providing a fixed set of forwarding functions — one per argument count — that the account contract can call. This avoids hard-coding every possible target function signature while keeping the account contract simple. ``` #[aztec] pub contract GenericProxy { #[external("private")] fn forward_private_0(target: AztecAddress, selector: FunctionSelector) { let _ = self.context.call_private_function_no_args(target, selector); } #[external("private")] fn forward_private_4(target: AztecAddress, selector: FunctionSelector, args: [Field; 4]) { let _ = self.context.call_private_function(target, selector, args); } #[external("private")] fn forward_private_4_and_return( target: AztecAddress, selector: FunctionSelector, args: [Field; 4], ) -> Field { let returns: Field = self.context.call_private_function(target, selector, args).get_preimage(); returns } // ... forward_private_1 through forward_private_8 } ``` The proxy exposes a family of `forward_private_N` functions, each accepting a different fixed argument count. Because Noir's type system requires array lengths to be known at compile time, the contract implements one overload per arity rather than a single variadic function. The `_and_return` variant captures the return value from the callee and passes it back to the caller. note The Generic Proxy does not implement any access control by itself. Callers are responsible for ensuring that forwarding to `target` is appropriate. In most protocols, the proxy is called from within an account contract that enforces its own authorization rules before delegating to the proxy. --- # Testing Contracts This guide shows you how to test your Aztec smart contracts using Noir's `TestEnvironment` for fast, lightweight testing. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * An Aztec contract project with functions to test * Basic understanding of Noir syntax tip For complex cross-chain or integration testing, see the [TypeScript testing guide](/developers/testnet/docs/aztec-js/how_to_test.md). ## Write Aztec contract tests[​](#write-aztec-contract-tests "Direct link to Write Aztec contract tests") Use `TestEnvironment` from `aztec-nr` for contract unit testing: * **Fast**: Lightweight environment with mocked components * **Convenient**: Similar to Foundry for simple contract tests * **Limited**: No rollup circuits or cross-chain messaging For complex end-to-end tests, use [TypeScript testing](/developers/testnet/docs/aztec-js/how_to_test.md) with `aztec.js`. ## Run your tests[​](#run-your-tests "Direct link to Run your tests") Execute Aztec Noir tests using: ``` aztec test ``` ### Test execution process[​](#test-execution-process "Direct link to Test execution process") 1. Compile contracts 2. Run `aztec test` warning Always use `aztec test` instead of `nargo test`. The `TestEnvironment` requires the test environment oracle resolver provided by the `aztec` CLI. ## Keep tests in the test crate[​](#keep-tests-in-the-test-crate "Direct link to Keep tests in the test crate") `aztec new` and `aztec init` scaffold a workspace with two crates: a contract crate and a separate test crate. For `aztec new my_project`, these are `my_project_contract` and `my_project_test`. Keep all `#[test]` functions in the test crate, not in the contract crate. If tests end up inside a contract crate, `aztec compile` emits a warning: ``` WARNING: Found tests in contract crate(s): my_project_contract::test_something Tests should be in a dedicated test crate, not in the contract crate. ``` The reason is **unnecessary recompilation**: a contract's compiled artifact depends on everything in its crate, so a test-only edit forces the contract to recompile even though its logic has not changed. Keeping tests in the separate test crate lets `aztec test` skip contract recompilation when only test code changed. ## Basic test structure[​](#basic-test-structure "Direct link to Basic test structure") `aztec new my_project` scaffolds a workspace with two crates: a `contract` crate that holds the contract code, and a separate `test` crate that holds your `#[test]` functions: ``` my_project/ ├── Nargo.toml # [workspace] members = ["my_project_contract", "my_project_test"] ├── my_project_contract/ │ ├── Nargo.toml # type = "contract" │ └── src/main.nr └── my_project_test/ ├── Nargo.toml # type = "lib", depends on my_project_contract └── src/lib.nr # #[test] functions go here ``` The motivation for the split of contract and tests into its own crates is **faster iteration**: editing a test does not invalidate the contract's compiled artifact, so `aztec test` skips contract recompilation when only test code changed. `aztec compile` warns if it finds `#[test]` functions inside a contract crate. The generated test crate template imports the contract by package name and then initializes it: ``` // my_project_test/src/lib.nr use aztec::test::helpers::test_environment::TestEnvironment; use my_project_contract::Main; #[test] unconstrained fn test_constructor() { let mut env = TestEnvironment::new(); let deployer = env.create_light_account(); let _contract_address = env.deploy("@my_project_contract/Main") .with_private_initializer(deployer, Main::interface().constructor()); } ``` Because tests live in their own crate, we refer to the contract via its crate name using the `@crate_name/ContractName` syntax. Test execution notes * Tests run in parallel by default * Use `unconstrained` functions for faster execution * See all `TestEnvironment` methods [here](/aztec-nr-api/testnet/noir_aztec/test/helpers/test_environment/struct.TestEnvironment) * It is always necessary to deploy a contract in order to test it If you'll add arguments to your contract's constructor you pass them directly to the constructor function in the test: ``` let initializer = MyContract::interface().constructor(param1, param2); ``` Since Aztec contracts can be initialized both in private and public or they can be interacted with without any kind of initialization (see [Contract creation](/developers/testnet/docs/foundational-topics/contract_creation.md) for how Aztec's deployment model differs from Ethereum's) there are 3 options on the deployer: ``` let contract_address = deployer.with_private_initializer(owner, initializer); let contract_address = deployer.with_public_initializer(owner, initializer); let contract_address = deployer.without_initializer(); ``` Reusable setup functions Create a setup function to avoid repeating initialization code: ``` pub unconstrained fn setup(initial_value: Field) -> (TestEnvironment, AztecAddress, AztecAddress) { let mut env = TestEnvironment::new(); let owner = env.create_light_account(); let initializer = MyContract::interface().constructor(initial_value, owner); let contract_address = env.deploy("@my_project_contract/MyContract").with_private_initializer(owner, initializer); (env, contract_address, owner) } #[test] unconstrained fn test_something() { let (env, contract_address, owner) = setup(42); // Your test logic here } ``` ## Calling contract functions[​](#calling-contract-functions "Direct link to Calling contract functions") TestEnvironment provides methods for different function types: ### Private functions[​](#private-functions "Direct link to Private functions") ``` // Call private function env.call_private(caller, Token::at(token_address).transfer(recipient, 100)); // Returns the result let result = env.call_private(owner, Contract::at(address).get_private_data()); ``` ### Public functions[​](#public-functions "Direct link to Public functions") ``` // Call public function env.call_public(caller, Token::at(token_address).mint_to_public(recipient, 100)); // View public state (read-only) let balance = env.view_public(Token::at(token_address).balance_of_public(owner)); ``` ### Utility/Unconstrained functions[​](#utilityunconstrained-functions "Direct link to Utility/Unconstrained functions") ``` // Simulate utility/view functions (unconstrained) let total = env.execute_utility(Token::at(token_address).balance_of_private(owner)); // To set the `msg_sender` the utility function observes, use the `_opts` variant let secret = env.execute_utility_opts( ExecuteUtilityOptions::new().with_from(caller), Registry::at(registry_address).get_app_siloed_secret(sender, recipient, mode), ); ``` Helper function pattern Create helper functions for common assertions: ``` pub unconstrained fn check_balance( env: TestEnvironment, token_address: AztecAddress, owner: AztecAddress, expected: u128, ) { assert_eq( env.execute_utility(Token::at(token_address).balance_of_private(owner)), expected ); } ``` ## Creating accounts[​](#creating-accounts "Direct link to Creating accounts") Two types of accounts are available: ``` // Light account - fast, limited features let owner = env.create_light_account(); // Contract account - full features, slower let owner = env.create_contract_account(); ``` Account type comparison **Light accounts:** * Fast to create * Work for simple transfers and tests * Cannot process authwits * No account contract deployed **Contract accounts:** * Required for authwit testing * Support account abstraction features * Slower to create (deploys account contract) * Needed for cross-contract authorization Choosing account types ``` pub unconstrained fn setup(with_authwits: bool) -> (TestEnvironment, AztecAddress, AztecAddress) { let mut env = TestEnvironment::new(); let (owner, recipient) = if with_authwits { (env.create_contract_account(), env.create_contract_account()) } else { (env.create_light_account(), env.create_light_account()) }; // ... deploy contracts ... (env, owner, recipient) } ``` ## Testing with authwits[​](#testing-with-authwits "Direct link to Testing with authwits") [Authwits](/developers/testnet/docs/aztec-nr/framework-description/authentication_witnesses.md) allow one account to authorize another to act on its behalf. warning Authwits require **contract accounts**, not light accounts. ### Import authwit helpers[​](#import-authwit-helpers "Direct link to Import authwit helpers") ``` use aztec::test::helpers::authwit::{ add_private_authwit_from_call, add_public_authwit_from_call, }; ``` ### Private authwits[​](#private-authwits "Direct link to Private authwits") ``` #[test] unconstrained fn test_private_authwit() { // Setup with contract accounts (required for authwits) let (env, token_address, owner, spender) = setup(true); // Create the call that needs authorization let amount = 100; let nonce = 7; // Non-zero nonce for authwit let burn_call = Token::at(token_address).burn_private(owner, amount, nonce); // Grant authorization from owner to spender add_private_authwit_from_call(env, owner, spender, burn_call); // Spender can now execute the authorized action env.call_private(spender, burn_call); } ``` ### Public authwits[​](#public-authwits "Direct link to Public authwits") ``` #[test] unconstrained fn test_public_authwit() { let (env, token_address, owner, spender) = setup(true); // Create public action that needs authorization let transfer_call = Token::at(token_address).transfer_in_public(owner, recipient, 100, nonce); // Grant public authorization add_public_authwit_from_call(env, owner, spender, transfer_call); // Execute with authorization env.call_public(spender, transfer_call); } ``` ## Time traveling[​](#time-traveling "Direct link to Time traveling") Contract calls do not advance the timestamp by default, despite each of them resulting in a block with a single transaction. Block timestamp can instead be manually manipulated by any of the following methods: ``` // Sets the timestamp of the next block to be mined, i.e. of the next public execution. Does not affect private execution. env.set_next_block_timestamp(block_timestamp); // Same as `set_next_block_timestamp`, but moving time forward by `duration` instead of advancing to a target timestamp. env.advance_next_block_timestamp_by(duration); // Mines an empty block at a given timestamp, causing the next public execution to occur at this time (like `set_next_block_timestamp`), but also allowing for private execution to happen using this empty block as the anchor block. env.mine_block_at(block_timestamp); ``` ## Testing failure cases[​](#testing-failure-cases "Direct link to Testing failure cases") Test functions that should fail using annotations: ### Generic failure[​](#generic-failure "Direct link to Generic failure") ``` #[test(should_fail)] unconstrained fn test_unauthorized_access() { let (env, contract, owner) = setup(false); let attacker = env.create_light_account(); // This should fail because attacker is not authorized env.call_private(attacker, Contract::at(contract).owner_only_function()); } ``` ### Specific error message[​](#specific-error-message "Direct link to Specific error message") ``` #[test(should_fail_with = "Balance too low")] unconstrained fn test_insufficient_balance() { let (env, token, owner, recipient) = setup(false); // Try to transfer more than available let balance = 100; let transfer_amount = 101; env.call_private(owner, Token::at(token).transfer(recipient, transfer_amount)); } ``` ### Testing authwit failures[​](#testing-authwit-failures "Direct link to Testing authwit failures") ``` #[test(should_fail_with = "Unknown auth witness for message hash")] unconstrained fn test_missing_authwit() { let (env, token, owner, spender) = setup(true); // Try to burn without authorization let burn_call = Token::at(token).burn_private(owner, 100, 1); // No authwit granted - this should fail env.call_private(spender, burn_call); } ``` ## Test environment oracle versioning[​](#test-environment-oracle-versioning "Direct link to Test environment oracle versioning") The test environment uses an oracle interface to communicate between your Noir test code and the `aztec test` CLI. This interface is versioned so that mismatches between the Aztec.nr dependency used to compile the test and the CLI version are detected automatically. The version uses two components, `major.minor`, with the same compatibility rules as [PXE oracle versioning](/developers/testnet/docs/foundational-topics/pxe.md#oracle-versioning): * **`major`** must match exactly. A major bump means oracles were removed or had their signatures changed, and a test environment on a different major cannot safely run the test. * **`minor`** indicates additive changes (new oracles). The test environment uses a best-effort approach: a test compiled against a higher `minor` is still allowed to run, and an error is only thrown if the test actually invokes an oracle the test environment does not know about. ### Resolving a version mismatch[​](#resolving-a-version-mismatch "Direct link to Resolving a version mismatch") If you see an error like *"Incompatible test environment version: The test was compiled with a newer version of Aztec.nr than your test environment supports"*, the test uses oracles from a newer Aztec.nr than your `aztec test` CLI supports. To fix it, make sure your `aztec` CLI version and the `aztec` dependency in the test crate's `Nargo.toml` are on the same release. Note that the test crate's Aztec.nr version can differ from the contract crate's version, depending on your project configuration. For example, if your CLI is on `v4.3.0`, the test crate's `Nargo.toml` should reference the matching tag: ``` [dependencies] aztec = { git="https://github.com/AztecProtocol/aztec-nr", tag="v4.3.0", directory="aztec" } ``` If the test environment reports a version that *should* include every oracle the test needs but an oracle is still missing, this is likely a bug rather than a version problem. --- # Aztec CLI Reference *This documentation is auto-generated from the `aztec` CLI help output.* *Generated: Tue 30 Jun 2026 17:56:34 UTC* *Command: `aztec`* ## Table of Contents[​](#table-of-contents "Direct link to Table of Contents") * [aztec](#aztec) * [aztec add-l1-validator](#aztec-add-l1-validator) * [aztec advance-epoch](#aztec-advance-epoch) * [aztec block-number](#aztec-block-number) * [aztec bridge-erc20](#aztec-bridge-erc20) * [aztec codegen](#aztec-codegen) * [aztec compile](#aztec-compile) * [aztec compute-genesis-values](#aztec-compute-genesis-values) * [aztec compute-selector](#aztec-compute-selector) * [aztec debug-rollup](#aztec-debug-rollup) * [aztec decode-enr](#aztec-decode-enr) * [aztec deploy-l1-contracts](#aztec-deploy-l1-contracts) * [aztec deploy-new-rollup](#aztec-deploy-new-rollup) * [aztec deposit-governance-tokens](#aztec-deposit-governance-tokens) * [aztec example-contracts](#aztec-example-contracts) * [aztec execute-governance-proposal](#aztec-execute-governance-proposal) * [aztec fast-forward-epochs](#aztec-fast-forward-epochs) * [aztec generate-bls-keypair](#aztec-generate-bls-keypair) * [aztec generate-bootnode-enr](#aztec-generate-bootnode-enr) * [aztec generate-keys](#aztec-generate-keys) * [aztec generate-l1-account](#aztec-generate-l1-account) * [aztec generate-p2p-private-key](#aztec-generate-p2p-private-key) * [aztec generate-secret-and-hash](#aztec-generate-secret-and-hash) * [aztec get-block](#aztec-get-block) * [aztec get-canonical-sponsored-fpc-address](#aztec-get-canonical-sponsored-fpc-address) * [aztec get-current-min-fee](#aztec-get-current-min-fee) * [aztec get-l1-addresses](#aztec-get-l1-addresses) * [aztec get-l1-balance](#aztec-get-l1-balance) * [aztec get-l1-to-l2-message-witness](#aztec-get-l1-to-l2-message-witness) * [aztec get-logs](#aztec-get-logs) * [aztec get-node-info](#aztec-get-node-info) * [aztec init](#aztec-init) * [aztec inspect-contract](#aztec-inspect-contract) * [aztec migrate-ha-db](#aztec-migrate-ha-db) * [aztec migrate-ha-db down](#aztec-migrate-ha-db-down) * [aztec migrate-ha-db up](#aztec-migrate-ha-db-up) * [aztec new](#aztec-new) * [aztec parse-parameter-struct](#aztec-parse-parameter-struct) * [aztec preload-crs](#aztec-preload-crs) * [aztec profile](#aztec-profile) * [aztec profile flamegraph](#aztec-profile-flamegraph) * [aztec profile gates](#aztec-profile-gates) * [aztec propose-with-lock](#aztec-propose-with-lock) * [aztec prover](#aztec-prover) * [aztec prover get-jobs](#aztec-prover-get-jobs) * [aztec prover start-proof](#aztec-prover-start-proof) * [aztec prune-rollup](#aztec-prune-rollup) * [aztec remove-l1-validator](#aztec-remove-l1-validator) * [aztec sequencers](#aztec-sequencers) * [aztec setup-protocol-contracts](#aztec-setup-protocol-contracts) * [aztec start](#aztec-start) * [aztec test](#aztec-test) * [aztec trigger-seed-snapshot](#aztec-trigger-seed-snapshot) * [aztec update](#aztec-update) * [aztec validator-keys|valKeys](#aztec-validator-keys%7Cvalkeys) * [aztec vote-on-governance-proposal](#aztec-vote-on-governance-proposal) ## aztec[​](#aztec "Direct link to aztec") Aztec command line interface **Usage:** ``` aztec [options] [command] ``` **Available Commands:** * `add-l1-validator [options]` - Adds a validator to the L1 rollup contract via a direct deposit. * `advance-epoch [options]` - Use L1 cheat codes to warp time until the next epoch. * `block-number [options]` - Gets the current Aztec L2 block number. * `bridge-erc20 [options] ` - Bridges ERC20 tokens to L2. * `codegen [options] ` - Validates and generates an Aztec Contract ABI from Noir ABI. * `compile [nargo-args...]` - Compile Aztec Noir contracts using nargo and postprocess them to generate transpiled artifacts and verification keys. All options are forwarded to nargo compile. * `compute-genesis-values [options]` - Computes genesis values (VK tree root, protocol contracts hash, genesis archive root). * `compute-selector ` - Given a function signature, it computes a selector * `debug-rollup [options]` - Debugs the rollup contract. * `decode-enr ` - Decodes an ENR record * `deploy-l1-contracts [options]` - Deploys all necessary Ethereum contracts for Aztec. * `deploy-new-rollup [options]` - Deploys a new rollup contract and adds it to the registry (if you are the owner). * `deposit-governance-tokens [options]` - Deposits governance tokens to the governance contract. * `example-contracts` - Lists the example contracts available to deploy from @aztec/noir-contracts.js * `execute-governance-proposal [options]` - Executes a governance proposal. * `fast-forward-epochs [options]` - Fast forwards the epoch of the L1 rollup contract. * `generate-bls-keypair [options]` - Generate a BLS keypair with convenience flags * `generate-bootnode-enr [options] ` - Generates the encoded ENR record for a bootnode. * `generate-keys [options]` - Generates encryption and signing private keys. * `generate-l1-account [options]` - Generates a new private key for an account on L1. * `generate-p2p-private-key` - Generates a LibP2P peer private key. * `generate-secret-and-hash` - Generates an arbitrary secret (Fr), and its hash (using aztec-nr defaults) * `get-block [options] [blockNumber]` - Gets info for a given block or latest. * `get-canonical-sponsored-fpc-address` - Gets the canonical SponsoredFPC address for this any testnet running on the same version as this CLI * `get-current-min-fee [options]` - Gets the current base fee. * `get-l1-addresses [options]` - Gets the addresses of the L1 contracts. * `get-l1-balance [options] ` - Gets the balance of an ERC token in L1 for the given Ethereum address. * `get-l1-to-l2-message-witness [options]` - Gets a L1 to L2 message witness. * `get-logs [options]` - Gets public logs for a contract and tag, optionally restricted by block range or tx hash. * `get-node-info [options]` - Gets the information of an Aztec node from a PXE or directly from an Aztec node. * `help [command]` - display help for command * `init` - creates a new Aztec Noir workspace in the current directory. * `inspect-contract ` - Shows list of external callable functions for a contract * `migrate-ha-db` - Run validator-ha-signer database migrations * `new ` - creates a new Aztec Noir workspace in its own directory (or creates a new contract-test crates pair and adds it to the current workspace if run in workspace). * `parse-parameter-struct [options] ` - Helper for parsing an encoded string into a contract's parameter struct. * `preload-crs` - Preload the points data needed for proving and verifying * `profile` - Profile compiled Aztec artifacts. * `propose-with-lock [options]` - Makes a proposal to governance with a lock * `prover` - Operate a prover node via its admin JSON-RPC endpoint * `prune-rollup [options]` - Prunes the pending chain on the rollup contract. * `remove-l1-validator [options]` - Removes a validator to the L1 rollup contract. * `sequencers [options] [who]` - Manages or queries registered sequencers on the L1 rollup contract. * `setup-protocol-contracts [options]` - Bootstrap the blockchain by initializing all the protocol contracts * `start [options]` - Starts Aztec modules. Options for each module can be set as key-value pairs (e.g. "option1=value1,option2=value2") or as environment variables. * `test [options]` - starts a TXE and runs "nargo test" using it as the oracle resolver. * `trigger-seed-snapshot [options]` - Triggers a seed snapshot for the next epoch. * `update [options] [projectPath]` - Updates Nodejs and Noir dependencies * `validator-keys|valKeys` - Manage validator keystores for node operators * `vote-on-governance-proposal [options]` - Votes on a governance proposal. **Options:** * `-V --version` - output the version number * `-h --help` - display help for command ### Subcommands[​](#subcommands "Direct link to Subcommands") ### aztec add-l1-validator[​](#aztec-add-l1-validator "Direct link to aztec add-l1-validator") Adds a validator to the L1 rollup contract via a direct deposit. **Usage:** ``` aztec add-l1-validator [options] ``` **Options:** * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \[""], env: ETHEREUM\_HOSTS) * `--network ` - Network to execute against (env: NETWORK) * `-pk, --private-key ` - The private key to use sending the transaction * `-m, --mnemonic ` - The mnemonic to use sending the transaction (default: "test test test test test test test test test test test junk") * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `--attester
` - ethereum address of the attester * `--withdrawer
` - ethereum address of the withdrawer * `--bls-secret-key ` - The BN254 scalar field element used as a secret key for BLS signatures. Will be associated with the attester address. * `--move-with-latest-rollup` - Whether to move with the latest rollup (default: true) * `--rollup ` - Rollup contract address * `-h, --help` - display help for command ### aztec advance-epoch[​](#aztec-advance-epoch "Direct link to aztec advance-epoch") Use L1 cheat codes to warp time until the next epoch. **Usage:** ``` aztec advance-epoch [options] ``` **Options:** * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \[""], env: ETHEREUM\_HOSTS) * `-n, --node-url ` - URL of the Aztec node (default: "", env: AZTEC\_NODE\_URL) * `-h, --help` - display help for command ### aztec block-number[​](#aztec-block-number "Direct link to aztec block-number") Gets the current Aztec L2 block number. **Usage:** ``` aztec block-number [options] ``` **Options:** * `-n, --node-url ` - URL of the Aztec node (default: "", env: AZTEC\_NODE\_URL) * `-h, --help` - display help for command ### aztec bridge-erc20[​](#aztec-bridge-erc20 "Direct link to aztec bridge-erc20") Bridges ERC20 tokens to L2. **Usage:** ``` aztec bridge-erc20 [options] ``` **Options:** * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \[""], env: ETHEREUM\_HOSTS) * `-m, --mnemonic ` - The mnemonic to use for deriving the Ethereum address that will mint and bridge (default: "test test test test test test test test test test test junk") * `--mint` - Mint the tokens on L1 (default: false) * `--private` - If the bridge should use the private flow (default: false) * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `-t, --token ` - The address of the token to bridge * `-p, --portal ` - The address of the portal contract * `-f, --faucet ` - The address of the faucet contract (only used if minting) * `--l1-private-key ` - The private key to use for deployment * `--json` - Output the claim in JSON format * `-h, --help` - display help for command ### aztec codegen[​](#aztec-codegen "Direct link to aztec codegen") Validates and generates an Aztec Contract ABI from Noir ABI. **Usage:** ``` aztec codegen [options] ``` **Options:** * `-o, --outdir ` - Output folder for the generated code. * `-f, --force` - Force code generation even when the contract has not changed. * `-h, --help` - display help for command ### aztec compile[​](#aztec-compile "Direct link to aztec compile") Compile Aztec Noir contracts using nargo and postprocess them to generate transpiled artifacts and verification keys. All options are forwarded to nargo compile. **Usage:** ``` aztec compile [options] [nargo-args...] ``` **Options:** * `-h, --help` - display help for command * `--package ` - The name of the package to run the command on. By default run on the first one found moving up along the ancestors of the current directory * `--workspace` - Run on all packages in the workspace * `--force` - Force a full recompilation * `--print-acir` - Display the ACIR for compiled circuit, including the Brillig bytecode * `--deny-warnings` - Treat all warnings as errors * `--silence-warnings` - Suppress warnings * `--debug-comptime-in-file ` - Enable printing results of comptime evaluation: provide a path suffix for the module to debug, e.g. "package\_name/src/main.nr" * `--skip-underconstrained-check` - Flag to turn off the compiler check for under constrained values. Warning: This can improve compilation speed but can also lead to correctness errors. This check should always be run on production code * `--skip-brillig-constraints-check` - Flag to turn off the compiler check for missing Brillig call constraints. Warning: This can improve compilation speed but can also lead to correctness errors. This check should always be run on production code * `--count-array-copies` - Count the number of arrays that are copied in an unconstrained context for performance debugging * `--inliner-aggressiveness ` - Setting to decide on an inlining strategy for Brillig functions. A more aggressive inliner should generate larger programs but more optimized A less aggressive inliner should generate smaller programs \[default: 9223372036854775807] * `-Z, --unstable-features ` - Unstable features to enable for this current build. If non-empty, it disables unstable features required in crate manifests. * `--no-unstable-features` - Disable any unstable features required in crate manifests * `-h, --help` - Print help (see a summary with '-h') ### aztec compute-genesis-values[​](#aztec-compute-genesis-values "Direct link to aztec compute-genesis-values") Computes genesis values (VK tree root, protocol contracts hash, genesis archive root). **Usage:** ``` aztec compute-genesis-values [options] ``` **Options:** * `--test-accounts ` - Include initial test accounts in genesis state (env: TEST\_ACCOUNTS) * `--sponsored-fpc ` - Include sponsored FPC contract in genesis state (env: SPONSORED\_FPC) * `-h, --help` - display help for command ### aztec compute-selector[​](#aztec-compute-selector "Direct link to aztec compute-selector") Given a function signature, it computes a selector **Usage:** ``` aztec compute-selector [options] ``` **Options:** * `-h, --help` - display help for command ### aztec debug-rollup[​](#aztec-debug-rollup "Direct link to aztec debug-rollup") Debugs the rollup contract. **Usage:** ``` aztec debug-rollup [options] ``` **Options:** * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \[""], env: ETHEREUM\_HOSTS) * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `--rollup
` - ethereum address of the rollup contract * `-h, --help` - display help for command ### aztec decode-enr[​](#aztec-decode-enr "Direct link to aztec decode-enr") Decodes and ENR record **Usage:** ``` aztec decode-enr [options] ``` **Options:** * `-h, --help` - display help for command ### aztec deploy-l1-contracts[​](#aztec-deploy-l1-contracts "Direct link to aztec deploy-l1-contracts") Deploys all necessary Ethereum contracts for Aztec. **Usage:** ``` aztec deploy-l1-contracts [options] ``` **Options:** * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \[""], env: ETHEREUM\_HOSTS) * `-pk, --private-key ` - The private key to use for deployment * `--validators ` - Comma separated list of validators * `-m, --mnemonic ` - The mnemonic to use in deployment (default: "test test test test test test test test test test test junk") * `-i, --mnemonic-index ` - The index of the mnemonic to use in deployment (default: 0) * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `--json` - Output the contract addresses in JSON format * `--test-accounts` - Populate genesis state with initial fee juice for test accounts * `--sponsored-fpc` - Populate genesis state with a testing sponsored FPC contract * `--real-verifier` - Deploy the real verifier (default: false) * `--existing-token
` - Use an existing ERC20 for both fee and staking * `-h, --help` - display help for command ### aztec deploy-new-rollup[​](#aztec-deploy-new-rollup "Direct link to aztec deploy-new-rollup") Deploys a new rollup contract and adds it to the registry (if you are the owner). **Usage:** ``` aztec deploy-new-rollup [options] ``` **Options:** * `-r, --registry-address ` - The address of the registry contract * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \[""], env: ETHEREUM\_HOSTS) * `-pk, --private-key ` - The private key to use for deployment * `--validators ` - Comma separated list of validators * `-m, --mnemonic ` - The mnemonic to use in deployment (default: "test test test test test test test test test test test junk") * `-i, --mnemonic-index ` - The index of the mnemonic to use in deployment (default: 0) * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `--json` - Output the contract addresses in JSON format * `--test-accounts` - Populate genesis state with initial fee juice for test accounts * `--sponsored-fpc` - Populate genesis state with a testing sponsored FPC contract * `--real-verifier` - Deploy the real verifier (default: false) * `-h, --help` - display help for command ### aztec deposit-governance-tokens[​](#aztec-deposit-governance-tokens "Direct link to aztec deposit-governance-tokens") Deposits governance tokens to the governance contract. **Usage:** ``` aztec deposit-governance-tokens [options] ``` **Options:** * `-r, --registry-address ` - The address of the registry contract * `--recipient ` - The recipient of the tokens * `-a, --amount ` - The amount of tokens to deposit * `--mint` - Mint the tokens on L1 (default: false) * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \[""], env: ETHEREUM\_HOSTS) * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `-p, --private-key ` - The private key to use to deposit * `-m, --mnemonic ` - The mnemonic to use to deposit (default: "test test test test test test test test test test test junk") * `-i, --mnemonic-index ` - The index of the mnemonic to use to deposit (default: 0) * `-h, --help` - display help for command ### aztec example-contracts[​](#aztec-example-contracts "Direct link to aztec example-contracts") Lists the example contracts available to deploy from @aztec/noir-contracts.js **Usage:** ``` aztec example-contracts [options] ``` **Options:** * `-h, --help` - display help for command ### aztec execute-governance-proposal[​](#aztec-execute-governance-proposal "Direct link to aztec execute-governance-proposal") Executes a governance proposal. **Usage:** ``` aztec execute-governance-proposal [options] ``` **Options:** * `-p, --proposal-id ` - The ID of the proposal * `-r, --registry-address ` - The address of the registry contract * `--wait ` - Whether to wait until the proposal is executable * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \[""], env: ETHEREUM\_HOSTS) * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `-pk, --private-key ` - The private key to use to vote * `-m, --mnemonic ` - The mnemonic to use to vote (default: "test test test test test test test test test test test junk") * `-i, --mnemonic-index ` - The index of the mnemonic to use to vote (default: 0) * `-h, --help` - display help for command ### aztec fast-forward-epochs[​](#aztec-fast-forward-epochs "Direct link to aztec fast-forward-epochs") *Help for this command is currently unavailable due to a technical issue with option serialization.* ### aztec generate-bls-keypair[​](#aztec-generate-bls-keypair "Direct link to aztec generate-bls-keypair") Generate a BLS keypair with convenience flags **Usage:** ``` aztec generate-bls-keypair [options] ``` **Options:** * `--mnemonic ` - Mnemonic for BLS derivation * `--ikm ` - Initial keying material for BLS (alternative to mnemonic) * `--bls-path ` - EIP-2334 path (default m/12381/3600/0/0/0) * `--g2` - Derive on G2 subgroup * `--compressed` - Output compressed public key * `--json` - Print JSON output to stdout * `--out ` - Write output to file * `-h, --help` - display help for command ### aztec generate-bootnode-enr[​](#aztec-generate-bootnode-enr "Direct link to aztec generate-bootnode-enr") Generates the encoded ENR record for a bootnode. **Usage:** ``` aztec generate-bootnode-enr [options] ``` **Options:** * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `-h, --help` - display help for command ### aztec generate-keys[​](#aztec-generate-keys "Direct link to aztec generate-keys") Generates and encryption and signing private key pair. **Usage:** ``` aztec generate-keys [options] ``` **Options:** * `--json` - Output the keys in JSON format * `-h, --help` - display help for command ### aztec generate-l1-account[​](#aztec-generate-l1-account "Direct link to aztec generate-l1-account") Generates a new private key for an account on L1. **Usage:** ``` aztec generate-l1-account [options] ``` **Options:** * `--json` - Output the private key in JSON format * `-h, --help` - display help for command ### aztec generate-p2p-private-key[​](#aztec-generate-p2p-private-key "Direct link to aztec generate-p2p-private-key") Generates a private key that can be used for running a node on a LibP2P network. **Usage:** ``` aztec generate-p2p-private-key [options] ``` **Options:** * `-h, --help` - display help for command ### aztec generate-secret-and-hash[​](#aztec-generate-secret-and-hash "Direct link to aztec generate-secret-and-hash") Generates an arbitrary secret (Fr), and its hash (using aztec-nr defaults) **Usage:** ``` aztec generate-secret-and-hash [options] ``` **Options:** * `-h, --help` - display help for command ### aztec get-block[​](#aztec-get-block "Direct link to aztec get-block") Gets info for a given block or latest. **Usage:** ``` aztec get-block [options] [blockNumber] ``` **Options:** * `-n, --node-url ` - URL of the Aztec node (default: "", env: AZTEC\_NODE\_URL) * `-h, --help` - display help for command ### aztec get-canonical-sponsored-fpc-address[​](#aztec-get-canonical-sponsored-fpc-address "Direct link to aztec get-canonical-sponsored-fpc-address") Gets the canonical SponsoredFPC address for this any testnet running on the same version as this CLI **Usage:** ``` aztec get-canonical-sponsored-fpc-address [options] ``` **Options:** * `-h, --help` - display help for command ### aztec get-current-min-fee[​](#aztec-get-current-min-fee "Direct link to aztec get-current-min-fee") Gets the current base fee. **Usage:** ``` aztec get-current-min-fee [options] ``` **Options:** * `-n, --node-url ` - URL of the Aztec node (default: "", env: AZTEC\_NODE\_URL) * `-h, --help` - display help for command ### aztec get-l1-addresses[​](#aztec-get-l1-addresses "Direct link to aztec get-l1-addresses") Gets the addresses of the L1 contracts. **Usage:** ``` aztec get-l1-addresses [options] ``` **Options:** * `-r, --registry-address ` - The address of the registry contract * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \[""], env: ETHEREUM\_HOSTS) * `-v, --rollup-version ` - The version of the rollup * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `--json` - Output the addresses in JSON format * `-h, --help` - display help for command ### aztec get-l1-balance[​](#aztec-get-l1-balance "Direct link to aztec get-l1-balance") Gets the balance of an ERC token in L1 for the given Ethereum address. **Usage:** ``` aztec get-l1-balance [options] ``` **Options:** * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \[""], env: ETHEREUM\_HOSTS) * `-t, --token ` - The address of the token to check the balance of * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `--json` - Output the balance in JSON format * `-h, --help` - display help for command ### aztec get-l1-to-l2-message-witness[​](#aztec-get-l1-to-l2-message-witness "Direct link to aztec get-l1-to-l2-message-witness") Gets a L1 to L2 message witness. **Usage:** ``` aztec get-l1-to-l2-message-witness [options] ``` **Options:** * `-ca, --contract-address
` - Aztec address of the contract. * `--message-hash ` - The L1 to L2 message hash. * `--secret ` - The secret used to claim the L1 to L2 message * `-n, --node-url ` - URL of the Aztec node (default: "", env: AZTEC\_NODE\_URL) * `-h, --help` - display help for command ### aztec get-logs[​](#aztec-get-logs "Direct link to aztec get-logs") Gets public logs for a contract and tag, optionally restricted by block range or tx hash. **Usage:** ``` aztec get-logs [options] ``` **Options:** * `-ca, --contract-address
` - Contract address that emitted the logs. * `--tag ` - Tag (Fr value) to filter logs by. * `-tx, --tx-hash ` - A transaction hash to restrict the search to. * `-fb, --from-block ` - Initial block number for getting logs (defaults to 1). * `-tb, --to-block ` - Up to which block to fetch logs (defaults to latest). \-\-\ to resume pagination after. * `-n, --node-url ` - URL of the Aztec node (default: "", env: AZTEC\_NODE\_URL) * `--follow` - If set, will keep polling for new logs until interrupted. * `-h, --help` - display help for command ### aztec get-node-info[​](#aztec-get-node-info "Direct link to aztec get-node-info") Gets the information of an Aztec node from a PXE or directly from an Aztec node. **Usage:** ``` aztec get-node-info [options] ``` **Options:** * `--json` - Emit output as json * `-n, --node-url ` - URL of the Aztec node (default: "", env: AZTEC\_NODE\_URL) * `-h, --help` - display help for command ### aztec init[​](#aztec-init "Direct link to aztec init") Aztec Init - Create a new Aztec Noir project in the current directory **Usage:** ``` aztec init ``` **Options:** * `-h, --help` - Print help ### aztec inspect-contract[​](#aztec-inspect-contract "Direct link to aztec inspect-contract") Shows list of external callable functions for a contract **Usage:** ``` aztec inspect-contract [options] ``` **Options:** * `-h, --help` - display help for command ### aztec migrate-ha-db[​](#aztec-migrate-ha-db "Direct link to aztec migrate-ha-db") Run validator-ha-signer database migrations **Usage:** ``` aztec migrate-ha-db [options] [command] ``` **Available Commands:** * `down [options]` - Rollback the last migration * `help [command]` - display help for command * `up [options]` - Apply pending migrations **Options:** * `-h --help` - display help for command #### Subcommands[​](#subcommands-1 "Direct link to Subcommands") #### aztec migrate-ha-db down[​](#aztec-migrate-ha-db-down "Direct link to aztec migrate-ha-db down") Rollback the last migration **Usage:** ``` aztec migrate-ha-db down [options] ``` **Options:** * `--database-url ` - PostgreSQL connection string * `--verbose` - Enable verbose output (default: false) * `-h, --help` - display help for command #### aztec migrate-ha-db up[​](#aztec-migrate-ha-db-up "Direct link to aztec migrate-ha-db up") Apply pending migrations **Usage:** ``` aztec migrate-ha-db up [options] ``` **Options:** * `--database-url ` - PostgreSQL connection string * `--verbose` - Enable verbose output (default: false) * `-h, --help` - display help for command ### aztec new[​](#aztec-new "Direct link to aztec new") Aztec New - Create a new Aztec Noir project or add a contract to an existing workspace **Usage:** ``` aztec new ``` **Options:** * `-h, --help` - Print help ### aztec parse-parameter-struct[​](#aztec-parse-parameter-struct "Direct link to aztec parse-parameter-struct") Helper for parsing an encoded string into a contract's parameter struct. **Usage:** ``` aztec parse-parameter-struct [options] ``` **Options:** * `-c, --contract-artifact ` - A compiled Aztec.nr contract's ABI in JSON format or name of a contract ABI exported by @aztec/noir-contracts.js * `-p, --parameter ` - The name of the struct parameter to decode into * `-h, --help` - display help for command ### aztec preload-crs[​](#aztec-preload-crs "Direct link to aztec preload-crs") Preload the points data needed for proving and verifying **Usage:** ``` aztec preload-crs [options] ``` **Options:** * `-h, --help` - display help for command ### aztec profile[​](#aztec-profile "Direct link to aztec profile") Profile compiled Aztec artifacts. **Usage:** ``` aztec profile [options] [command] ``` **Available Commands:** * `flamegraph ` - Generate a gate count flamegraph SVG for a contract function. * `gates [options] [target-dir]` - Display gate counts for all compiled Aztec artifacts in a target directory. * `help [command]` - display help for command **Options:** * `-h --help` - display help for command #### Subcommands[​](#subcommands-2 "Direct link to Subcommands") #### aztec profile flamegraph[​](#aztec-profile-flamegraph "Direct link to aztec profile flamegraph") Generate a gate count flamegraph SVG for a contract function. **Usage:** ``` aztec profile flamegraph [options] ``` **Options:** * `-h, --help` - display help for command #### aztec profile gates[​](#aztec-profile-gates "Direct link to aztec profile gates") Display gate counts for all compiled Aztec artifacts in a target directory. **Usage:** ``` aztec profile gates [options] [target-dir] ``` **Options:** * `--json` - Output gate counts as JSON instead of a table (default: false) * `-h, --help` - display help for command ### aztec propose-with-lock[​](#aztec-propose-with-lock "Direct link to aztec propose-with-lock") Makes a proposal to governance with a lock **Usage:** ``` aztec propose-with-lock [options] ``` **Options:** * `-r, --registry-address ` - The address of the registry contract * `-p, --payload-address ` - The address of the payload contract * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \[""], env: ETHEREUM\_HOSTS) * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `-pk, --private-key ` - The private key to use to propose * `-m, --mnemonic ` - The mnemonic to use to propose (default: "test test test test test test test test test test test junk") * `-i, --mnemonic-index ` - The index of the mnemonic to use to propose (default: 0) * `--json` - Output the proposal ID in JSON format * `-h, --help` - display help for command ### aztec prover[​](#aztec-prover "Direct link to aztec prover") Operate a prover node via its admin JSON-RPC endpoint **Usage:** ``` aztec prover [options] [command] ``` **Available Commands:** * `get-jobs [options]` - Lists the prover node proving jobs * `help [command]` - display help for command * `start-proof [options]` - Schedules proving for the given epoch **Options:** * `-h --help` - display help for command #### Subcommands[​](#subcommands-3 "Direct link to Subcommands") #### aztec prover get-jobs[​](#aztec-prover-get-jobs "Direct link to aztec prover get-jobs") Lists the prover node proving jobs **Usage:** ``` aztec prover get-jobs [options] ``` **Options:** * `--admin-url ` - URL of the prover node admin JSON-RPC endpoint * `--api-key ` - Admin API key * `-h, --help` - display help for command #### aztec prover start-proof[​](#aztec-prover-start-proof "Direct link to aztec prover start-proof") Schedules proving for the given epoch **Usage:** ``` aztec prover start-proof [options] ``` **Options:** * `--epoch ` - Epoch number to prove * `--admin-url ` - URL of the prover node admin JSON-RPC endpoint * `--api-key ` - Admin API key * `-h, --help` - display help for command ### aztec prune-rollup[​](#aztec-prune-rollup "Direct link to aztec prune-rollup") Prunes the pending chain on the rollup contract. **Usage:** ``` aztec prune-rollup [options] ``` **Options:** * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \[""], env: ETHEREUM\_HOSTS) * `-pk, --private-key ` - The private key to use for deployment * `-m, --mnemonic ` - The mnemonic to use in deployment (default: "test test test test test test test test test test test junk") * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `--rollup
` - ethereum address of the rollup contract * `-h, --help` - display help for command ### aztec remove-l1-validator[​](#aztec-remove-l1-validator "Direct link to aztec remove-l1-validator") Removes a validator to the L1 rollup contract. **Usage:** ``` aztec remove-l1-validator [options] ``` **Options:** * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \[""], env: ETHEREUM\_HOSTS) * `-pk, --private-key ` - The private key to use for deployment * `-m, --mnemonic ` - The mnemonic to use in deployment (default: "test test test test test test test test test test test junk") * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `--validator
` - ethereum address of the validator * `--rollup
` - ethereum address of the rollup contract * `-h, --help` - display help for command ### aztec sequencers[​](#aztec-sequencers "Direct link to aztec sequencers") Manages or queries registered sequencers on the L1 rollup contract. **Usage:** ``` aztec sequencers [options] [who] ``` **Options:** * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \[""]) * `-m, --mnemonic ` - The mnemonic for the sender of the tx (default: "test test test test test test test test test test test junk") * `--block-number ` - Block number to query next sequencer for * `-n, --node-url ` - URL of the Aztec node (default: "", env: AZTEC\_NODE\_URL) * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `-h, --help` - display help for command ### aztec setup-protocol-contracts[​](#aztec-setup-protocol-contracts "Direct link to aztec setup-protocol-contracts") Bootstrap the blockchain by initializing all the protocol contracts **Usage:** ``` aztec setup-protocol-contracts [options] ``` **Options:** * `-n, --node-url ` - URL of the Aztec node (default: "", env: AZTEC\_NODE\_URL) * `--testAccounts` - Deploy funded test accounts. * `--json` - Output the contract addresses in JSON format * `-h, --help` - display help for command ### aztec start[​](#aztec-start "Direct link to aztec start") **MISC** * `--network ` Network to run Aztec on *Environment: `$NETWORK`* * `--enable-auto-shutdown` Soft-shutdown the node when the canonical rollup is no longer compatible (protocol constants diverge), keeping the health server up so K8s probes keep passing. Only applies to nodes following the canonical rollup. *Environment: `$ENABLE_AUTO_SHUTDOWN`* * `--sync-mode ` (default: `snapshot`) Set sync mode to `full` to always sync via L1, `snapshot` to download a snapshot if there is no local data, `force-snapshot` to download even if there is local data. *Environment: `$SYNC_MODE`* * `--snapshots-urls ` Base URLs for snapshots index, comma-separated. *Environment: `$SYNC_SNAPSHOTS_URLS`* * `--fisherman-mode` Whether to run in fisherman mode. *Environment: `$FISHERMAN_MODE`* * `--local-network` Starts Aztec Local Network * `--local-network.l1Mnemonic ` (default: `test test test test test test test test test test test junk`) Mnemonic for L1 accounts. Will be used *Environment: `$MNEMONIC`* * `--local-network.testAccounts` (default: `true`) Deploy test accounts on local network start *Environment: `$TEST_ACCOUNTS`* **API** * `--port ` (default: `8080`) Port to run the Aztec Services on *Environment: `$AZTEC_PORT`* * `--admin-port ` (default: `8880`) Port to run admin APIs of Aztec Services on *Environment: `$AZTEC_ADMIN_PORT`* * `--admin-api-key-hash ` SHA-256 hex hash of a pre-generated admin API key. When set, the node uses this hash for authentication instead of auto-generating a key. *Environment: `$AZTEC_ADMIN_API_KEY_HASH`* * `--disable-admin-api-key` Disable API key authentication on the admin RPC endpoint. By default, a key is auto-generated, displayed once, and its hash is persisted. *Environment: `$AZTEC_DISABLE_ADMIN_API_KEY`* * `--reset-admin-api-key` Force-generate a new admin API key, replacing any previously persisted key hash. The new key is displayed once at startup. *Environment: `$AZTEC_RESET_ADMIN_API_KEY`* * `--node-debug` Expose debug endpoints (e.g. mineBlock) on the main RPC port *Environment: `$AZTEC_NODE_DEBUG`* * `--api-prefix ` Prefix for API routes on any service that is started *Environment: `$API_PREFIX`* * `--rpcMaxBatchSize ` (default: `100`) Maximum allowed batch size for JSON RPC batch requests. *Environment: `$RPC_MAX_BATCH_SIZE`* * `--rpcMaxBodySize ` (default: `1mb`) Maximum allowed batch size for JSON RPC batch requests. *Environment: `$RPC_MAX_BODY_SIZE`* **ETHEREUM** * `--l1-chain-id ` The chain ID of the ethereum host. *Environment: `$L1_CHAIN_ID`* * `--l1-rpc-urls ` List of URLs of Ethereum RPC nodes that services will connect to (comma separated). *Environment: `$ETHEREUM_HOSTS`* * `--l1-consensus-host-urls ` List of URLs of the Ethereum consensus nodes that services will connect to (comma separated) *Environment: `$L1_CONSENSUS_HOST_URLS`* * `--l1-consensus-host-api-keys ` List of API keys for the corresponding L1 consensus clients, if needed. Added to the end of the corresponding URL as "?key=\" unless a header is defined *Environment: `$L1_CONSENSUS_HOST_API_KEYS`* * `--l1-consensus-host-api-key-headers ` List of header names for the corresponding L1 consensus client API keys, if needed. Added to the corresponding request as "\: \" *Environment: `$L1_CONSENSUS_HOST_API_KEY_HEADERS`* * `--registry-address ` The deployed L1 registry contract address. *Environment: `$REGISTRY_CONTRACT_ADDRESS`* * `--rollup-version ` The version of the rollup. *Environment: `$ROLLUP_VERSION`* **STORAGE** * `--data-directory ` Optional dir to store data. If omitted will store in memory. *Environment: `$DATA_DIRECTORY`* * `--data-store-map-size-kb ` (default: `134217728`) The maximum possible size of a data store DB in KB. Can be overridden by component-specific options. *Environment: `$DATA_STORE_MAP_SIZE_KB`* **WORLD STATE** * `--world-state-data-directory ` Optional directory for the world state database *Environment: `$WS_DATA_DIRECTORY`* * `--world-state-db-map-size-kb ` The maximum possible size of the world state DB in KB. Overwrites the general dataStoreMapSizeKb. *Environment: `$WS_DB_MAP_SIZE_KB`* * `--world-state-checkpoint-history ` (default: `64`) The number of historic checkpoints worth of blocks to maintain. Values less than 1 mean all history is maintained *Environment: `$WS_NUM_HISTORIC_CHECKPOINTS`* **AZTEC NODE** * `--node` Starts Aztec Node with options **ARCHIVER** * `--archiver.blobSinkMapSizeKb ` The maximum possible size of the blob sink DB in KB. Overwrites the general dataStoreMapSizeKb. *Environment: `$BLOB_SINK_MAP_SIZE_KB`* * `--archiver.blobAllowEmptySources ` Whether to allow having no blob sources configured during startup *Environment: `$BLOB_ALLOW_EMPTY_SOURCES`* * `--archiver.blobFileStoreUrls ` URLs for filestore blob archive, comma-separated. Tried in order until blobs are found. *Environment: `$BLOB_FILE_STORE_URLS`* * `--archiver.blobFileStoreUploadUrl ` URL for uploading blobs to filestore (s3://, gs\://, file://) *Environment: `$BLOB_FILE_STORE_UPLOAD_URL`* * `--archiver.blobHealthcheckUploadIntervalMinutes ` Interval in minutes for uploading healthcheck file to file store (default: 60 = 1 hour) *Environment: `$BLOB_HEALTHCHECK_UPLOAD_INTERVAL_MINUTES`* * `--archiver.blobPreferFilestores ` Whether to prefer filestores over consensus clients when fetching blobs. Default: false. *Environment: `$BLOB_PREFER_FILESTORES`* * `--archiver.blobFileStoreTimeoutMs ` Timeout in ms for HTTP requests to the blob file store. Default: 10000 (10s). *Environment: `$BLOB_FILE_STORE_TIMEOUT_MS`* * `--archiver.archiveApiUrl ` The URL of the archive API *Environment: `$BLOB_ARCHIVE_API_URL`* * `--archiver.archiverPollingIntervalMS ` (default: `500`) The polling interval in ms for retrieving new L2 blocks and encrypted logs. *Environment: `$ARCHIVER_POLLING_INTERVAL_MS`* * `--archiver.archiverBatchSize ` (default: `100`) The number of L2 blocks the archiver will attempt to download at a time. *Environment: `$ARCHIVER_BATCH_SIZE`* * `--archiver.archiverStoreMapSizeKb ` The maximum possible size of the archiver DB in KB. Overwrites the general dataStoreMapSizeKb. *Environment: `$ARCHIVER_STORE_MAP_SIZE_KB`* * `--archiver.blockDurationMs ` Duration per block in milliseconds when building multiple blocks per slot. Used to derive orphan proposed block pruning timing. *Environment: `$SEQ_BLOCK_DURATION_MS`* * `--archiver.checkpointProposalSyncGraceSeconds ` Consensus grace in seconds for a received checkpoint proposal to materialize into local proposed state. *Environment: `$CHECKPOINT_PROPOSAL_SYNC_GRACE_SECONDS`* * `--archiver.skipValidateCheckpointAttestations ` Skip validating checkpoint attestations (for testing purposes only) * `--archiver.skipPromoteProposedCheckpointDuringL1Sync ` Skip promoting proposed checkpoints during L1 sync (for testing purposes only) * `--archiver.maxAllowedEthClientDriftSeconds ` (default: `300`) Maximum allowed drift in seconds between the Ethereum client and current time. *Environment: `$MAX_ALLOWED_ETH_CLIENT_DRIFT_SECONDS`* * `--archiver.ethereumAllowNoDebugHosts ` (default: `true`) Whether to allow starting the archiver without debug/trace method support on Ethereum hosts *Environment: `$ETHEREUM_ALLOW_NO_DEBUG_HOSTS`* * `--archiver.archiverSkipHistoricalLogsCheck ` Skip the startup check that probes the L1 RPC for historical Rollup contract logs. Set to true to bypass the check when the connected RPC node is known to prune old logs. *Environment: `$ARCHIVER_SKIP_HISTORICAL_LOGS_CHECK`* * `--archiver.orphanPruneNoProposalTolerance ` (default: `1`) Local tolerance in seconds before pruning an orphan block when no checkpoint proposal was received. *Environment: `$ARCHIVER_ORPHAN_PRUNE_NO_PROPOSAL_TOLERANCE`* * `--archiver.skipOrphanProposedBlockPruning ` Skip pruning orphan proposed blocks that have no matching proposed checkpoint. *Environment: `$ARCHIVER_SKIP_ORPHAN_PROPOSED_BLOCK_PRUNING`* **SEQUENCER** * `--sequencer` Starts Aztec Sequencer with options * `--sequencer.blockDurationMs ` (default: `3000`) Duration per block in milliseconds, used to derive how many blocks fit in a slot. *Environment: `$SEQ_BLOCK_DURATION_MS`* * `--sequencer.validatorPrivateKeys ` (default: `[Redacted]`) List of private keys of the validators participating in attestation duties *Environment: `$VALIDATOR_PRIVATE_KEYS`* * `--sequencer.validatorAddresses ` List of addresses of the validators to use with remote signers *Environment: `$VALIDATOR_ADDRESSES`* * `--sequencer.disableValidator ` Do not run the validator *Environment: `$VALIDATOR_DISABLED`* * `--sequencer.disabledValidators ` Temporarily disable these specific validator addresses * `--sequencer.attestationPollingIntervalMs ` (default: `200`) Interval between polling for new attestations *Environment: `$VALIDATOR_ATTESTATIONS_POLLING_INTERVAL_MS`* * `--sequencer.alwaysReexecuteBlockProposals ` (default: `true`) Whether to always reexecute block proposals, even for non-validator nodes (useful for monitoring network status). * `--sequencer.skipCheckpointProposalValidation ` Skip checkpoint proposal validation and always attest (default: false) * `--sequencer.skipPushProposedBlocksToArchiver ` Skip pushing proposed blocks to archiver (default: true) * `--sequencer.attestToEquivocatedProposals ` Agree to attest to equivocated checkpoint proposals (for testing purposes only) * `--sequencer.skipProposalSlotValidation ` Accept proposal validation regardless of slot timing (for testing only) * `--sequencer.validateMaxL2BlockGas ` Maximum L2 block gas for validation. Proposals exceeding this limit are rejected. *Environment: `$VALIDATOR_MAX_L2_BLOCK_GAS`* * `--sequencer.validateMaxDABlockGas ` Maximum DA block gas for validation. Proposals exceeding this limit are rejected. *Environment: `$VALIDATOR_MAX_DA_BLOCK_GAS`* * `--sequencer.validateMaxTxsPerBlock ` Maximum transactions per block for validation. Proposals exceeding this limit are rejected. *Environment: `$VALIDATOR_MAX_TX_PER_BLOCK`* * `--sequencer.validateMaxTxsPerCheckpoint ` Maximum transactions per checkpoint for validation. Proposals exceeding this limit are rejected. *Environment: `$VALIDATOR_MAX_TX_PER_CHECKPOINT`* * `--sequencer.nodeId ` The unique identifier for this node *Environment: `$VALIDATOR_HA_NODE_ID`* * `--sequencer.pollingIntervalMs ` (default: `100`) The number of ms to wait between polls when a duty is being signed *Environment: `$VALIDATOR_HA_POLLING_INTERVAL_MS`* * `--sequencer.signingTimeoutMs ` (default: `3000`) The maximum time to wait for a duty being signed to complete *Environment: `$VALIDATOR_HA_SIGNING_TIMEOUT_MS`* * `--sequencer.maxStuckDutiesAgeMs ` The maximum age of a stuck duty in ms (defaults to 2x Aztec slot duration) *Environment: `$VALIDATOR_HA_MAX_STUCK_DUTIES_AGE_MS`* * `--sequencer.cleanupOldDutiesAfterHours ` Optional: clean up old duties after this many hours (disabled if not set) *Environment: `$VALIDATOR_HA_OLD_DUTIES_MAX_AGE_H`* * `--sequencer.signingProtectionMapSizeKb ` Maximum size of the local signing-protection LMDB store in KB. Overwrites the general dataStoreMapSizeKb. *Environment: `$SIGNING_PROTECTION_MAP_SIZE_KB`* * `--sequencer.haSigningEnabled ` Whether HA signing / slashing protection is enabled *Environment: `$VALIDATOR_HA_SIGNING_ENABLED`* * `--sequencer.databaseUrl ` PostgreSQL connection string for validator HA signer (format: postgresql://user:password@host:port/database) *Environment: `$VALIDATOR_HA_DATABASE_URL`* * `--sequencer.poolMaxCount ` (default: `10`) Maximum number of clients in the pool *Environment: `$VALIDATOR_HA_POOL_MAX`* * `--sequencer.poolMinCount ` Minimum number of clients in the pool *Environment: `$VALIDATOR_HA_POOL_MIN`* * `--sequencer.poolIdleTimeoutMs ` (default: `10000`) Idle timeout in milliseconds *Environment: `$VALIDATOR_HA_POOL_IDLE_TIMEOUT_MS`* * `--sequencer.poolConnectionTimeoutMs ` Connection timeout in milliseconds (0 means no timeout) *Environment: `$VALIDATOR_HA_POOL_CONNECTION_TIMEOUT_MS`* * `--sequencer.sequencerPollingIntervalMS ` (default: `500`) The number of ms to wait between polling for checking to build on the next slot. *Environment: `$SEQ_POLLING_INTERVAL_MS`* * `--sequencer.maxTxsPerCheckpoint ` The maximum number of txs across all blocks in a checkpoint. *Environment: `$SEQ_MAX_TX_PER_CHECKPOINT`* * `--sequencer.minTxsPerBlock ` (default: `1`) The minimum number of txs to include in a block. *Environment: `$SEQ_MIN_TX_PER_BLOCK`* * `--sequencer.minValidTxsPerBlock ` The minimum number of valid txs (after execution) to include in a block. If not set, falls back to minTxsPerBlock. * `--sequencer.publishTxsWithProposals ` Whether to publish txs with proposals. *Environment: `$SEQ_PUBLISH_TXS_WITH_PROPOSALS`* * `--sequencer.maxL2BlockGas ` The maximum L2 block gas. *Environment: `$SEQ_MAX_L2_BLOCK_GAS`* * `--sequencer.maxDABlockGas ` The maximum DA block gas. *Environment: `$SEQ_MAX_DA_BLOCK_GAS`* * `--sequencer.perBlockAllocationMultiplier ` (default: `1.2`) Per-block gas budget multiplier for both L2 and DA gas. Budget per block is (checkpointLimit / maxBlocks) \* multiplier. Values greater than one allow early blocks to use more than their even share, relying on checkpoint-level capping for later blocks. *Environment: `$SEQ_PER_BLOCK_ALLOCATION_MULTIPLIER`* * `--sequencer.perBlockDAAllocationMultiplier ` (default: `1.5`) Per-block budget multiplier applied to DA gas and blob fields in place of perBlockAllocationMultiplier. Defaults higher than the general multiplier so the largest contract class deploy fits a single block. *Environment: `$SEQ_PER_BLOCK_DA_ALLOCATION_MULTIPLIER`* * `--sequencer.redistributeCheckpointBudget ` (default: `true`) Redistribute remaining checkpoint budget evenly across remaining blocks instead of allowing a single block to consume the entire remaining budget. *Environment: `$SEQ_REDISTRIBUTE_CHECKPOINT_BUDGET`* * `--sequencer.coinbase ` Recipient of block reward. *Environment: `$COINBASE`* * `--sequencer.feeRecipient ` Address to receive fees. *Environment: `$FEE_RECIPIENT`* * `--sequencer.acvmWorkingDirectory ` The working directory to use for simulation/proving *Environment: `$ACVM_WORKING_DIRECTORY`* * `--sequencer.acvmBinaryPath ` The path to the ACVM binary *Environment: `$ACVM_BINARY_PATH`* * `--sequencer.governanceProposerPayload ` The address of the payload for the governanceProposer *Environment: `$GOVERNANCE_PROPOSER_PAYLOAD_ADDRESS`* * `--sequencer.l1PublishingTime ` (default: `12`) How much time in seconds to allow in the slot for publishing the L1 transaction. *Environment: `$SEQ_L1_PUBLISHING_TIME_ALLOWANCE_IN_SLOT`* * `--sequencer.secondsBeforeInvalidatingBlockAsCommitteeMember ` (default: `144`) How many seconds to wait before trying to invalidate a block from the pending chain as a committee member (zero to never invalidate). The next proposer is expected to invalidate, so the committee acts as a fallback. *Environment: `$SEQ_SECONDS_BEFORE_INVALIDATING_BLOCK_AS_COMMITTEE_MEMBER`* * `--sequencer.secondsBeforeInvalidatingBlockAsNonCommitteeMember ` (default: `432`) How many seconds to wait before trying to invalidate a block from the pending chain as a non-committee member (zero to never invalidate). The next proposer is expected to invalidate, then the committee, so other sequencers act as a fallback. *Environment: `$SEQ_SECONDS_BEFORE_INVALIDATING_BLOCK_AS_NON_COMMITTEE_MEMBER`* * `--sequencer.skipWaitForValidParentCheckpointOnL1 ` Bypass the parent checkpoint validity check before submitting a pipelined checkpoint, allowing the proposer to publish even when the parent landed on L1 with invalid attestations (for testing only) * `--sequencer.broadcastInvalidBlockProposal ` Broadcast invalid block proposals with corrupted state (for testing only) * `--sequencer.invalidBlockProposalIndexWithinCheckpoint ` Broadcast an invalid block proposal only at this indexWithinCheckpoint (for testing only) * `--sequencer.broadcastInvalidCheckpointProposalOnly ` Broadcast invalid checkpoint proposals while keeping the underlying block proposals valid (for testing only). When unset, the checkpoint follows broadcastInvalidBlockProposal. * `--sequencer.injectFakeAttestation ` Inject a fake attestation (for testing only) * `--sequencer.injectHighSValueAttestation ` Inject a malleable attestation with a high-s value (for testing only) * `--sequencer.injectUnrecoverableSignatureAttestation ` Inject an attestation with an unrecoverable signature (for testing only) * `--sequencer.shuffleAttestationOrdering ` Shuffle attestation ordering to create invalid ordering (for testing only) * `--sequencer.expectedBlockProposalsPerSlot ` Expected number of block proposals per slot for P2P peer scoring. 0 (default) disables block proposal scoring. Set to a positive value to enable. *Environment: `$SEQ_EXPECTED_BLOCK_PROPOSALS_PER_SLOT`* * `--sequencer.checkpointProposalSyncGraceSeconds ` (default: `6`) Consensus grace in seconds for a received checkpoint proposal to materialize into local proposed state. Defaults to twice the block duration. *Environment: `$CHECKPOINT_PROPOSAL_SYNC_GRACE_SECONDS`* * `--sequencer.maxTxsPerBlock ` The maximum number of txs to include in a block. *Environment: `$SEQ_MAX_TX_PER_BLOCK`* * `--sequencer.attestationPropagationTime ` (default: `2`) How many seconds it takes for proposals and attestations to travel across the p2p layer (one-way). *Environment: `$SEQ_ATTESTATION_PROPAGATION_TIME`* * `--sequencer.checkpointProposalPrepareTime ` (default: `1`) Local time in seconds between the last block build finishing and the checkpoint proposal being ready for p2p send. *Environment: `$SEQ_CHECKPOINT_PROPOSAL_PREPARE_TIME`* * `--sequencer.minBlockDuration ` (default: `2`) Minimum block-building time in seconds still worth allocating if the proposer starts late. *Environment: `$SEQ_MIN_BLOCK_DURATION`* * `--sequencer.maxBlocksPerCheckpoint ` (default: `24`) Maximum number of blocks the sequencer packs into a single checkpoint, and the maximum indexWithinCheckpoint accepted on inbound block proposals. *Environment: `$MAX_BLOCKS_PER_CHECKPOINT`* * `--sequencer.buildCheckpointIfEmpty ` Have sequencer build and publish an empty checkpoint if there are no txs *Environment: `$SEQ_BUILD_CHECKPOINT_IF_EMPTY`* * `--sequencer.minBlocksForCheckpoint ` Minimum number of blocks required for a checkpoint proposal (test only) * `--sequencer.skipPublishingCheckpointsPercent ` Percent probability (0 - 100) of sequencer skipping checkpoint publishing (testing only) *Environment: `$SEQ_SKIP_CHECKPOINT_PUBLISH_PERCENT`* * `--sequencer.skipBroadcastProposals ` Skip broadcasting checkpoint and block proposals via gossipsub when proposer (for testing only) * `--sequencer.skipBroadcastCheckpointProposal ` Skip broadcasting only the CheckpointProposal via gossipsub when proposer; the held last block is broadcast standalone instead so peers still receive it as a proposed-but-uncheckpointed tip (for testing only) * `--sequencer.pauseProposingForSlots ` List of slots for which the sequencer will not produce a proposal (for testing only). Attestation paths are unaffected. * `--sequencer.txPublicSetupAllowListExtend ` Additional entries to extend the default setup allow list. Format: I:address :flags ,C:classId :flags . Flags: os (onlySelf), rn (rejectNullMsgSender), cl=N (calldataLength), joined with +. *Environment: `$TX_PUBLIC_SETUP_ALLOWLIST`* * `--sequencer.keyStoreDirectory ` Location of key store directory *Environment: `$KEY_STORE_DIRECTORY`* * `--sequencer.sequencerPublisherPrivateKeys ` The private keys to be used by the sequencer publisher. *Environment: `$SEQ_PUBLISHER_PRIVATE_KEYS`* * `--sequencer.sequencerPublisherAddresses ` The addresses of the publishers to use with remote signers *Environment: `$SEQ_PUBLISHER_ADDRESSES`* * `--sequencer.blobAllowEmptySources ` Whether to allow having no blob sources configured during startup *Environment: `$BLOB_ALLOW_EMPTY_SOURCES`* * `--sequencer.blobFileStoreUrls ` URLs for filestore blob archive, comma-separated. Tried in order until blobs are found. *Environment: `$BLOB_FILE_STORE_URLS`* * `--sequencer.blobFileStoreUploadUrl ` URL for uploading blobs to filestore (s3://, gs\://, file://) *Environment: `$BLOB_FILE_STORE_UPLOAD_URL`* * `--sequencer.blobHealthcheckUploadIntervalMinutes ` Interval in minutes for uploading healthcheck file to file store (default: 60 = 1 hour) *Environment: `$BLOB_HEALTHCHECK_UPLOAD_INTERVAL_MINUTES`* * `--sequencer.blobPreferFilestores ` Whether to prefer filestores over consensus clients when fetching blobs. Default: false. *Environment: `$BLOB_PREFER_FILESTORES`* * `--sequencer.blobFileStoreTimeoutMs ` Timeout in ms for HTTP requests to the blob file store. Default: 10000 (10s). *Environment: `$BLOB_FILE_STORE_TIMEOUT_MS`* * `--sequencer.archiveApiUrl ` The URL of the archive API *Environment: `$BLOB_ARCHIVE_API_URL`* * `--sequencer.sequencerPublisherAllowInvalidStates ` (default: `true`) True to use publishers in invalid states (timed out, cancelled, etc) if no other is available *Environment: `$SEQ_PUBLISHER_ALLOW_INVALID_STATES`* * `--sequencer.sequencerPublisherForwarderAddress ` Address of the forwarder contract to wrap all L1 transactions through (for testing purposes only) *Environment: `$SEQ_PUBLISHER_FORWARDER_ADDRESS`* * `--sequencer.sequencerPublisherPreviousL1BlockWaitTimeoutMs ` (default: `8000`) How long to wait for the previous L1 block before sending scheduled publisher txs anyway, in milliseconds. *Environment: `$SEQ_PUBLISHER_PREVIOUS_L1_BLOCK_WAIT_TIMEOUT_MS`* * `--sequencer.sequencerPublisherPreviousL1BlockWaitPollIntervalMs ` (default: `500`) Poll interval while waiting for the previous L1 block before scheduled publisher txs, in milliseconds. *Environment: `$SEQ_PUBLISHER_PREVIOUS_L1_BLOCK_WAIT_POLL_INTERVAL_MS`* * `--sequencer.l1TxFailedStore ` Store for failed L1 transaction inputs (test networks only). Format: gs\://bucket/path *Environment: `$L1_TX_FAILED_STORE`* * `--sequencer.publisherFundingThreshold ` Min ETH balance below which a publisher gets funded. Specified in ether (e.g. 0.1). Unset = funding disabled. *Environment: `$PUBLISHER_FUNDING_THRESHOLD`* * `--sequencer.publisherFundingAmount ` Amount of ETH to send when funding a publisher. Specified in ether (e.g. 0.5). Unset = funding disabled. *Environment: `$PUBLISHER_FUNDING_AMOUNT`* **PROVER NODE** * `--prover-node` Starts Aztec Prover Node with options * `--proverNode.keyStoreDirectory ` Location of key store directory *Environment: `$KEY_STORE_DIRECTORY`* * `--proverNode.acvmWorkingDirectory ` The working directory to use for simulation/proving *Environment: `$ACVM_WORKING_DIRECTORY`* * `--proverNode.acvmBinaryPath ` The path to the ACVM binary *Environment: `$ACVM_BINARY_PATH`* * `--proverNode.bbWorkingDirectory ` The working directory to use for proving *Environment: `$BB_WORKING_DIRECTORY`* * `--proverNode.bbBinaryPath ` The path to the bb binary *Environment: `$BB_BINARY_PATH`* * `--proverNode.bbSkipCleanup ` Whether to skip cleanup of bb temporary files *Environment: `$BB_SKIP_CLEANUP`* * `--proverNode.numConcurrentIVCVerifiers ` (default: `8`) Max concurrent verifications for the RPC verifier (QueuedIVCVerifier). *Environment: `$BB_NUM_IVC_VERIFIERS`* * `--proverNode.bbIVCConcurrency ` (default: `1`) Thread count for the RPC IVC verifier. *Environment: `$BB_IVC_CONCURRENCY`* * `--proverNode.bbChonkVerifyMaxBatch ` (default: `16`) Upper bound on proofs per batch for the peer chonk batch verifier. Proofs are verified immediately as they arrive; this only caps how many can accumulate while a batch is already being processed. *Environment: `$BB_CHONK_VERIFY_MAX_BATCH`* * `--proverNode.bbChonkVerifyConcurrency ` (default: `6`) Thread count for the peer batch verifier parallel reduce. 0 = auto. *Environment: `$BB_CHONK_VERIFY_BATCH_CONCURRENCY`* * `--proverNode.bbDebugOutputDir ` When set, bb.js operations write input/output files and log equivalent CLI commands to this directory *Environment: `$BB_DEBUG_OUTPUT_DIR`* * `--proverNode.nodeUrl ` The URL to the Aztec node to take proving jobs from *Environment: `$AZTEC_NODE_URL`* * `--proverNode.proverId ` Hex value that identifies the prover. Defaults to the address used for submitting proofs if not set. *Environment: `$PROVER_ID`* * `--proverNode.failedProofStore ` Store for failed proof inputs. Google cloud storage is only supported at the moment. Set this value as gs\://bucket-name/path/to/store. *Environment: `$PROVER_FAILED_PROOF_STORE`* * `--proverNode.enqueueConcurrency ` (default: `50`) Max concurrent jobs the orchestrator serializes and enqueues to the broker. *Environment: `$PROVER_ENQUEUE_CONCURRENCY`* * `--proverNode.blobSinkMapSizeKb ` The maximum possible size of the blob sink DB in KB. Overwrites the general dataStoreMapSizeKb. *Environment: `$BLOB_SINK_MAP_SIZE_KB`* * `--proverNode.blobAllowEmptySources ` Whether to allow having no blob sources configured during startup *Environment: `$BLOB_ALLOW_EMPTY_SOURCES`* * `--proverNode.blobFileStoreUrls ` URLs for filestore blob archive, comma-separated. Tried in order until blobs are found. *Environment: `$BLOB_FILE_STORE_URLS`* * `--proverNode.blobFileStoreUploadUrl ` URL for uploading blobs to filestore (s3://, gs\://, file://) *Environment: `$BLOB_FILE_STORE_UPLOAD_URL`* * `--proverNode.blobHealthcheckUploadIntervalMinutes ` Interval in minutes for uploading healthcheck file to file store (default: 60 = 1 hour) *Environment: `$BLOB_HEALTHCHECK_UPLOAD_INTERVAL_MINUTES`* * `--proverNode.blobPreferFilestores ` Whether to prefer filestores over consensus clients when fetching blobs. Default: false. *Environment: `$BLOB_PREFER_FILESTORES`* * `--proverNode.blobFileStoreTimeoutMs ` Timeout in ms for HTTP requests to the blob file store. Default: 10000 (10s). *Environment: `$BLOB_FILE_STORE_TIMEOUT_MS`* * `--proverNode.archiveApiUrl ` The URL of the archive API *Environment: `$BLOB_ARCHIVE_API_URL`* * `--proverNode.proverPublisherAllowInvalidStates ` (default: `true`) True to use publishers in invalid states (timed out, cancelled, etc) if no other is available *Environment: `$PROVER_PUBLISHER_ALLOW_INVALID_STATES`* * `--proverNode.proverPublisherForwarderAddress ` Address of the forwarder contract to wrap all L1 transactions through (for testing purposes only) *Environment: `$PROVER_PUBLISHER_FORWARDER_ADDRESS`* * `--proverNode.publisherFundingThreshold ` Min ETH balance below which a publisher gets funded. Specified in ether (e.g. 0.1). Unset = funding disabled. *Environment: `$PUBLISHER_FUNDING_THRESHOLD`* * `--proverNode.publisherFundingAmount ` Amount of ETH to send when funding a publisher. Specified in ether (e.g. 0.5). Unset = funding disabled. *Environment: `$PUBLISHER_FUNDING_AMOUNT`* * `--proverNode.proverPublisherPrivateKeys ` The private keys to be used by the prover publisher. *Environment: `$PROVER_PUBLISHER_PRIVATE_KEYS`* * `--proverNode.proverPublisherAddresses ` The addresses of the publishers to use with remote signers *Environment: `$PROVER_PUBLISHER_ADDRESSES`* * `--proverNode.proverNodeMaxPendingJobs ` (default: `10`) The maximum number of pending jobs for the prover node *Environment: `$PROVER_NODE_MAX_PENDING_JOBS`* * `--proverNode.proverNodePollingIntervalMs ` (default: `1000`) The interval in milliseconds to poll for new jobs *Environment: `$PROVER_NODE_POLLING_INTERVAL_MS`* * `--proverNode.proverNodeMaxParallelBlocksPerEpoch ` The Maximum number of blocks to process in parallel while proving an epoch *Environment: `$PROVER_NODE_MAX_PARALLEL_BLOCKS_PER_EPOCH`* * `--proverNode.proverNodeFailedEpochStore ` File store where to upload node state when an epoch fails to be proven *Environment: `$PROVER_NODE_FAILED_EPOCH_STORE`* * `--proverNode.proverNodeEpochProvingDelayMs ` Optional delay in milliseconds to wait for late-arriving events (e.g. reorgs) to settle before starting top-tree proving for an epoch * `--proverNode.txGatheringIntervalMs ` (default: `1000`) How often to check that tx data is available *Environment: `$PROVER_NODE_TX_GATHERING_INTERVAL_MS`* * `--proverNode.txGatheringBatchSize ` (default: `10`) How many transactions to gather from a node in a single request *Environment: `$PROVER_NODE_TX_GATHERING_BATCH_SIZE`* * `--proverNode.txGatheringMaxParallelRequestsPerNode ` (default: `100`) How many tx requests to make in parallel to each node *Environment: `$PROVER_NODE_TX_GATHERING_MAX_PARALLEL_REQUESTS_PER_NODE`* * `--proverNode.txGatheringTimeoutMs ` (default: `120000`) How long to wait for tx data to be available before giving up *Environment: `$PROVER_NODE_TX_GATHERING_TIMEOUT_MS`* * `--proverNode.proverNodeDisableProofPublish ` Whether the prover node skips publishing proofs to L1 *Environment: `$PROVER_NODE_DISABLE_PROOF_PUBLISH`* * `--proverNode.web3SignerUrl ` URL of the Web3Signer instance *Environment: `$WEB3_SIGNER_URL`* **PROVER BROKER** * `--prover-broker` Starts Aztec proving job broker * `--proverBroker.proverBrokerJobTimeoutMs ` (default: `30000`) Jobs are retried if not kept alive for this long *Environment: `$PROVER_BROKER_JOB_TIMEOUT_MS`* * `--proverBroker.proverBrokerPollIntervalMs ` (default: `1000`) The interval to check job health status *Environment: `$PROVER_BROKER_POLL_INTERVAL_MS`* * `--proverBroker.proverBrokerJobMaxRetries ` (default: `3`) If starting a prover broker locally, the max number of retries per proving job *Environment: `$PROVER_BROKER_JOB_MAX_RETRIES`* * `--proverBroker.proverBrokerBatchSize ` (default: `100`) The prover broker writes jobs to disk in batches *Environment: `$PROVER_BROKER_BATCH_SIZE`* * `--proverBroker.proverBrokerBatchIntervalMs ` (default: `50`) How often to flush batches to disk *Environment: `$PROVER_BROKER_BATCH_INTERVAL_MS`* * `--proverBroker.proverBrokerMaxEpochsToKeepResultsFor ` (default: `1`) The maximum number of epochs to keep results for *Environment: `$PROVER_BROKER_MAX_EPOCHS_TO_KEEP_RESULTS_FOR`* * `--proverBroker.proverBrokerStoreMapSizeKb ` The size of the prover broker's database. Will override the dataStoreMapSizeKb if set. *Environment: `$PROVER_BROKER_STORE_MAP_SIZE_KB`* * `--proverBroker.proverBrokerDebugReplayEnabled ` Enable debug replay mode for replaying proving jobs from stored inputs *Environment: `$PROVER_BROKER_DEBUG_REPLAY_ENABLED`* **PROVER AGENT** * `--prover-agent` Starts Aztec Prover Agent with options * `--proverAgent.proverAgentCount ` (default: `1`) Whether this prover has a local prover agent *Environment: `$PROVER_AGENT_COUNT`* * `--proverAgent.proverAgentPollIntervalMs ` (default: `1000`) The interval agents poll for jobs at *Environment: `$PROVER_AGENT_POLL_INTERVAL_MS`* * `--proverAgent.proverAgentProofTypes ` The types of proofs the prover agent can generate *Environment: `$PROVER_AGENT_PROOF_TYPES`* * `--proverAgent.proverBrokerUrl ` The URL where this agent takes jobs from *Environment: `$PROVER_BROKER_HOST`* * `--proverAgent.realProofs ` (default: `true`) Whether to construct real proofs *Environment: `$PROVER_REAL_PROOFS`* * `--proverAgent.proverTestDelayType ` (default: `fixed`) The type of artificial delay to introduce *Environment: `$PROVER_TEST_DELAY_TYPE`* * `--proverAgent.proverTestDelayMs ` Artificial delay to introduce to all operations to the test prover. *Environment: `$PROVER_TEST_DELAY_MS`* * `--proverAgent.proverTestDelayFactor ` (default: `1`) If using realistic delays, what percentage of realistic times to apply. *Environment: `$PROVER_TEST_DELAY_FACTOR`* * `--proverAgent.proverTestVerificationDelayMs ` (default: `10`) The delay (ms) to inject during fake proof verification *Environment: `$PROVER_TEST_VERIFICATION_DELAY_MS`* * `--proverAgent.cancelJobsOnStop ` Whether to abort pending proving jobs when the orchestrator is cancelled. When false (default), jobs remain in the broker queue and can be reused on restart/reorg. *Environment: `$PROVER_CANCEL_JOBS_ON_STOP`* * `--proverAgent.proofStore ` Optional proof input store for the prover *Environment: `$PROVER_PROOF_STORE`* * `--p2p-enabled [value]` Enable P2P subsystem *Environment: `$P2P_ENABLED`* * `--p2p.validateMaxTxsPerBlock ` Maximum transactions per block for validation. Overrides maxTxsPerBlock for gossip validation when set. *Environment: `$VALIDATOR_MAX_TX_PER_BLOCK`* * `--p2p.validateMaxTxsPerCheckpoint ` Maximum transactions per checkpoint for validation. Used as fallback for maxTxsPerBlock when that is not set. *Environment: `$VALIDATOR_MAX_TX_PER_CHECKPOINT`* * `--p2p.p2pDiscoveryDisabled ` A flag dictating whether the P2P discovery system should be disabled. *Environment: `$P2P_DISCOVERY_DISABLED`* * `--p2p.blockCheckIntervalMS ` (default: `100`) The frequency in which to check for new L2 blocks. *Environment: `$P2P_BLOCK_CHECK_INTERVAL_MS`* * `--p2p.debugDisableColocationPenalty ` DEBUG: Disable colocation penalty - NEVER set to true in production *Environment: `$DEBUG_P2P_DISABLE_COLOCATION_PENALTY`* * `--p2p.peerCheckIntervalMS ` (default: `30000`) The frequency in which to check for new peers. *Environment: `$P2P_PEER_CHECK_INTERVAL_MS`* * `--p2p.peerFailedBanTimeMs ` (default: `300000`) How long to ban a peer after it fails maximum dial attempts. *Environment: `$P2P_PEER_FAILED_BAN_TIME_MS`* * `--p2p.l2QueueSize ` (default: `1000`) Size of queue of L2 blocks to store. *Environment: `$P2P_L2_QUEUE_SIZE`* * `--p2p.listenAddress ` (default: `0.0.0.0`) The listen address. ipv4 address. *Environment: `$P2P_LISTEN_ADDR`* * `--p2p.p2pPort ` (default: `40400`) The port for the P2P service. Defaults to 40400 *Environment: `$P2P_PORT`* * `--p2p.p2pBroadcastPort ` The port to broadcast the P2P service on (included in the node's ENR). Defaults to P2P\_PORT. *Environment: `$P2P_BROADCAST_PORT`* * `--p2p.p2pIp ` The IP address for the P2P service. ipv4 address. *Environment: `$P2P_IP`* * `--p2p.peerIdPrivateKey ` An optional peer id private key. If blank, will generate a random key. *Environment: `$PEER_ID_PRIVATE_KEY`* * `--p2p.peerIdPrivateKeyPath ` An optional path to store generated peer id private keys. If blank, will default to storing any generated keys in the root of the data directory. *Environment: `$PEER_ID_PRIVATE_KEY_PATH`* * `--p2p.bootstrapNodes ` A list of bootstrap peer ENRs to connect to. Separated by commas. *Environment: `$BOOTSTRAP_NODES`* * `--p2p.bootstrapNodeEnrVersionCheck ` Whether to check the version of the bootstrap node ENR. *Environment: `$P2P_BOOTSTRAP_NODE_ENR_VERSION_CHECK`* * `--p2p.bootstrapNodesAsFullPeers ` Whether to consider our configured bootnodes as full peers *Environment: `$P2P_BOOTSTRAP_NODES_AS_FULL_PEERS`* * `--p2p.maxPeerCount ` (default: `100`) The maximum number of peers to connect to. *Environment: `$P2P_MAX_PEERS`* * `--p2p.queryForIp ` If announceUdpAddress or announceTcpAddress are not provided, query for the IP address of the machine. Default is false. *Environment: `$P2P_QUERY_FOR_IP`* * `--p2p.publicIpServices ` (default: `https://api.ipify.org/,https://checkip.amazonaws.com/,https://ifconfig.me/ip,https://icanhazip.com/`) Comma-separated HTTPS URLs that return plain-text public IPv4. Used when P2P\_QUERY\_FOR\_IP is true and P2P\_IP is unset. Tried in order until one succeeds. *Environment: `$P2P_PUBLIC_IP_SERVICES`* * `--p2p.gossipsubInterval ` (default: `700`) The interval of the gossipsub heartbeat to perform maintenance tasks. *Environment: `$P2P_GOSSIPSUB_INTERVAL_MS`* * `--p2p.gossipsubD ` (default: `8`) The D parameter for the gossipsub protocol. *Environment: `$P2P_GOSSIPSUB_D`* * `--p2p.gossipsubDlo ` (default: `4`) The Dlo parameter for the gossipsub protocol. *Environment: `$P2P_GOSSIPSUB_DLO`* * `--p2p.gossipsubDhi ` (default: `12`) The Dhi parameter for the gossipsub protocol. *Environment: `$P2P_GOSSIPSUB_DHI`* * `--p2p.gossipsubDLazy ` (default: `8`) The Dlazy parameter for the gossipsub protocol. *Environment: `$P2P_GOSSIPSUB_DLAZY`* * `--p2p.gossipsubFloodPublish ` Whether to flood publish messages. - For testing purposes only *Environment: `$P2P_GOSSIPSUB_FLOOD_PUBLISH`* * `--p2p.gossipsubMcacheLength ` (default: `12`) The number of gossipsub interval message cache windows to keep. *Environment: `$P2P_GOSSIPSUB_MCACHE_LENGTH`* * `--p2p.gossipsubMcacheGossip ` (default: `3`) How many message cache windows to include when gossiping with other peers. *Environment: `$P2P_GOSSIPSUB_MCACHE_GOSSIP`* * `--p2p.gossipsubSeenTTL ` (default: `1200000`) How long to keep message IDs in the seen cache. *Environment: `$P2P_GOSSIPSUB_SEEN_TTL`* * `--p2p.maxGossipClockDisparityMs ` (default: `500`) Maximum clock-disparity tolerance (ms) applied to both ends of proposal/attestation gossip receive windows. *Environment: `$P2P_MAX_GOSSIP_CLOCK_DISPARITY_MS`* * `--p2p.gossipsubTxTopicWeight ` (default: `1`) The weight of the tx topic for the gossipsub protocol. *Environment: `$P2P_GOSSIPSUB_TX_TOPIC_WEIGHT`* * `--p2p.gossipsubTxInvalidMessageDeliveriesWeight ` (default: `-20`) The weight of the tx invalid message deliveries for the gossipsub protocol. *Environment: `$P2P_GOSSIPSUB_TX_INVALID_MESSAGE_DELIVERIES_WEIGHT`* * `--p2p.gossipsubTxInvalidMessageDeliveriesDecay ` (default: `0.5`) Determines how quickly the penalty for invalid message deliveries decays over time. Between 0 and 1. *Environment: `$P2P_GOSSIPSUB_TX_INVALID_MESSAGE_DELIVERIES_DECAY`* * `--p2p.peerPenaltyValues ` (default: `2,10,50`) The values for the peer scoring system. Passed as a comma separated list of values in order: low, mid, high tolerance errors. *Environment: `$P2P_PEER_PENALTY_VALUES`* * `--p2p.peerBanDurationSeconds ` (default: `86400`) How long (in seconds) a peer is banned for once its score drops below the ban threshold. *Environment: `$P2P_PEER_BAN_DURATION_SECONDS`* * `--p2p.doubleSpendSeverePeerPenaltyWindow ` (default: `30`) The "age" (in L2 blocks) of a tx after which we heavily penalize a peer for sending it. *Environment: `$P2P_DOUBLE_SPEND_SEVERE_PEER_PENALTY_WINDOW`* * `--p2p.blockRequestBatchSize ` (default: `20`) The number of blocks to fetch in a single batch. *Environment: `$P2P_BLOCK_REQUEST_BATCH_SIZE`* * `--p2p.archivedTxLimit ` The number of transactions that will be archived. If the limit is set to 0 then archiving will be disabled. *Environment: `$P2P_ARCHIVED_TX_LIMIT`* * `--p2p.trustedPeers ` A list of trusted peer ENRs that will always be persisted. Separated by commas. *Environment: `$P2P_TRUSTED_PEERS`* * `--p2p.privatePeers ` A list of private peer ENRs that will always be persisted and not be used for discovery. Separated by commas. *Environment: `$P2P_PRIVATE_PEERS`* * `--p2p.preferredPeers ` A list of preferred peer ENRs that will always be persisted and not be used for discovery. Separated by commas. *Environment: `$P2P_PREFERRED_PEERS`* * `--p2p.p2pStoreMapSizeKb ` The maximum possible size of the P2P DB in KB. Overwrites the general dataStoreMapSizeKb. *Environment: `$P2P_STORE_MAP_SIZE_KB`* * `--p2p.txPublicSetupAllowListExtend ` Additional entries to extend the default setup allow list. Format: I:address :flags ,C:classId :flags . Flags: os (onlySelf), rn (rejectNullMsgSender), cl=N (calldataLength), joined with +. *Environment: `$TX_PUBLIC_SETUP_ALLOWLIST`* * `--p2p.maxPendingTxCount ` (default: `1000`) The maximum number of pending txs before evicting lower priority txs. *Environment: `$P2P_MAX_PENDING_TX_COUNT`* * `--p2p.seenMessageCacheSize ` (default: `100000`) The number of messages to keep in the seen message cache *Environment: `$P2P_SEEN_MSG_CACHE_SIZE`* * `--p2p.txValidationCacheSize ` (default: `5000`) Maximum number of items to keep in the tx validation LRU cache. *Environment: `$P2P_TX_VALIDATION_CACHE_SIZE`* * `--p2p.p2pDisableStatusHandshake ` True to disable the status handshake on peer connected. *Environment: `$P2P_DISABLE_STATUS_HANDSHAKE`* * `--p2p.p2pAllowOnlyValidators ` True to only permit validators to connect. *Environment: `$P2P_ALLOW_ONLY_VALIDATORS`* * `--p2p.p2pMaxFailedAuthAttemptsAllowed ` (default: `3`) Number of auth attempts to allow before peer is banned. Number is inclusive *Environment: `$P2P_MAX_AUTH_FAILED_ATTEMPTS_ALLOWED`* * `--p2p.dropTransactionsProbability ` The probability that a transaction is discarded (0 - 1). - For testing purposes only *Environment: `$P2P_DROP_TX_CHANCE`* * `--p2p.disableTransactions ` Whether transactions are disabled for this node. This means transactions will be rejected at the RPC and P2P layers. *Environment: `$TRANSACTIONS_DISABLED`* * `--p2p.txPoolDeleteTxsAfterReorg ` Whether to delete transactions from the pool after a reorg instead of moving them back to pending. *Environment: `$P2P_TX_POOL_DELETE_TXS_AFTER_REORG`* * `--p2p.debugP2PInstrumentMessages ` Alters the format of p2p messages to include things like broadcast timestamp FOR TESTING ONLY *Environment: `$DEBUG_P2P_INSTRUMENT_MESSAGES`* * `--p2p.broadcastEquivocatedProposals ` Broadcast block proposals even when a conflicting proposal for the same slot already exists in the pool (for testing purposes only). * `--p2p.skipIncomingProposals ` Drop incoming block and checkpoint proposals at the libp2p dispatch layer (for testing only) * `--p2p.skipProposalSlotValidation ` Accept proposal gossip regardless of slot timing (for testing only) * `--p2p.skipCheckpointProposalValidation ` Skip checkpoint proposal validation and always attest, broadcasting the attestation before processing the embedded last bloc ### aztec test[​](#aztec-test "Direct link to aztec test") WARNING: Found tests in contract crate(s): packing\_example::types::test::test\_card\_pack\_unpack packing\_example::types::test::test\_card\_pack\_unpack\_max packing\_example::types::test::test\_config\_pack\_unpack packing\_example::types::test::test\_config\_pack\_unpack\_max packing\_example::types::test::test\_game\_state\_pack\_unpack packing\_example::types::test::test\_game\_state\_pack\_unpack\_max Tests should be in a dedicated test crate, not in the contract crate. Learn more: Postprocessing contracts... Compilation complete! Run the tests for this program **Usage:** ``` aztec test [OPTIONS] [TEST_NAMES]... ``` **Options:** * `--show-output` - Display output of `println` statements * `--exact` - Only run tests that match exactly * `--list-tests` - Print all matching test names, without running them * `--no-run` - Only compile the tests, without running them * `--package ` - The name of the package to run the command on. By default run on the first one found moving up along the ancestors of the current directory * `--workspace` - Run on all packages in the workspace * `--force` - Force a full recompilation * `--print-acir` - Display the ACIR for compiled circuit, including the Brillig bytecode * `--deny-warnings` - Treat all warnings as errors * `--silence-warnings` - Suppress warnings * `--debug-comptime-in-file ` - Enable printing results of comptime evaluation: provide a path suffix for the module to debug, e.g. "package\_name/src/main.nr" * `--skip-underconstrained-check` - Flag to turn off the compiler check for under constrained values. Warning: This can improve compilation speed but can also lead to correctness errors. This check should always be run on production code * `--skip-brillig-constraints-check` - Flag to turn off the compiler check for missing Brillig call constraints. Warning: This can improve compilation speed but can also lead to correctness errors. This check should always be run on production code * `--count-array-copies` - Count the number of arrays that are copied in an unconstrained context for performance debugging * `--inliner-aggressiveness ` - Setting to decide on an inlining strategy for Brillig functions. A more aggressive inliner should generate larger programs but more optimized A less aggressive inliner should generate smaller programs \[default: 9223372036854775807] * `-Z, --unstable-features ` - Unstable features to enable for this current build. If non-empty, it disables unstable features required in crate manifests. * `--no-unstable-features` - Disable any unstable features required in crate manifests * `--oracle-resolver ` - JSON RPC url to solve oracle calls * `--test-threads ` - Number of threads used for running tests in parallel \[default: 28] * `--format ` - Configure formatting of output Possible values: - pretty: Print verbose output - terse: Display one character per test - json: Output a JSON Lines document * `-q, --quiet` - Display one character per test instead of one line * `--no-fuzz` - Do not run fuzz tests (tests that have arguments) * `--only-fuzz` - Only run fuzz tests (tests that have arguments) * `--corpus-dir ` - If given, load/store fuzzer corpus from this folder * `--minimized-corpus-dir ` - If given, perform corpus minimization instead of fuzzing and store results in the given folder * `--fuzzing-failure-dir ` - If given, store the failing input in the given folder * `--fuzz-timeout ` - Maximum time in seconds to spend fuzzing (default: 1 seconds) \[default: 1] * `--fuzz-max-executions ` - Maximum number of executions to run for each fuzz test (default: 100000) \[default: 100000] * `--fuzz-show-progress` - Show progress of fuzzing (default: false) * `--coverage` - Produce a coverage report. Writes coverage data to the workspace target directory into `target/coverage/<package-name>/lcov.info` or `target/coverage/lcov.info` files, depending on whether we are dealing with a workspace. * `--coverage-dir ` - Override the directory where coverage files are written. If not set, defaults to the workspace target directory. * `-h, --help` - Print help (see a summary with '-h') ### aztec trigger-seed-snapshot[​](#aztec-trigger-seed-snapshot "Direct link to aztec trigger-seed-snapshot") Triggers a seed snapshot for the next epoch. **Usage:** ``` aztec trigger-seed-snapshot [options] ``` **Options:** * `-pk, --private-key ` - The private key to use for deployment * `-m, --mnemonic ` - The mnemonic to use in deployment (default: "test test test test test test test test test test test junk") * `--rollup
` - ethereum address of the rollup contract * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \[""], env: ETHEREUM\_HOSTS) * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `-h, --help` - display help for command ### aztec update[​](#aztec-update "Direct link to aztec update") Updates Nodejs and Noir dependencies **Usage:** ``` aztec update [options] [projectPath] ``` **Options:** * `--contract [paths...]` - Paths to contracts to update dependencies (default: \[]) * `--aztec-version ` - The version to update Aztec packages to. Defaults to latest (default: "latest") * `-h, --help` - display help for command ### aztec validator-keys|valKeys[​](#aztec-validator-keysvalkeys "Direct link to aztec validator-keys|valKeys") *This subcommand does not provide its own help information.* ### aztec vote-on-governance-proposal[​](#aztec-vote-on-governance-proposal "Direct link to aztec vote-on-governance-proposal") Votes on a governance proposal. **Usage:** ``` aztec vote-on-governance-proposal [options] ``` **Options:** * `-p, --proposal-id ` - The ID of the proposal * `-a, --vote-amount ` - The amount of tokens to vote * `--in-favor ` - Whether to vote in favor of the proposal. Use "yea" for true, any other value for false. * `--wait ` - Whether to wait until the proposal is active * `-r, --registry-address ` - The address of the registry contract * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \[""], env: ETHEREUM\_HOSTS) * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `-pk, --private-key ` - The private key to use to vote * `-m, --mnemonic ` - The mnemonic to use to vote (default: "test test test test test test test test test test test junk") * `-i, --mnemonic-index ` - The index of the mnemonic to use to vote (default: 0) * `-h, --help` - display help for command --- # Aztec Up CLI Reference *This documentation is auto-generated from the `aztec-up` CLI help output.* *Generated: Tue 30 Jun 2026 17:56:51 UTC* *Command: `aztec-up`* ## Table of Contents[​](#table-of-contents "Direct link to Table of Contents") * [aztec-up](#aztec-up) * [aztec-up env](#aztec-up-env) * [aztec-up install](#aztec-up-install) * [aztec-up list](#aztec-up-list) * [aztec-up prune](#aztec-up-prune) * [aztec-up self-update](#aztec-up-self-update) * [aztec-up uninstall](#aztec-up-uninstall) * [aztec-up use](#aztec-up-use) ## aztec-up[​](#aztec-up "Direct link to aztec-up") aztec-up - Aztec version manager **Usage:** ``` aztec-up [command] [options] ``` **Available Commands:** * `env` - Output PATH for .aztecrc version (for eval) * `install ` - Install a version and switch to it * `list` - List installed versions * `prune` - Remove all versions except the current one * `self-update` - Update aztec-up itself to the latest version * `uninstall ` - Remove an installed version * `use []` - Switch to an installed version (or read from .aztecrc) **Options:** * `-h --help` - Show this help message **Examples:** ``` aztec-up install 0.85.0 Install a specific version aztec-up install nightly Install the nightly version aztec-up use 0.85.0 Switch to version 0.85.0 aztec-up use Read version from .aztecrc and switch to it aztec-up list Show all installed versions aztec-up self-update Update aztec-up to latest ``` ### Subcommands[​](#subcommands "Direct link to Subcommands") ### aztec-up env[​](#aztec-up-env "Direct link to aztec-up env") Output PATH export for the version specified in .aztecrc **Usage:** ``` aztec-up env ``` **Options:** * `-h, --help` - Print help ### aztec-up install[​](#aztec-up-install "Direct link to aztec-up install") Install a version of Aztec and switch to it **Usage:** ``` aztec-up install ``` **Options:** * `-h, --help` - Print help ### aztec-up list[​](#aztec-up-list "Direct link to aztec-up list") List installed Aztec versions and available aliases **Usage:** ``` aztec-up list ``` **Options:** * `-h, --help` - Print help ### aztec-up prune[​](#aztec-up-prune "Direct link to aztec-up prune") Remove all installed versions except the currently active one **Usage:** ``` aztec-up prune ``` **Options:** * `-h, --help` - Print help ### aztec-up self-update[​](#aztec-up-self-update "Direct link to aztec-up self-update") Update aztec-up itself to the latest version **Usage:** ``` aztec-up self-update ``` **Options:** * `-h, --help` - Print help ### aztec-up uninstall[​](#aztec-up-uninstall "Direct link to aztec-up uninstall") Remove an installed version of Aztec **Usage:** ``` aztec-up uninstall ``` **Options:** * `-h, --help` - Print help ### aztec-up use[​](#aztec-up-use "Direct link to aztec-up use") Switch to an installed version of Aztec **Usage:** ``` aztec-up use [VERSION] ``` **Options:** * `-h, --help` - Print help --- # Aztec Wallet CLI Reference *This documentation is auto-generated from the `aztec-wallet` CLI help output.* *Generated: Tue 30 Jun 2026 17:56:52 UTC* *Command: `aztec-wallet`* ## Table of Contents[​](#table-of-contents "Direct link to Table of Contents") * [aztec-wallet](#aztec-wallet) * [aztec-wallet alias](#aztec-wallet-alias) * [aztec-wallet authorize-action](#aztec-wallet-authorize-action) * [aztec-wallet bridge-fee-juice](#aztec-wallet-bridge-fee-juice) * [aztec-wallet create-account](#aztec-wallet-create-account) * [aztec-wallet create-authwit](#aztec-wallet-create-authwit) * [aztec-wallet create-secret](#aztec-wallet-create-secret) * [aztec-wallet deploy](#aztec-wallet-deploy) * [aztec-wallet deploy-account](#aztec-wallet-deploy-account) * [aztec-wallet get-alias](#aztec-wallet-get-alias) * [aztec-wallet get-fee-juice-balance](#aztec-wallet-get-fee-juice-balance) * [aztec-wallet get-tx](#aztec-wallet-get-tx) * [aztec-wallet import-test-accounts](#aztec-wallet-import-test-accounts) * [aztec-wallet profile](#aztec-wallet-profile) * [aztec-wallet register-contract](#aztec-wallet-register-contract) * [aztec-wallet register-sender](#aztec-wallet-register-sender) * [aztec-wallet send](#aztec-wallet-send) * [aztec-wallet simulate](#aztec-wallet-simulate) ## aztec-wallet[​](#aztec-wallet "Direct link to aztec-wallet") Aztec wallet **Usage:** ``` aztec-wallet [options] [command] ``` **Available Commands:** * `alias ` - Aliases information for easy reference. * `authorize-action [options] ` - Authorizes a public call on the caller, so they can perform an action on behalf of the provided account * `bridge-fee-juice [options] ` - Mints L1 Fee Juice and pushes them to L2. * `create-account [options]` - Creates an aztec account that can be used for sending transactions. * `create-authwit [options] ` - Creates an authorization witness that can be privately sent to a caller so they can perform an action on behalf of the provided account * `create-secret [options]` - Creates an aliased secret to use in other commands * `deploy [options] [artifact]` - Deploys a compiled Aztec.nr contract to Aztec. * `deploy-account [options]
` - Deploys an already registered aztec account that can be used for sending transactions. * `get-alias [alias]` - Shows stored aliases * `get-fee-juice-balance [options]
` - Checks the Fee Juice balance for a given address. * `get-tx [options] [txHash]` - Gets the status of the recent txs, or a detailed view if a specific transaction hash is provided * `help [command]` - display help for command * `import-test-accounts [options]` - Import test accounts from pxe. * `profile [options] ` - Profiles a private function by counting the unconditional operations in its execution steps * `register-contract [options] [address] [artifact]` - Registers a contract in this wallet's PXE * `register-sender [options] [address]` - Registers a sender's address in the wallet, so the note synching process will look for notes sent by them * `send [options] ` - Calls a function on an Aztec contract. * `simulate [options] ` - Simulates the execution of a function on an Aztec contract. **Options:** * `-V --version` - output the version number * `-d --data-dir ` - Storage directory for wallet data (default: "/home/josh/.aztec/wallet") * `-p --prover ` - The type of prover the wallet uses (choices: "wasm", "native", "none", default: "native", env: PXE\_PROVER) * `-n --node-url ` - URL of the Aztec node to connect to (default: "", env: AZTEC\_NODE\_URL) * `-h --help` - display help for command ### Subcommands[​](#subcommands "Direct link to Subcommands") ### aztec-wallet alias[​](#aztec-wallet-alias "Direct link to aztec-wallet alias") Aliases information for easy reference. **Usage:** ``` aztec-wallet alias [options] ``` **Options:** * `-h, --help` - display help for command ### aztec-wallet authorize-action[​](#aztec-wallet-authorize-action "Direct link to aztec-wallet authorize-action") Authorizes a public call on the caller, so they can perform an action on behalf of the provided account **Usage:** ``` aztec-wallet authorize-action [options] ``` **Options:** * `--args [args...]` - Function arguments (default: \[]) * `-ca, --contract-address
` - Aztec address of the contract. * `-c, --contract-artifact ` - Path to a compiled Aztec contract's artifact in JSON format. If executed inside a nargo workspace, a package and contract name can be specified as package\@contract * `-f, --from ` - Alias or address of the account to simulate from * `-h, --help` - display help for command ### aztec-wallet bridge-fee-juice[​](#aztec-wallet-bridge-fee-juice "Direct link to aztec-wallet bridge-fee-juice") Mints L1 Fee Juice and pushes them to L2. **Usage:** ``` aztec-wallet bridge-fee-juice [options] ``` **Options:** * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \[""]) * `-m, --mnemonic ` - The mnemonic to use for deriving the Ethereum address that will mint and bridge (default: "test test test test test test test test test test test junk") * `--mint` - Mint the tokens on L1 (default: false) * `--l1-private-key ` - The private key to the eth account bridging * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `--json` - Output the claim in JSON format * `--no-wait` - Wait for the bridged funds to be available in L2, polling every 60 seconds * `--interval ` - The polling interval in seconds for the bridged funds (default: "60") * `-h, --help` - display help for command ### aztec-wallet create-account[​](#aztec-wallet-create-account "Direct link to aztec-wallet create-account") Creates an aztec account that can be used for sending transactions. Registers the account on the PXE and deploys an account contract. Uses a Schnorr account which uses an immutable key for authentication. **Usage:** ``` aztec-wallet create-account [options] ``` **Options:** * `-f, --from ` - Alias or address of the account performing the deployment * `--skip-initialization` - Skip initializing the account contract. Useful for publicly deploying an existing account. * `--public-deploy` - Publishes the account contract instance (and the class, if needed). Needed if the contract contains public functions. * `--register-class` - Register the contract class (useful for when the contract class has not been deployed yet). * `-p, --public-key ` - Public key that identifies a private signing key stored outside of the wallet. Used for ECDSA SSH accounts over the secp256r1 curve. * `-sk, --secret-key ` - Secret key for account. Uses random by default. (env: SECRET\_KEY) * `-a, --alias ` - Alias for the account. Used for easy reference in subsequent commands. * `-t, --type ` - Type of account to create (choices: "schnorr", "schnorr\_initializerless", "ecdsasecp256r1", "ecdsasecp256r1ssh", "ecdsasecp256k1", default: "schnorr") * `-s, --salt ` - Optional deployment salt as a hex string for generating the deployment address. Defaults to 0. * `--register-only` - Just register the account on the Wallet. Do not deploy or initialize the account contract. * `--json` - Emit output as json * `--no-wait` - Skip waiting for the contract to be deployed. Print the hash of deployment transaction * `--wait-for-status ` - Tx status to wait for: 'proposed' or 'checkpointed' (default: "proposed") * `-v, --verbose` - Provide timings on all executed operations (synching, simulating, proving) (default: false) * `--payment ` - Fee payment method and arguments. Parameters: method Valid values: "fee\_juice", "fpc-public", "fpc-private", "fpc-sponsored" Default: fee\_juice asset The asset used for fee payment. Required for "fpc-public" and "fpc-private". fpc The FPC contract that pays in fee juice. Not required for the "fee\_juice" method. claim Whether to use a previously stored claim to bridge fee juice. claimSecret The secret to claim fee juice on L1. claimAmount The amount of fee juice to be claimed. messageLeafIndex The index of the claim in the l1toL2Message tree. * `--gas-limits ` - Gas limits for the tx. * `--max-fees-per-gas ` - Maximum fees per gas unit for DA and L2 computation. * `--max-priority-fees-per-gas ` - Maximum priority fees per gas unit for DA and L2 computation. * `--estimate-gas-only` - Only report gas estimation for the tx, do not send it. * `-h, --help` - display help for command ### aztec-wallet create-authwit[​](#aztec-wallet-create-authwit "Direct link to aztec-wallet create-authwit") Creates an authorization witness that can be privately sent to a caller so they can perform an action on behalf of the provided account **Usage:** ``` aztec-wallet create-authwit [options] ``` **Options:** * `--args [args...]` - Function arguments (default: \[]) * `-ca, --contract-address
` - Aztec address of the contract. * `-c, --contract-artifact ` - Path to a compiled Aztec contract's artifact in JSON format. If executed inside a nargo workspace, a package and contract name can be specified as package\@contract * `-f, --from ` - Alias or address of the account to simulate from * `-a, --alias ` - Alias for the authorization witness. Used for easy reference in subsequent commands. * `-h, --help` - display help for command ### aztec-wallet create-secret[​](#aztec-wallet-create-secret "Direct link to aztec-wallet create-secret") Creates an aliased secret to use in other commands **Usage:** ``` aztec-wallet create-secret [options] ``` **Options:** * `-a, --alias ` - Key to alias the secret with * `-h, --help` - display help for command ### aztec-wallet deploy[​](#aztec-wallet-deploy "Direct link to aztec-wallet deploy") Deploys a compiled Aztec.nr contract to Aztec. **Usage:** ``` aztec-wallet deploy [options] [artifact] ``` **Options:** * `--init ` - The contract initializer function to call (default: "constructor") * `--no-init` - Leave the contract uninitialized * `-k, --public-key ` - Optional encryption public key for this address. Set this value only if this contract is expected to receive private notes, which will be encrypted using this public key. * `-s, --salt ` - Optional deployment salt as a hex string for generating the deployment address. Defaults to random. * `--universal` - Do not mix the sender address into the deployment. * `--args [args...]` - Constructor arguments (default: \[]) * `-f, --from ` - Alias or address of the account to deploy from * `-a, --alias ` - Alias for the contract. Used for easy reference subsequent commands. * `--json` - Emit output as json * `--no-wait` - Skip waiting for the contract to be deployed. Print the hash of deployment transaction * `--no-class-registration` - Don't register this contract class * `--no-public-deployment` - Don't emit this contract's public bytecode * `--timeout ` - The amount of time in seconds to wait for the deployment to post to L2 * `--wait-for-status ` - Tx status to wait for: 'proposed' or 'checkpointed' (default: "proposed") * `-v, --verbose` - Provide timings on all executed operations (synching, simulating, proving) (default: false) * `--payment ` - Fee payment method and arguments. Parameters: method Valid values: "fee\_juice", "fpc-public", "fpc-private", "fpc-sponsored" Default: fee\_juice asset The asset used for fee payment. Required for "fpc-public" and "fpc-private". fpc The FPC contract that pays in fee juice. Not required for the "fee\_juice" method. claim Whether to use a previously stored claim to bridge fee juice. claimSecret The secret to claim fee juice on L1. claimAmount The amount of fee juice to be claimed. messageLeafIndex The index of the claim in the l1toL2Message tree. * `--gas-limits ` - Gas limits for the tx. * `--max-fees-per-gas ` - Maximum fees per gas unit for DA and L2 computation. * `--max-priority-fees-per-gas ` - Maximum priority fees per gas unit for DA and L2 computation. * `--estimate-gas-only` - Only report gas estimation for the tx, do not send it. * `-h, --help` - display help for command ### aztec-wallet deploy-account[​](#aztec-wallet-deploy-account "Direct link to aztec-wallet deploy-account") Deploys an already registered aztec account that can be used for sending transactions. **Usage:** ``` aztec-wallet deploy-account [options]
``` **Options:** * `-f, --from ` - Alias or address of the account performing the deployment * `--json` - Emit output as json * `--no-wait` - Skip waiting for the contract to be deployed. Print the hash of deployment transaction * `--register-class` - Register the contract class (useful for when the contract class has not been deployed yet). * `--public-deploy` - Publishes the account contract instance (and the class, if needed). Needed if the contract contains public functions. * `--skip-initialization` - Skip initializing the account contract. Useful for publicly deploying an existing account. * `--wait-for-status ` - Tx status to wait for: 'proposed' or 'checkpointed' (default: "proposed") * `-v, --verbose` - Provide timings on all executed operations (synching, simulating, proving) (default: false) * `--payment ` - Fee payment method and arguments. Parameters: method Valid values: "fee\_juice", "fpc-public", "fpc-private", "fpc-sponsored" Default: fee\_juice asset The asset used for fee payment. Required for "fpc-public" and "fpc-private". fpc The FPC contract that pays in fee juice. Not required for the "fee\_juice" method. claim Whether to use a previously stored claim to bridge fee juice. claimSecret The secret to claim fee juice on L1. claimAmount The amount of fee juice to be claimed. messageLeafIndex The index of the claim in the l1toL2Message tree. * `--gas-limits ` - Gas limits for the tx. * `--max-fees-per-gas ` - Maximum fees per gas unit for DA and L2 computation. * `--max-priority-fees-per-gas ` - Maximum priority fees per gas unit for DA and L2 computation. * `--estimate-gas-only` - Only report gas estimation for the tx, do not send it. * `-h, --help` - display help for command ### aztec-wallet get-alias[​](#aztec-wallet-get-alias "Direct link to aztec-wallet get-alias") Shows stored aliases **Usage:** ``` aztec-wallet get-alias [options] [alias] ``` **Options:** * `-h, --help` - display help for command ### aztec-wallet get-fee-juice-balance[​](#aztec-wallet-get-fee-juice-balance "Direct link to aztec-wallet get-fee-juice-balance") Checks the Fee Juice balance for a given address. **Usage:** ``` aztec-wallet get-fee-juice-balance [options]
``` **Options:** * `--json` - Emit output as json * `--exact` - Show exact balance with all 18 decimal places * `-h, --help` - display help for command ### aztec-wallet get-tx[​](#aztec-wallet-get-tx "Direct link to aztec-wallet get-tx") Gets the status of the recent txs, or a detailed view if a specific transaction hash is provided **Usage:** ``` aztec-wallet get-tx [options] [txHash] ``` **Options:** * `-p, --page ` - The page number to display (default: 1) * `-s, --page-size ` - The number of transactions to display per page (default: 10) * `-h, --help` - display help for command ### aztec-wallet import-test-accounts[​](#aztec-wallet-import-test-accounts "Direct link to aztec-wallet import-test-accounts") Import test accounts from pxe. **Usage:** ``` aztec-wallet import-test-accounts [options] ``` **Options:** * `--json` - Emit output as json * `-h, --help` - display help for command ### aztec-wallet profile[​](#aztec-wallet-profile "Direct link to aztec-wallet profile") Profiles a private function by counting the unconditional operations in its execution steps **Usage:** ``` aztec-wallet profile [options] ``` **Options:** * `--args [args...]` - Function arguments (default: \[]) * `-ca, --contract-address
` - Aztec address of the contract. * `-c, --contract-artifact ` - Path to a compiled Aztec contract's artifact in JSON format. If executed inside a nargo workspace, a package and contract name can be specified as package\@contract * `--debug-execution-steps-dir
` - Directory to write execution step artifacts for bb profiling/debugging. * `-aw, --auth-witness ` - Authorization witness to use for the simulation * `-f, --from ` - Alias or address of the account to simulate from * `--payment ` - Fee payment method and arguments. Parameters: method Valid values: "fee\_juice", "fpc-public", "fpc-private", "fpc-sponsored" Default: fee\_juice asset The asset used for fee payment. Required for "fpc-public" and "fpc-private". fpc The FPC contract that pays in fee juice. Not required for the "fee\_juice" method. claim Whether to use a previously stored claim to bridge fee juice. claimSecret The secret to claim fee juice on L1. claimAmount The amount of fee juice to be claimed. messageLeafIndex The index of the claim in the l1toL2Message tree. * `--gas-limits ` - Gas limits for the tx. * `--max-fees-per-gas ` - Maximum fees per gas unit for DA and L2 computation. * `--max-priority-fees-per-gas ` - Maximum priority fees per gas unit for DA and L2 computation. * `--estimate-gas-only` - Only report gas estimation for the tx, do not send it. * `-h, --help` - display help for command ### aztec-wallet register-contract[​](#aztec-wallet-register-contract "Direct link to aztec-wallet register-contract") Registers a contract in this wallet's PXE **Usage:** ``` aztec-wallet register-contract [options] [address] [artifact] ``` **Options:** * `--init ` - The contract initializer function to call (default: "constructor") * `-k, --public-key ` - Optional encryption public key for this address. Set this value only if this contract is expected to receive private notes, which will be encrypted using this public key. * `-s, --salt ` - Optional deployment salt as a hex string for generating the deployment address. * `--deployer ` - The address of the account that deployed the contract * `--args [args...]` - Constructor arguments (default: \[]) * `-a, --alias ` - Alias for the contact. Used for easy reference in subsequent commands. * `-h, --help` - display help for command ### aztec-wallet register-sender[​](#aztec-wallet-register-sender "Direct link to aztec-wallet register-sender") Registers a sender's address in the wallet, so the note synching process will look for notes sent by them **Usage:** ``` aztec-wallet register-sender [options] [address] ``` **Options:** * `-a, --alias ` - Alias for the sender. Used for easy reference in subsequent commands. * `-h, --help` - display help for command ### aztec-wallet send[​](#aztec-wallet-send "Direct link to aztec-wallet send") Calls a function on an Aztec contract. **Usage:** ``` aztec-wallet send [options] ``` **Options:** * `--args [args...]` - Function arguments (default: \[]) * `-c, --contract-artifact ` - Path to a compiled Aztec contract's artifact in JSON format. If executed inside a nargo workspace, a package and contract name can be specified as package\@contract * `-ca, --contract-address
` - Aztec address of the contract. * `-a, --alias ` - Alias for the transaction hash. Used for easy reference in subsequent commands. * `-aw, --auth-witness ` - Authorization witness to use for the transaction. If using multiple, pass a comma separated string * `-f, --from ` - Alias or address of the account to send the transaction from * `--no-wait` - Print transaction hash without waiting for it to be mined * `--wait-for-status ` - Tx status to wait for: 'proposed' or 'checkpointed' (default: "proposed") * `-v, --verbose` - Provide timings on all executed operations (synching, simulating, proving) (default: false) * `--payment ` - Fee payment method and arguments. Parameters: method Valid values: "fee\_juice", "fpc-public", "fpc-private", "fpc-sponsored" Default: fee\_juice asset The asset used for fee payment. Required for "fpc-public" and "fpc-private". fpc The FPC contract that pays in fee juice. Not required for the "fee\_juice" method. claim Whether to use a previously stored claim to bridge fee juice. claimSecret The secret to claim fee juice on L1. claimAmount The amount of fee juice to be claimed. messageLeafIndex The index of the claim in the l1toL2Message tree. * `--gas-limits ` - Gas limits for the tx. * `--max-fees-per-gas ` - Maximum fees per gas unit for DA and L2 computation. * `--max-priority-fees-per-gas ` - Maximum priority fees per gas unit for DA and L2 computation. * `--estimate-gas-only` - Only report gas estimation for the tx, do not send it. * `-h, --help` - display help for command ### aztec-wallet simulate[​](#aztec-wallet-simulate "Direct link to aztec-wallet simulate") Simulates the execution of a function on an Aztec contract. **Usage:** ``` aztec-wallet simulate [options] ``` **Options:** * `--args [args...]` - Function arguments (default: \[]) * `-ca, --contract-address
` - Aztec address of the contract. * `-c, --contract-artifact ` - Path to a compiled Aztec contract's artifact in JSON format. If executed inside a nargo workspace, a package and contract name can be specified as package\@contract * `-sk, --secret-key ` - The sender's secret key (env: SECRET\_KEY) * `-aw, --auth-witness ` - Authorization witness to use for the simulation * `-f, --from ` - Alias or address of the account to simulate from * `-v, --verbose` - Provide timings on all executed operations (synching, simulating, proving) (default: false) * `--payment ` - Fee payment method and arguments. Parameters: method Valid values: "fee\_juice", "fpc-public", "fpc-private", "fpc-sponsored" Default: fee\_juice asset The asset used for fee payment. Required for "fpc-public" and "fpc-private". fpc The FPC contract that pays in fee juice. Not required for the "fee\_juice" method. claim Whether to use a previously stored claim to bridge fee juice. claimSecret The secret to claim fee juice on L1. claimAmount The amount of fee juice to be claimed. messageLeafIndex The index of the claim in the l1toL2Message tree. * `--gas-limits ` - Gas limits for the tx. * `--max-fees-per-gas ` - Maximum fees per gas unit for DA and L2 computation. * `--max-priority-fees-per-gas ` - Maximum priority fees per gas unit for DA and L2 computation. * `--estimate-gas-only` - Only report gas estimation for the tx, do not send it. * `-h, --help` - display help for command --- # Aztec Overview This page outlines Aztec's fundamental technical concepts. It is recommended to read this before diving into building on Aztec. ## What is Aztec?[​](#what-is-aztec "Direct link to What is Aztec?") Aztec is a privacy-first Layer 2 on Ethereum. It supports smart contracts with both private & public state and private & public execution. Prefer video? This explainer covers the core idea in under 90 seconds, and there are more [video lessons](/developers/testnet/docs/resources/video_lessons.md) available. [What is Aztec: Explained in Under 90 Seconds](https://www.youtube-nocookie.com/embed/urcBvo2QJp0) ![](/assets/ideal-img/Aztec_overview.4d3e9fb.640.png) ## High level view[​](#high-level-view "Direct link to High level view") ![](/assets/ideal-img/aztec-high-level.4ac0d53.640.png) 1. A user interacts with Aztec through Aztec.js (like web3js or ethersjs) 2. Private functions are executed in the PXE, which is client-side 3. Proofs and tree updates are sent to the Public VM (running on an Aztec node) 4. Public functions are executed in the Public VM 5. The Public VM rolls up the transactions that include private and public state updates into blocks 6. The block data and proof of a correct state transition are submitted to Ethereum for verification ## Private and public execution[​](#private-and-public-execution "Direct link to Private and public execution") Private functions are executed client side, on user devices to maintain maximum privacy. Public functions are executed by a remote network of nodes, similar to other blockchains. These distinct execution environments create a directional execution flow for a single transaction--a transaction begins in the private context on the user's device then moves to the public network. This means that private functions executed by a transaction can enqueue public functions to be executed later in the transaction life cycle, but public functions cannot call private functions. ### Private Execution Environment (PXE)[​](#private-execution-environment-pxe "Direct link to Private Execution Environment (PXE)") Private functions are executed on the user's device in the Private Execution Environment (PXE, pronounced 'pixie'), then it generates proofs for onchain verification. It is a client-side library for execution and proof-generation of private operations. It holds keys, notes, and generates proofs. It is included in aztec.js, a TypeScript library, and can be run within Node or the browser. Note: It is easy for private functions to be written in a detrimentally unoptimized way, because many intuitions of regular program execution do not apply to proving. For more about writing performant private functions in Noir, see [this page](https://noir-lang.org/docs/explainers/explainer-writing-noir) of the Noir documentation. ### Aztec Virtual Machine (AVM)[​](#aztec-virtual-machine-avm "Direct link to Aztec Virtual Machine (AVM)") Public functions are executed by the Aztec Virtual Machine (AVM), which is conceptually similar to the Ethereum Virtual Machine (EVM). As such, writing efficient public functions follow the same intuition as gas-efficient solidity contracts. The PXE is unaware of the Public VM. And the Public VM is unaware of the PXE. They are completely separate execution environments. This means: * The PXE and the Public VM cannot directly communicate with each other * Private transactions in the PXE are executed first, followed by public transactions ## Private and public state[​](#private-and-public-state "Direct link to Private and public state") Private state works with UTXOs, which are chunks of data that we call notes. To keep things private, notes are stored in an [append-only UTXO tree](/developers/testnet/docs/foundational-topics/advanced/storage/indexed_merkle_tree.md), and a nullifier is created when notes are invalidated (aka deleted). Nullifiers are stored in their own [nullifier tree](/developers/testnet/docs/foundational-topics/advanced/storage/indexed_merkle_tree.md). Public state works similarly to other chains like Ethereum, behaving like a public ledger. Public data is stored in a public data tree. ![Public vs private state](/assets/images/public-and-private-state-diagram-ff88262b40b259d4fe4c8b7d667924aa.png) Aztec [smart contract](/developers/testnet/docs/aztec-nr/framework-description/contract_structure.md) developers should keep in mind that different data types are used when manipulating private or public state. Working with private state is creating commitments and nullifiers to state, whereas working with public state is directly updating state. ## Accounts and keys[​](#accounts-and-keys "Direct link to Accounts and keys") ### Account abstraction[​](#account-abstraction "Direct link to Account abstraction") Every account in Aztec is a smart contract (account abstraction). This allows implementing different schemes for authorizing transactions, nonce management, and fee payments. Developers can write their own account contract to define the rules by which user transactions are authorized and paid for, as well as how user keys are managed. Learn more about account contracts [here](/developers/testnet/docs/foundational-topics/accounts.md). ### Key pairs[​](#key-pairs "Direct link to Key pairs") Each account in Aztec is backed by 3 key pairs: * A **nullifier key pair** used for note nullifier computation * A **incoming viewing key pair** used to encrypt a note for the recipient * A **outgoing viewing key pair** used to encrypt a note for the sender As Aztec has native account abstraction, accounts do not automatically have a signing key pair to authenticate transactions. This is up to the account contract developer to implement. ## Noir[​](#noir "Direct link to Noir") Noir is a zero-knowledge domain specific language used for writing smart contracts for the Aztec network. It is also possible to write circuits with Noir that can be verified on or offchain. For more in-depth docs into the features of Noir, go to the [Noir documentation](https://noir-lang.org/). --- # Understanding Accounts in Aztec This page provides a comprehensive understanding of how accounts work in Aztec. We'll explore the architecture, implementation details, and the powerful features enabled by Aztec's native account abstraction. ## What is Account Abstraction?[​](#what-is-account-abstraction "Direct link to What is Account Abstraction?") Account abstraction fundamentally changes how we think about blockchain accounts. Instead of accounts being simple key pairs (like in Bitcoin or traditional Ethereum EOAs), accounts become programmable smart contracts that can define their own rules for authentication, authorization, and transaction execution. ### Why Account Abstraction Matters[​](#why-account-abstraction-matters "Direct link to Why Account Abstraction Matters") Traditional blockchain accounts have significant limitations: * **Rigid authentication**: You lose your private key, you lose everything * **Limited authorization**: Can't easily implement multi-signature schemes or time-locked transactions * **Fixed fee payment**: Must pay fees in the native token from the same account * **No customization**: Can't adapt to different security requirements or use cases Account abstraction solves these problems by making accounts programmable. This enables: * **Recovery mechanisms**: Social recovery, hardware wallet backups, time-delayed recovery * **Flexible authentication**: Biometrics, passkeys, multi-factor authentication, custom signature schemes * **Fee abstraction**: Pay fees in any token, or have someone else pay for you * **Custom authorization**: Complex permission systems, spending limits, automated transactions ## Aztec's Native Account Abstraction[​](#aztecs-native-account-abstraction "Direct link to Aztec's Native Account Abstraction") Unlike Ethereum where account abstraction is implemented at the application layer (ex. ERC-4337), Aztec has **native account abstraction** at the protocol level. This means: 1. **Every account is a smart contract** - There are no externally owned accounts (EOAs) 2. **Unified experience** - All accounts have the same capabilities and flexibility 3. **Protocol-level support** - The entire network is designed around smart contract accounts 4. **Privacy-first design** - Account abstraction works seamlessly with Aztec's privacy features ### Breaking the DoS Attack Problem[​](#breaking-the-dos-attack-problem "Direct link to Breaking the DoS Attack Problem") One of the biggest challenges in account abstraction is preventing denial-of-service (DoS) attacks. If accounts can have arbitrary validation logic, malicious actors could flood the network with transactions that are expensive to validate but ultimately invalid. Other account abstraction systems (like ERC-4337) solve this by restricting what validation logic can do - limiting opcodes, storage access, and gas. This works but limits flexibility. Aztec takes a different approach: validation happens client-side with ZK proofs, so the sequencer only verifies a constant-size proof regardless of validation complexity: With this approach: * **Client performs validation**: All complex logic runs on the user's device * **Proof generation**: The client generates a succinct ZK proof that validation succeeded * **Constant verification cost**: The sequencer only verifies the proof - a constant-time operation regardless of validation complexity This means we can have: * **Unlimited validation complexity** without affecting network performance * **Free complex operations** like verifying 100 signatures or checking complex conditions * **Better privacy** as validation logic isn't visible onchain ## How Aztec Accounts Work[​](#how-aztec-accounts-work "Direct link to How Aztec Accounts Work") ### Account Architecture[​](#account-architecture "Direct link to Account Architecture") Every Aztec account is a smart contract with a specific structure. At its core, an account contract must: 1. **Authenticate transactions** - Verify that the transaction is authorized by the account owner 2. **Execute calls** - Perform the requested operations (transfers, contract calls, etc.) 3. **Manage keys** - Handle the various keys used for privacy and authentication 4. **Handle fees** - Determine how transaction fees are paid ### The Account Contract Structure[​](#the-account-contract-structure "Direct link to The Account Contract Structure") Here's the essential structure of an Aztec account contract: The entrypoint function follows this pattern: 1. **Authentication** - Verify the transaction is authorized (signatures, multisig, etc.) 2. **Fee Payer Setup** - Set the account as fee payer if using its own balance 3. **Application Execution** - Execute the requested function calls 4. **Cancellation Handling** - Optionally emit a nullifier for transaction cancellation ### Address Derivation[​](#address-derivation "Direct link to Address Derivation") Aztec addresses are **deterministic** - they can be computed before deployment. An address is derived from: ``` Address = hash( public_keys_hash, // All the account's public keys partial_address // Contract deployment information ) ``` Where: * **public\_keys\_hash** = Combined hash of nullifier, incoming viewing, and other keys * **partial\_address** = Hash of the contract code and deployment parameters This deterministic addressing enables powerful features: * **Pre-funding**: Send funds to an address before the account is deployed * **Counterfactual deployment**: Interact with an account as if it exists, deploy it later * **Address recovery**: Recompute addresses from known keys #### Complete Address[​](#complete-address "Direct link to Complete Address") While an address alone is sufficient for receiving funds, spending notes requires a **complete address** which includes: * All the user's public keys (nullifier, incoming viewing, etc.) * The partial address (contract deployment information) * The contract address itself The complete address proves that the nullifier key inside the address is correct, enabling the user to spend their notes. ## The Entrypoint Pattern[​](#the-entrypoint-pattern "Direct link to The Entrypoint Pattern") The entrypoint is the gateway to your account. When someone wants to execute a transaction from your account, they call the entrypoint with a payload describing what to do. ### Transaction Flow[​](#transaction-flow "Direct link to Transaction Flow") Here's how a transaction flows through an account: ### Non-Standard Entrypoints[​](#non-standard-entrypoints "Direct link to Non-Standard Entrypoints") The beauty of account abstraction is that not every contract needs authentication. Some contracts can have **permissionless entrypoints**. For example, a lottery contract where anyone can trigger the payout: * No authentication required * Anyone can call the function * The contract itself handles the logic and constraints This pattern is useful for: * **Automated operations**: Keepers can trigger time-based actions * **Public goods**: Anyone can advance the state of a protocol * **Gasless transactions**: Users don't need to hold fee tokens ## Account Lifecycle[​](#account-lifecycle "Direct link to Account Lifecycle") ### 1. Pre-deployment (Counterfactual State)[​](#1-pre-deployment-counterfactual-state "Direct link to 1. Pre-deployment (Counterfactual State)") Before deployment, an account exists in a **counterfactual state**: * The address can be computed deterministically * Can receive funds (notes can be encrypted to the address) * Cannot send transactions (no code deployed) ### 2. Deployment[​](#2-deployment "Direct link to 2. Deployment") Deploying an account involves: 1. Submitting the account contract code 2. Registering in the contract instance registry 3. Paying deployment fees (either self-funded or sponsored) See [Creating Accounts](/developers/testnet/docs/aztec-js/how_to_create_account.md) for code examples. ### 3. Initialization[​](#3-initialization "Direct link to 3. Initialization") Accounts can be initialized for different purposes: * **Private-only**: Just needs initialization, no public deployment * **Public interaction**: Requires both initialization and deployment The contract is initialized when one of the functions marked with the `#[initializer]` annotation has been invoked. Multiple functions in the contract can be marked as initializers. Contracts may have functions that skip the initialization check (marked with `#[noinitcheck]`). note Account deployment and initialization are not required to receive notes. The user address is deterministically derived, so funds can be sent to an account that hasn't been deployed yet. ### 4. Active Use[​](#4-active-use "Direct link to 4. Active Use") Once deployed and initialized, accounts can: * Send and receive private notes * Interact with public and private functions * Authorize actions via authentication witnesses * Pay fees in various ways ## Authentication Witnesses (AuthWit)[​](#authentication-witnesses-authwit "Direct link to Authentication Witnesses (AuthWit)") Aztec replaces Ethereum's dangerous "infinite approval" pattern with **Authentication Witnesses** - a more secure authorization scheme where users sign specific actions rather than granting blanket permissions. Instead of approving unlimited token transfers, users authorize exact actions with precise parameters. This eliminates persistent security risks while enabling better UX through batched operations. For detailed information about how AuthWit works in both private and public contexts, see the [Authentication Witness documentation](/developers/testnet/docs/foundational-topics/advanced/authwit.md). ## Transaction Abstractions[​](#transaction-abstractions "Direct link to Transaction Abstractions") Aztec abstracts two critical components of transactions that are typically rigid in other blockchains: nonces and fees. ### Nonce Abstraction[​](#nonce-abstraction "Direct link to Nonce Abstraction") Unlike Ethereum where nonces are sequential counters enforced by the protocol, Aztec lets account contracts implement their own replay protection. **Different nonce strategies possible:** | Strategy | How it Works | Benefits | | ------------------------------ | ------------------------------------------ | ---------------------------------- | | **Sequential** (like Ethereum) | Must use nonces in order (1, 2, 3...) | Simple, predictable ordering | | **Unordered** (like Bitcoin) | Any unused nonce is valid | Parallel transactions, no blocking | | **Time-windowed** | Nonces valid only in specific time periods | Automatic expiration, batching | | **Merkle-tree based** | Nonces from a pre-committed set | Privacy, batch pre-authorization | This enables: * **Parallel transactions**: No need to wait for one tx to complete before sending another * **Custom cancellation**: Define your own rules for replacing/cancelling transactions * **Flexible ordering**: Implement priority queues, batching, or time-based ordering ### Fee Abstraction[​](#fee-abstraction "Direct link to Fee Abstraction") Unlike traditional blockchains where users must pay fees in the native token, Aztec accounts can implement custom fee payment logic: * **Pay with any token** through integrated swaps * **Sponsored transactions** where applications pay for users * **Meta-transactions** with relayer networks * **Custom payment models** like subscriptions or paymasters This flexibility is crucial for user onboarding and enables gasless experiences. For detailed information about fee mechanics and payment options, see the [Fees documentation](/developers/testnet/docs/foundational-topics/fees.md). ## Account Contracts[​](#account-contracts "Direct link to Account Contracts") Aztec provides several account contract implementations: * **Schnorr Account** - Single-key account using Schnorr signatures (default) * **ECDSA Account** - Single-key account using ECDSA signatures (secp256k1 or secp256r1) These implement the simple signature pattern where a single key controls the account. The flexibility of account abstraction also enables more complex patterns like multisig, social recovery, or session keys - these can be implemented as custom account contracts. ## Summary[​](#summary "Direct link to Summary") Aztec's native account abstraction means every account is a smart contract with customizable authentication, fee payment, and authorization logic. Because validation happens client-side with ZK proofs, complex validation doesn't increase network costs - enabling patterns that aren't practical on other blockchains. --- # Keys ## Account Keys in Aztec[​](#account-keys-in-aztec "Direct link to Account Keys in Aztec") Unlike traditional blockchains where accounts use a single key pair, Aztec accounts use **multiple specialized key pairs**, each serving a distinct cryptographic purpose. This separation is fundamental to Aztec's privacy model and enables powerful security features that aren't possible with single-key systems. ## Why Multiple Keys?[​](#why-multiple-keys "Direct link to Why Multiple Keys?") The separation of keys in Aztec serves critical purposes: * **Privacy isolation**: Different keys for different operations prevent correlation attacks * **Selective disclosure**: Share viewing access without compromising spending ability * **Damage limitation**: If one key is compromised, others remain secure * **Flexible authorization**: Choose any authentication method without affecting core protocol keys * **Per-application security**: Keys can be scoped to specific contracts to minimize exposure This multi-key architecture is what enables Aztec to provide strong privacy guarantees while maintaining flexibility and security. ## Key Types[​](#key-types "Direct link to Key Types") Each Aztec account uses multiple key pairs: | Key Type | Purpose | Protocol Managed | Rotatable | | ------------------------------------ | ------------------------------------------------------------ | ---------------- | --------- | | **Nullifier Keys** (`Npk_m`) | Spending notes (destroying private state) | Yes | No | | **Incoming Viewing Keys** (`Ivpk_m`) | Decrypting received notes | Yes | No | | **Outgoing Viewing Keys** (`Ovpk_m`) | Reserved — not currently used | Yes | No | | **Tagging Keys** (`Tpk_m`) | Reserved — not currently used | Yes | No | | **Message-Signing Keys** (`Mspk_m`) | Reserved — slot for future protocol-level message signing | Yes | No | | **Fallback Keys** (`Fbpk_m`) | Reserved — slot for future account-recovery / fallback flows | Yes | No | | **Signing Keys** | Transaction authorization | No (app-defined) | Yes | Protocol keys are embedded into the protocol and cannot be changed once an account is created. The signing key is abstracted to the account contract developer, allowing complete flexibility in authentication methods. ### Nullifier Keys[​](#nullifier-keys "Direct link to Nullifier Keys") **Purpose**: Spending notes (private state consumption) Nullifier keys enable spending private notes. When using a note (like spending a token), the spender must prove they have the right to nullify it - essentially marking it as "spent" without revealing which note is being spent. **How it works:** 1. Each account has a master nullifier key pair (`Npk_m`, `nhk_m`) 2. For each application, an **app-siloed** key is derived: `nhk_app = hash(nhk_m, app_contract_address)` 3. To spend a note, compute its nullifier using the note hash and app-siloed key 4. The protocol verifies the app-siloed key comes from your master key and that your master public key is in your address This ensures only the rightful owner can spend notes, while the app-siloing provides additional security isolation between contracts. tip This last point could be confusing for most developers: how could a protocol verify a secret key is derived from another secret key without knowing it? Well, *you* make that derivation, generating a ZK proof for it. The protocol just verifies that ZK proof! #### Accessing nullifier keys in code[​](#accessing-nullifier-keys-in-code "Direct link to Accessing nullifier keys in code") The nullifier hiding key (`nhk`) — sometimes referred to in older documentation as the "nullifier secret key" (`nsk`) — is the secret scalar used to compute nullifiers. You should **never** derive or construct this key manually. Use the framework-provided functions: | Context | Function | Import / Access | | ----------------------- | ------------------------------------------- | ----------------------------------------------------------------------- | | Private (constrained) | `context.request_nhk_app(owner_npk_m_hash)` | Called on `&mut PrivateContext` | | Unconstrained | `get_nhk_app(owner_npk_m_hash)` | `use aztec::keys::getters::get_nhk_app` | | TypeScript (master key) | `deriveMasterNullifierHidingKey(secretKey)` | `import { deriveMasterNullifierHidingKey } from '@aztec/aztec.js/keys'` | | TypeScript (app-siloed) | `computeAppNullifierHidingKey(nhkM, app)` | `import { computeAppNullifierHidingKey } from '@aztec/aztec.js/keys'` | To get the owner's master nullifier public key hash (needed as input): ``` let owner_npk_m_hash = get_public_keys(owner).npk_m_hash; ``` `PublicKeys` exposes the nullifier, outgoing-viewing, and tagging keys directly as their hashes; only `ivpk_m` is held as a Grumpkin point (it is required as a point for address derivation and encrypt-to-address). warning Do not compute nullifier keys by hand or derive custom blinding factors. The protocol kernel validates that `nhk_app` derives correctly from the master key — a hand-rolled value will fail verification. ### Incoming Viewing Keys[​](#incoming-viewing-keys "Direct link to Incoming Viewing Keys") **Purpose**: Receiving and decrypting private notes Incoming viewing keys enable private information to be shared with recipients. The sender uses the recipient's public viewing key (`Ivpk`) to encrypt notes, and the recipient uses their secret viewing key (`ivsk`) to decrypt them. **The encryption flow:** This uses elliptic curve Diffie-Hellman: both parties compute the same shared secret `S`, but only the recipient has the private key needed to decrypt. ### Signing Keys[​](#signing-keys "Direct link to Signing Keys") **Purpose**: Transaction authorization (optional, application-defined) Unlike nullifier and incoming viewing keys which are protocol-mandated, signing keys are **completely abstracted** - thanks to [native account abstraction](/developers/testnet/docs/foundational-topics/accounts.md), any authorization method can be implemented: * **Signature-based**: ECDSA, Schnorr, BLS, multi-signature * **Biometric**: Face ID, fingerprint * **Web2 credentials**: Google OAuth, passkeys * **Custom logic**: Time locks, spending limits, multi-party authorization **Traditional signature approach:** When using signatures, the account contract validates the signature against a stored public key. Here's an example from the Schnorr account contract: is\_valid\_impl ``` // Load public key from storage let storage = Storage::init(context); let public_key = storage.signing_public_key.get_note(); // Safety: The witness is only used as a "magical value" that makes the signature verification below pass. // Hence it's safe. let limbs: [Field; 4] = unsafe { get_auth_witness(outer_hash) }; let signature = ( std::embedded_curve_ops::EmbeddedCurveScalar::new(limbs[0], limbs[1]), std::embedded_curve_ops::EmbeddedCurveScalar::new(limbs[2], limbs[3]), ); let pub_key = std::embedded_curve_ops::EmbeddedCurvePoint { x: public_key.x, y: public_key.y }; // Verify signature of the payload bytes schnorr::verify_signature(pub_key, signature, outer_hash) ``` > [Source code: noir-projects/noir-contracts/contracts/account/schnorr\_account\_contract/src/main.nr#L67-L83](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/account/schnorr_account_contract/src/main.nr#L67-L83) The flexibility of signing key storage and rotation is entirely up to your account contract implementation. ## Address Derivation[​](#address-derivation "Direct link to Address Derivation") Your Aztec address is deterministically computed from your public keys and account contract. This enables anyone to encrypt notes to your address without needing additional information. ![Address derivation](/assets/images/address_derivation-b3d47a6dfb56a156e7ce237481166bcf.svg) note The diagram above is being updated to reflect the new key-hashing scheme described below. Treat the text formulas as authoritative until a refresh lands. ``` pre_address = hash(public_keys_hash, partial_address) where: public_keys_hash = hash(npk_m_hash, ivpk_m_hash, ovpk_m_hash, tpk_m_hash, mspk_m_hash, fbpk_m_hash) ivpk_m_hash = hash(Ivpk_m.x, Ivpk_m.y) // computed in-circuit npk_m_hash, ovpk_m_hash, tpk_m_hash, // computed off-circuit (PXE) mspk_m_hash, fbpk_m_hash // computed off-circuit (PXE) partial_address = hash(contract_class_id, salted_initialization_hash) contract_class_id = hash(artifact_hash, fn_tree_root, public_bytecode_commitment) salted_initialization_hash = hash(salt, constructor_hash, deployer_address, immutables_hash) ``` The final address is derived as `address = (pre_address * G + Ivpk_m).x` - only the x-coordinate of the resulting elliptic curve point. Note that `Ivpk_m` (the master incoming viewing key) is the only public key still represented as an elliptic curve point: address derivation needs it as a point so that anyone can encrypt to the address without further information. The other five master keys are exposed only as their hashes. This derivation ensures: * Your address is deterministic (can be computed before deployment) * Keys and contract code are cryptographically bound to the address * The address proves ownership of the nullifier key needed to spend notes note The `Ovpk` (outgoing viewing key) and `Tpk` (tagging key) exist in the protocol's `PublicKeys` struct but are not currently used. They're reserved for future protocol upgrades. note The `Mspk` (master message-signing key) and `Fbpk` (master fallback key) hash slots exist in `PublicKeys` and participate in the address derivation above, but there is **no canonical derivation path for them yet**. `deriveKeys(secretKey)` stamps the canonical default hashes (`DEFAULT_MSPK_M_HASH`, `DEFAULT_FBPK_M_HASH`) into every account, so today they contribute no per-account entropy. When a real derivation lands in a future release, the address derived from the same secret will change — see the migration notes. ## Key Management[​](#key-management "Direct link to Key Management") ### Key Generation and Derivation[​](#key-generation-and-derivation "Direct link to Key Generation and Derivation") Protocol keys (nullifier and incoming viewing) are automatically generated by the [Private Execution Environment (PXE)](/developers/testnet/docs/foundational-topics/pxe.md) when creating an account. The PXE handles: * Initial key pair generation * App-siloed key derivation * Secure key storage and oracle access * Key material never leaves the client All keys use elliptic curve cryptography on the Grumpkin curve: * Secret keys are scalars * Public keys are elliptic curve points (secret × generator point) Signing keys are application-defined and managed by your account contract logic. ### App-Siloed Keys[​](#app-siloed-keys "Direct link to App-Siloed Keys") Nullifier keys are **app-siloed** - scoped to each contract that uses them. This provides crucial security isolation: **How it works:** ``` nhk_app = hash(nhk_m, app_contract_address) ``` **Security benefits:** 1. **Damage containment**: If a nullifier key for one app leaks, other apps remain secure 2. **Privacy preservation**: Activity in different apps cannot be correlated via nullifier keys ### Key Rotation[​](#key-rotation "Direct link to Key Rotation") **Protocol keys (nullifier, incoming viewing):** Cannot be rotated. They are embedded in the address, which is immutable. If compromised, a new account must be deployed. **Signing keys:** Fully rotatable, depending on the account contract implementation. Options include: * Change keys on a schedule * Rotate after suspicious activity * Implement time-delayed rotation for security ## Summary[​](#summary "Direct link to Summary") Aztec's multi-key architecture is fundamental to its privacy and security model: | Key Type | Purpose | App-Siloed | Rotatable | Managed By | | ------------------------------- | ------------------------- | ---------- | --------- | -------------- | | **Nullifier** (`Npk_m`) | Spend notes | Yes | No | Protocol (PXE) | | **Incoming Viewing** (`Ivpk_m`) | Decrypt received notes | No | No | Protocol (PXE) | | **Outgoing Viewing** (`Ovpk_m`) | Reserved | N/A | No | Protocol (PXE) | | **Tagging** (`Tpk_m`) | Reserved | N/A | No | Protocol (PXE) | | **Message-Signing** (`Mspk_m`) | Reserved | N/A | No | Protocol (PXE) | | **Fallback** (`Fbpk_m`) | Reserved | N/A | No | Protocol (PXE) | | **Signing** | Transaction authorization | N/A | Yes | Application | **Key takeaways:** * **Separation enables privacy**: Different keys for different operations prevent correlation and limit damage from compromise * **App-siloing adds security**: Per-contract nullifier keys isolate risk * **Flexibility in authorization**: Signing keys are completely abstracted - use any authentication method * **Protocol keys are permanent**: Nullifier and viewing keys are embedded in your address and cannot be changed * **Client-side security**: All key material is generated and managed in the PXE, never exposed to the network This architecture allows Aztec to provide strong privacy guarantees while maintaining the flexibility needed for various security models and use cases. --- # Authentication Witness (Authwit) Authentication Witness is a scheme for authenticating actions on Aztec, allowing users to authorize third-parties (protocols or other users) to execute actions on their behalf. For a video walkthrough of how authwits work, including both the private and public flows, watch this explainer (find more on the [video lessons](/developers/testnet/docs/resources/video_lessons.md) page): [How Authorization Works on Aztec](https://www.youtube-nocookie.com/embed/VRZVOCdjGZ4) ## Summary[​](#summary "Direct link to Summary") * **Authwits authorize specific actions**, not blanket allowances like ERC20 approvals * **Two-level hash structure**: inner hash (caller, selector, args) wrapped in message hash (consumer, chain\_id, version, inner\_hash) * **Private authwits** are verified via static calls to the account contract, with witnesses provided through oracles * **Public authwits** use a shared registry where authorizations are stored and consumed * **Single-use enforcement** through nullifiers prevents replay attacks * **Implementation**: Use the `#[authorize_once]` macro in your contracts (see [implementation guide](/developers/testnet/docs/aztec-nr/framework-description/authentication_witnesses.md)) ## Background[​](#background "Direct link to Background") In traditional EVM contracts, users authorize third-party actions through `approve` (setting allowances) or `permit` (signed approvals). Both approaches have drawbacks: infinite approvals create security risks, two-transaction flows hurt UX, and smart contract wallets struggle with signature-based permits. Aztec's private state model makes traditional approvals even more problematic. Even if you approve an allowance, the recipient can't spend your private tokens without knowing the note secrets. See [Hybrid State model](/developers/testnet/docs/foundational-topics/state_management.md) and [keys](/developers/testnet/docs/foundational-topics/accounts/keys.md) for more on private state. Authwits solve this by authorizing specific actions rather than blanket allowances, with verification happening through account contracts. ## How authwits work[​](#how-authwits-work "Direct link to How authwits work") Since private execution happens on the user's device, we can use oracles to provide authorization data mid-execution. The user provides a "witness" (proof of authorization) that the account contract validates. Witness vs signature We use "witness" instead of "signature" because authorization doesn't require a cryptographic signature. Depending on the account contract implementation, it could be a password or other mechanism. ### Hash structure[​](#hash-structure "Direct link to Hash structure") Authwits use a two-level hash structure: **Inner hash** encodes the specific action being authorized: ``` inner_hash = H(caller, selector, args_hash) ``` **Message hash** wraps the inner hash with context to prevent cross-chain replay attacks: ``` message_hash = H(consumer, chain_id, version, inner_hash) ``` Where * `caller` is the address attempting the action (e.g., a DeFi contract) * `selector` is the function selector being called * `args_hash` is the hash of the function arguments * `consumer` is the contract verifying the authorization (e.g., the token contract) * `chain_id` and `version` prevent cross-chain replay attacks **Example:** Authorizing a DeFi contract to transfer tokens: ``` inner_hash = H(defi, transfer_selector, H(alice_account, defi, 1000)); message_hash = H(token, chain_id, version, inner_hash); ``` This reads as "defi is allowed to call the token's transfer function with arguments (alice\_account, defi, 1000) on this specific chain". ### Private authwit flow[​](#private-authwit-flow "Direct link to Private authwit flow") In private execution, the Token contract asks Alice's account contract to verify the authwit. The account contract requests the witness from Alice via an oracle, validates it, and returns the result. Static calls for security The authwit verification uses a static call to the account contract. This prevents the account from re-entering the flow and modifying state during verification. ### Public authwit flow[​](#public-authwit-flow "Direct link to Public authwit flow") In public execution, oracles aren't available since the sequencer runs the code. Instead, authorizations are stored in a shared registry before use. The registry approach has a gas optimization: if authorization is set and consumed in the same transaction, the state changes cancel out, saving gas. Why use Auth Registry for signatures in public? ECDSA signature verification is not directly available in public functions due to AVM limitations. The public authwit flow above is the recommended pattern: verify signatures in private, store approvals in the Auth Registry, and consume them in public. See [AVM Cryptographic Compatibility](/developers/testnet/docs/foundational-topics/advanced/circuits/avm_compatibility.md) for more details. ### Replay prevention[​](#replay-prevention "Direct link to Replay prevention") Each authwit can only be used once. The consuming contract emits a nullifier for the action, preventing reuse. This is similar to how notes work. To allow the same action multiple times (e.g., repeated transfers of the same amount), include a nonce in the arguments: ``` inner_hash = H(defi, transfer_selector, H(alice_account, defi, 1000, nonce)); ``` The account contract cannot emit the nullifier (it's called via static call), so the consuming contract handles this. The authwit library manages this automatically. ### Cancelling authwits[​](#cancelling-authwits "Direct link to Cancelling authwits") You can cancel an authwit before it's used by emitting its nullifier directly. This invalidates the authwit without executing the authorized action: ``` fn cancel_authwit(inner_hash: Field) { let on_behalf_of = self.msg_sender(); let nullifier = compute_authwit_nullifier(on_behalf_of, inner_hash); self.context.push_nullifier_unsafe(nullifier); } ``` ## Differences from ERC20 approvals[​](#differences-from-erc20-approvals "Direct link to Differences from ERC20 approvals") | Aspect | ERC20 Approve | Authwit | | -------------- | ------------------------------- | -------------------------- | | Scope | Blanket allowance | Specific action | | User awareness | Often unclear amounts | Exact action visible | | Revocation | Requires transaction | Can cancel with nullifier | | Private state | Cannot work (need note secrets) | Works via account contract | Private authwits and note secrets While authwits authorize a contract to perform an action, spending private notes still requires knowledge of the note secrets. For private tokens, the note owner must be involved in the transaction—they cannot simply give another user an authwit and have that user spend the notes independently. ## Use cases[​](#use-cases "Direct link to Use cases") Authwits work for any function requiring third-party authorization: * Token transfers and burns * DeFi deposits and withdrawals * Governance voting * Bridge operations (public to private transfers) * Any contract interaction requiring user approval ## Implementation[​](#implementation "Direct link to Implementation") Use the `#[authorize_once]` macro to add authwit verification to your contract functions: ``` #[authorize_once("from", "authwit_nonce")] #[external("private")] fn transfer_in_private( from: AztecAddress, to: AztecAddress, amount: u128, authwit_nonce: Field, ) { // Transfer logic here } ``` The macro handles authwit verification and nullifier emission automatically. For complete implementation details, see the [developer documentation](/developers/testnet/docs/aztec-nr/framework-description/authentication_witnesses.md). --- # Circuits Central to Aztec's operations are 'circuits' derived both from the core protocol and the developer-written Aztec.nr contracts. The core circuits enhance privacy by adding additional security checks and preserving transaction details - a characteristic Ethereum lacks. On this page, you’ll learn a bit more about these circuits and their integral role in promoting secure and efficient transactions within Aztec's privacy-centric framework. ## Motivation[​](#motivation "Direct link to Motivation") In Aztec, circuits come from two sources: 1. Core protocol circuits 2. User-written circuits (written as Aztec.nr Contracts and deployed to the network) This page focuses on the core protocol circuits. These circuits check that the rules of the protocol are being adhered to. When a function in an Ethereum smart contract is executed, the EVM performs checks to ensure that Ethereum's transaction rules are being adhered-to correctly. Stuff like: * "Does this tx have a valid signature?" * "Does this contract address contain deployed code?" * "Does this function exist in the requested contract?" * "Is this function allowed to call this function?" * "How much gas has been paid, and how much is left?" * "Is this contract allowed to read/update this state variable?" * "Perform the state read / state write" * "Execute these opcodes" All of these checks have a computational cost, for which users are charged gas. Many existing L2s move this logic offchain, as a way of saving their users gas costs, and as a way of increasing tx throughput. zk-Rollups, in particular, move these checks offchain by encoding them in zk-S(N/T)ARK circuits. Rather than paying a committee of Ethereum validators to perform the above kinds of checks, L2 users instead pay a sequencer to execute these checks via the circuit(s) which encode them. The sequencer can then generate a zero-knowledge proof of having executed the circuit(s) correctly, which they can send to a rollup contract on Ethereum. The Ethereum validators then verify this zk-S(N/T)ARK. It often turns out to be much cheaper for users to pay the sequencer to do this, than to execute a smart contract on Ethereum directly. But there's a problem. Ethereum (and the EVM) doesn't have a notion of privacy. * There is no notion of a private state variable in the EVM. * There is no notion of a private function in the EVM. So users cannot keep private state variables' values private from Ethereum validators, nor from existing (non-private) L2 sequencers. Nor can users keep the details of which function they've executed private from validators or sequencers. How does Aztec add privacy? Well, we just encode *extra* checks in our zk-Rollup's zk-SNARK circuits! These extra checks introduce the notions of private state and private functions, and enforce privacy-preserving constraints on every transaction being sent to the network. In other words, since neither the EVM nor other rollups have rules for how to preserve privacy, we've written a new rollup which introduces such rules, and we've written circuits to enforce those rules! What kind of extra rules / checks does a rollup need, to enforce notions of private states and private functions? Stuff like: * "Perform state reads and writes using new tree structures which prevent tx linkability" (see [indexed merkle tree](/developers/testnet/docs/foundational-topics/advanced/storage/indexed_merkle_tree.md). * "Hide which function was just executed, by wrapping it in a zk-snark" * "Hide all functions which were executed as part of this tx's stack trace, by wrapping the whole tx in a zk-snark" ## Aztec core protocol circuits[​](#aztec-core-protocol-circuits "Direct link to Aztec core protocol circuits") So what kinds of core protocol circuits does Aztec have? ### Kernel, Rollup, and Squisher Circuits[​](#kernel-rollup-and-squisher-circuits "Direct link to Kernel, Rollup, and Squisher Circuits") The specs of these have recently been updated. Eg for squisher circuits since Honk and Goblin Plonk schemes are still being improved! But we'll need some extra circuit(s) to squish a Honk proof (as produced by the Root Rollup Circuit) into a Standard Plonk or Fflonk proof, for cheap verification on Ethereum. --- # AVM Cryptographic Compatibility Private and public functions in Aztec use different execution models. Private functions compile to ACIR circuits and have access to the full Noir standard library. Public functions compile to AVM bytecode via the transpiler, which supports only a specific set of cryptographic operations. ## Compatibility Table[​](#compatibility-table "Direct link to Compatibility Table") The table below lists the low-level blackbox operations and whether they are available in the AVM. Higher-level Noir standard library functions (like `sha256::sha256_var`, `keccak256::keccak256`, `poseidon2::hash`, and `std::hash::pedersen_hash`) are built on these primitives and work in public functions when the underlying operations are supported. | Noir Primitive | Private (ACIR) | Public (AVM) | Notes | | --------------------------- | -------------- | ----------------- | ------------------------------------------- | | Poseidon2 Permutation | Supported | Supported | `POSEIDON2PERM` opcode | | Pedersen Hash / Commitment | Supported | Supported | Lowered to `ECADD` and `MSM` operations | | SHA-256 Compression | Supported | Supported | `SHA256COMPRESSION` opcode | | Keccak f1600 | Supported | Supported | `KECCAKF1600` opcode | | Embedded Curve Add | Supported | Supported | `ECADD` opcode (Grumpkin curve) | | Multi-Scalar Multiplication | Supported | Supported | Lowered to `TORADIXBE` + `ECADD` operations | | ToRadix | Supported | Supported | `TORADIXBE` opcode | | ECDSA secp256k1 | Supported | **Not supported** | Transpiler panics | | ECDSA secp256r1 | Supported | **Not supported** | Transpiler panics | | AES-128 Encrypt | Supported | **Not supported** | Transpiler panics | | Blake2s | Supported | **Not supported** | Transpiler panics | | Blake3 | Supported | **Not supported** | Transpiler panics | ## Why the Difference[​](#why-the-difference "Direct link to Why the Difference") Private functions are compiled to ACIR (Abstract Circuit Intermediate Representation), which supports the full set of Noir standard library blackbox functions. These are evaluated as part of the zk-SNARK proof generation on the user's device. Public functions are compiled to AVM bytecode via the transpiler. The AVM has a fixed instruction set, and each supported cryptographic operation must either have a dedicated opcode or be reducible to a sequence of supported opcodes. For example, multi-scalar multiplication has no dedicated opcode but is lowered to `TORADIXBE` and `ECADD` instructions. Operations that cannot be mapped to supported opcodes cannot be transpiled. ## What Error Will I See?[​](#what-error-will-i-see "Direct link to What Error Will I See?") If you use an unsupported blackbox function in a `#[external("public")]` function, the transpiler will panic at compile time with a message like: ``` Transpiler doesn't know how to process EcdsaSecp256k1 ``` where the final token is the name of the unsupported `BlackBoxOp` variant (e.g. `AES128Encrypt`, `Blake2s`, `Blake3`). ## Signature Verification in Public: Workarounds[​](#signature-verification-in-public-workarounds "Direct link to Signature Verification in Public: Workarounds") Since ECDSA signature verification is not available in public functions, use the **Authentication Registry** pattern: 1. Verify signatures in a **private** function (where all Noir primitives are available) 2. Store approval hashes in the **Auth Registry** (a shared public contract) 3. Consume the approvals in **public** functions This is exactly how public authwits work. See [Authentication Witnesses](/developers/testnet/docs/foundational-topics/advanced/authwit.md) for the full pattern. Schnorr signatures The [`noir-lang/schnorr`](https://github.com/noir-lang/schnorr) library implements Schnorr verification in pure Noir using embedded curve operations (ECADD, MSM), which are supported in the AVM. This means Schnorr verification may work in public functions. However, the standard Aztec account contracts only use Schnorr in private functions, and the recommended pattern remains verifying signatures in private via the Auth Registry. ## ISA Reference[​](#isa-reference "Direct link to ISA Reference") For the complete list of AVM opcodes, see the [AVM ISA Quick Reference](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/yarn-project/simulator/docs/avm/avm-isa-quick-reference.md). ## Related Pages[​](#related-pages "Direct link to Related Pages") * [Public Execution (AVM)](/developers/testnet/docs/foundational-topics/advanced/circuits/public_execution.md) – How the AVM executes public functions * [Authentication Witnesses](/developers/testnet/docs/foundational-topics/advanced/authwit.md) – The Auth Registry pattern for public authorization * [Call Types](/developers/testnet/docs/foundational-topics/call_types.md) – How private and public functions interact * [Private Kernel](/developers/testnet/docs/foundational-topics/advanced/circuits/private_kernel.md) – How private functions are processed --- # Private Kernel Circuit The private kernel circuit is executed by the user on their own device. This ensures private inputs remain private. note This is the only core protocol circuit that truly requires the "zero-knowledge" property. Other circuits use SNARKs for succinct verification, but don't need to hide witness data. The private kernel must hide: the contract function executed, the user's address, and the function's inputs and outputs. ## Overview[​](#overview "Direct link to Overview") The private kernel processes all private function calls in a transaction, accumulating their side effects (note hashes, nullifiers, logs, messages) and validation requests. It runs recursively—once per private function call—building up a proof that all private execution was correct. The kernel validates: * Proof of private function execution * Correct call context (caller, address, arguments) * Proper scoping of side effects to their originating contract * Uniqueness and ordering of side effect counters ## Kernel Phases[​](#kernel-phases "Direct link to Kernel Phases") The private kernel consists of five circuit types: ### Init[​](#init "Direct link to Init") The entry point for private kernel execution. It processes the first private function call in a transaction and validates: * The call matches the transaction request (origin, function, arguments) * The function is marked as private * No msg\_sender exists (first call has no caller) ### Inner[​](#inner "Direct link to Inner") Processes subsequent private function calls after init. Can chain multiple times as the call stack grows. It: * Verifies the previous kernel proof * Pops the next call from the private call stack * Validates the call matches its request * Appends new side effects to accumulated data ### Reset[​](#reset "Direct link to Reset") Can be called one or more times at any point after init and before tail/tail-to-public. Optimizes the accumulated data by: * Squashing transient note hash/nullifier pairs (where a note is created and nullified within the same transaction) along with their associated logs * Validating note hash and nullifier read requests against the state * Validating key validation requests This circuit reduces the data size before finalization. ### Tail[​](#tail "Direct link to Tail") The final circuit for **private-only** transactions (no public function calls). It: * Sorts remaining side effects * Converts accumulated data to rollup format * Produces output ready for rollup aggregation ### Tail to Public[​](#tail-to-public "Direct link to Tail to Public") The bridge circuit for transactions with both private and public execution. It: * Splits side effects into non-revertible and revertible arrays * Prepares data for public execution * Handles the transition from private to public phases ## Data Flow[​](#data-flow "Direct link to Data Flow") As the kernel processes each private call, it accumulates: | Data Type | Description | | -------------------- | --------------------------------------------------- | | Note hashes | Commitments to new private notes | | Nullifiers | Markers that invalidate notes or provide uniqueness | | L2 to L1 messages | Cross-chain messages to Ethereum | | Private logs | Encrypted event data | | Public call requests | Queued public function calls | Each item is scoped with the contract address that emitted it, ensuring proper attribution. ## Performance Impact[​](#performance-impact "Direct link to Performance Impact") The kernel circuits add significant overhead to every transaction. Understanding this overhead is important when designing contracts and profiling performance. ### Gate counts per phase[​](#gate-counts-per-phase "Direct link to Gate counts per phase") Consider a typical transaction where a user calls a single contract function. This actually involves **two** private function calls — the account entrypoint (e.g. `SchnorrAccount:entrypoint`) and your contract function — so the kernel processes both: ``` Account entrypoint: 22,000 gates (1st private call → processed by init) Your function: 14,000 gates (2nd private call → processed by inner) private_kernel_init: ~46,000 gates private_kernel_inner: ~101,000 gates private_kernel_reset: ~200,000 gates private_kernel_tail: ~44,000 gates ───────────────────────────────────── Total transaction: ~427,000 gates ``` The init circuit handles the first private function call (the account entrypoint), and inner handles each subsequent one. The exact kernel gate counts vary depending on the transaction's complexity (number of note hashes, nullifiers, read requests, etc.), but the key takeaway is: **kernel overhead is substantial and scales with the number of private function calls**. Each additional private function call in a transaction adds at least one more kernel inner circuit (\~101k gates). This means that architectural decisions — like whether to inline logic into one function vs. splitting across multiple function calls — can have a significant impact on total proving time. Design tip When profiling shows high gate counts, consider whether you can reduce the number of distinct private function calls in your transaction. For example, inlining a verification step into the calling function saves an entire kernel fold (\~101k gates), even if it slightly increases the calling function's own gate count. Large circuits do not prevent transaction inclusion A contract with a high gate count is **not** uncallable. The only effect of a large circuit is that it takes longer to prove on the client. Your transaction will still be included in a block as long as it is valid by the time the proof is submitted. In practice, there are only two edge cases where slow proving could cause issues: 1. **Transaction expiry**: If proving takes so long (e.g. over an hour) that the transaction becomes invalid by the time you're done. This is unlikely for most use cases. 2. **Fee volatility**: If network fees increase rapidly while you're proving, your transaction may be underpriced by the time it's broadcast. This is similar to signing an Ethereum transaction and waiting before submitting it. You can mitigate this by overpaying for fees if rapid inclusion is a priority. One of the major benefits of Aztec is that private computation is proven client-side: you can do as much computation as you want in private functions — the network cost is the same regardless. For tools to measure these costs, see the [profiling guide](/developers/testnet/docs/aztec-nr/framework-description/advanced/how_to_profile_transactions.md). ## Related Pages[​](#related-pages "Direct link to Related Pages") * [Transactions](/developers/testnet/docs/foundational-topics/transactions.md) - How private kernel fits into transaction execution * [Public Execution](/developers/testnet/docs/foundational-topics/advanced/circuits/public_execution.md) - How public functions are executed by the AVM * [Profiling Transactions](/developers/testnet/docs/aztec-nr/framework-description/advanced/how_to_profile_transactions.md) - Measuring gate counts and identifying bottlenecks * [Writing Efficient Contracts](/developers/testnet/docs/aztec-nr/framework-description/advanced/writing_efficient_contracts.md) - Optimization strategies --- # Public Execution (AVM) Public function execution in Aztec is handled by the **Aztec Virtual Machine (AVM)**. Unlike private execution (which runs on user devices), public execution runs on the sequencer's infrastructure where access to current state is required. note Unlike the private kernel which runs recursively for each private call, **there is no "public kernel" circuit**. The AVM executes all public functions for a transaction in a single proof. The term "public kernel" is sometimes used colloquially to refer to the AVM's role in public execution. ## Overview[​](#overview "Direct link to Overview") The AVM processes public call requests that were queued during private execution. It operates on the current state of the public data tree, note hash tree, and nullifier tree—state that only the sequencer knows at execution time. For transactions containing public functions, the execution flow is: 1. **Private Kernel** - Processes private functions, queues public call requests 2. **Hiding Kernel** - Bridges private output to public phase 3. **AVM** - Executes all public functions, produces accumulated data 4. **Rollup Circuits** - Validates proofs and includes in block ## Supported Cryptographic Operations[​](#supported-cryptographic-operations "Direct link to Supported Cryptographic Operations") The AVM supports Poseidon2, Pedersen, SHA-256, Keccak, and Grumpkin curve operations (embedded curve add, multi-scalar multiplication). ECDSA signature verification, AES-128, Blake2s, and Blake3 are not available in public functions. warning If your contract uses unsupported Noir blackbox functions in a public function, transpilation will fail at compile time. See [AVM Cryptographic Compatibility](/developers/testnet/docs/foundational-topics/advanced/circuits/avm_compatibility.md) for the full compatibility table and workarounds. ## Execution Phases[​](#execution-phases "Direct link to Execution Phases") The AVM executes public functions in three distinct phases: | Phase | Revertible | Purpose | | ------------- | ---------- | ----------------------------------------------- | | **Setup** | No | Non-revertible initialization (fee preparation) | | **App Logic** | Yes | Main application logic | | **Teardown** | Yes | Fee payment finalization | This phased approach enables atomic fee payment even if the main transaction logic reverts. The setup phase cannot be reverted, ensuring the sequencer receives payment. ## Inputs and Outputs[​](#inputs-and-outputs "Direct link to Inputs and Outputs") ### Inputs from Private Execution[​](#inputs-from-private-execution "Direct link to Inputs from Private Execution") The AVM receives from the private phase: * **Public call requests**: Setup, app logic, and teardown function calls * **Non-revertible accumulated data**: Note hashes, nullifiers, L2-L1 messages from setup phase * **Revertible accumulated data**: Note hashes, nullifiers, L2-L1 messages that can be reverted * **Gas settings**: Limits for execution and teardown * **Fee payer**: Address responsible for transaction fees ### Outputs[​](#outputs "Direct link to Outputs") After execution, the AVM produces: | Output | Description | | ------------------ | ------------------------------------------ | | Note hashes | Combined private + public note commitments | | Nullifiers | Combined private + public nullifiers | | L2-L1 messages | Cross-chain messages to Ethereum | | Public logs | Event data from public execution | | Public data writes | State updates to the public data tree | | End tree snapshots | Final state of all trees after execution | | Transaction fee | Computed fee based on gas consumed | | Reverted flag | Whether app logic phase reverted | ## State Transitions[​](#state-transitions "Direct link to State Transitions") The AVM validates state transitions by tracking tree snapshots: * **Start snapshots**: Tree roots before public execution * **End snapshots**: Tree roots after all public functions complete These snapshots are validated in the rollup circuits to ensure continuity across transactions in a block. ## Related Pages[​](#related-pages "Direct link to Related Pages") * [AVM Cryptographic Compatibility](/developers/testnet/docs/foundational-topics/advanced/circuits/avm_compatibility.md) – Which Noir primitives work in public functions * [Private Kernel](/developers/testnet/docs/foundational-topics/advanced/circuits/private_kernel.md) – How private functions are processed * [Call Types](/developers/testnet/docs/foundational-topics/call_types.md) – How private and public functions interact * [State Management](/developers/testnet/docs/foundational-topics/state_management.md) – How public and private state works --- # Rollup Circuits The rollup circuits compress thousands of transactions into a single SNARK proof for verification on Ethereum. They aggregate proofs from private kernel and AVM execution, validate state transitions, and produce the final epoch proof submitted to L1. note The rollup circuits use a "binary tree of proofs" topology. This allows proof generation to be parallelized across prover instances—each layer of the tree can be computed in parallel, or subtrees can be distributed to different provers. ## Circuit Hierarchy[​](#circuit-hierarchy "Direct link to Circuit Hierarchy") Rollup circuits operate at four levels, each producing outputs consumed by the next: | Level | Circuits | Input | Output | | --------------- | ---------------------------------- | ------------------- | ----------------------- | | **Transaction** | TX Base (Private/Public), TX Merge | Kernel proofs | Transaction rollup data | | **Block** | Block Root, Block Merge | Transaction rollups | Block rollup data | | **Checkpoint** | Checkpoint Root, Checkpoint Merge | Block rollups | Checkpoint data | | **Epoch** | Root Rollup | Checkpoint rollups | Final epoch proof | ## Transaction Level[​](#transaction-level "Direct link to Transaction Level") ### TX Base Rollups[​](#tx-base-rollups "Direct link to TX Base Rollups") Process individual transactions from kernel proofs: * **TX Base Private** - Processes transactions with only private execution. Validates the private kernel proof, updates tree snapshots (note hash, nullifier), and accumulates fees and mana usage. * **TX Base Public** - Processes transactions that include public (AVM) execution. Validates the AVM proof, which has already performed tree updates and fee/mana accumulation during public execution. ### TX Merge Rollup[​](#tx-merge-rollup "Direct link to TX Merge Rollup") Merges pairs of transaction rollup proofs in binary fashion. Can chain recursively to aggregate many transactions into a single proof. Validates proof correctness and consecutive transaction ordering. ## Block Level[​](#block-level "Direct link to Block Level") ### Block Root Rollups[​](#block-root-rollups "Direct link to Block Root Rollups") Transition from transaction-level to block-level outputs. Several variants handle different scenarios: * **Block Root First** - First block of a checkpoint (validates parity root and L1-to-L2 tree) * **Block Root** - Subsequent blocks in a checkpoint * **Block Root Single TX** - Optimized variant for single-transaction blocks * **Block Root Empty TX First** - Handles empty blocks These circuits update the archive tree, compute block headers, and accumulate L2-to-L1 message hashes. ### Block Merge Rollup[​](#block-merge-rollup "Direct link to Block Merge Rollup") Merges pairs of block rollup proofs within a checkpoint. Validates archive continuity and state consistency between blocks. ## Checkpoint Level[​](#checkpoint-level "Direct link to Checkpoint Level") ### Checkpoint Root Rollups[​](#checkpoint-root-rollups "Direct link to Checkpoint Root Rollups") Transition from block-level to checkpoint-level outputs: * **Checkpoint Root** - Standard checkpoint containing multiple blocks * **Checkpoint Root Single Block** - Optimized for single-block checkpoints These circuits validate previous block headers, compute blob commitments, and accumulate fee recipients. ### Checkpoint Merge Rollup[​](#checkpoint-merge-rollup "Direct link to Checkpoint Merge Rollup") Merges pairs of checkpoint proofs. Validates checkpoint continuity and blob accumulator consistency. ### Checkpoint Padding[​](#checkpoint-padding "Direct link to Checkpoint Padding") A special circuit for epochs with only one checkpoint. Provides an empty right child for the binary tree structure. ## Epoch Level (Root Rollup)[​](#epoch-level-root-rollup "Direct link to Epoch Level (Root Rollup)") The final circuit that completes an epoch proof. It: * Merges two checkpoint rollup proofs * Validates epoch-level blob batching challenges * Produces the final `RootRollupPublicInputs` for L1 submission The root rollup output includes: * Previous and new archive roots * Checkpoint header hashes * Accumulated fees across all checkpoints * Final blob public inputs for data availability ## Flexible Tree Topology[​](#flexible-tree-topology "Direct link to Flexible Tree Topology") The architecture supports asymmetric "wonky trees" for efficiency: * Transactions can be grouped variably into blocks * Not all branches need the same depth * Single-element optimizations reduce proof overhead * Padding circuits handle partial epochs This flexibility allows sequencers to optimize proving costs based on actual workload. ## Related Pages[​](#related-pages "Direct link to Related Pages") * [Private Kernel](/developers/testnet/docs/foundational-topics/advanced/circuits/private_kernel.md) - How private function proofs are generated * [Public Execution](/developers/testnet/docs/foundational-topics/advanced/circuits/public_execution.md) - How the AVM produces public execution proofs --- # Indexed Merkle Tree (Nullifier Tree) ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") This page assumes familiarity with: * Merkle trees and membership proofs * The UTXO model for private state * Zero-knowledge proof concepts (circuits, constraints) ## Overview[​](#overview "Direct link to Overview") This page covers indexed merkle trees and how they improve nullifier tree performance in circuits, including: * Why nullifier trees are necessary * How indexed merkle trees work * Membership exclusion proofs * Batch insertions * Tradeoffs This content was presented to the [Privacy + Scaling Explorations team at the Ethereum Foundation](https://pse.dev/). [YouTube video player](https://www.youtube-nocookie.com/embed/x_0ZhUKtWSs?si=TmguEhgz4Gu07Dac) ## Primer on Nullifier Trees[​](#primer-on-nullifier-trees "Direct link to Primer on Nullifier Trees") Privacy in public blockchains requires a UTXO model. State is stored in encrypted UTXOs in merkle trees. Since updating state directly leaks information, we simulate updates by "destroying" old UTXOs and creating new ones, resulting in an append-only merkle tree. A classic merkle tree: ![Classic merkle tree structure showing leaf nodes hashed up to a root](/assets/ideal-img/normal-merkle-tree.568bc10.640.png) To destroy state, a "nullifier" tree stores deterministic values linked to notes in the append-only tree. This is typically implemented as a sparse Merkle Tree. A sparse merkle tree (not every leaf stores a value): ![Sparse merkle tree with empty leaves represented as zeros](/assets/ideal-img/sparse-merkle-tree.59ba475.640.png) To spend or modify a note in the private state tree, you must create a nullifier and prove it does not already exist in the nullifier tree. Since nullifier trees are modeled as sparse merkle trees, non-membership checks are conceptually trivial. Data is stored at the leaf index corresponding to its value. For example, in a sparse tree containing 2256 values, to prove non-membership of value 2128: * Prove tree\_values\[2128]=0 via a merkle membership proof (value does not exist) * Conversely, prove tree\_values\[2128]==1 to show the item exists ## Problems introduced by using Sparse Merkle Trees for Nullifier Trees[​](#problems-introduced-by-using-sparse-merkle-trees-for-nullifier-trees "Direct link to Problems introduced by using Sparse Merkle Trees for Nullifier Trees") While sparse Merkle Trees offer a simple solution, they have significant drawbacks. A sparse nullifier tree must have an index for e∈Fp​, which for the bn254 curve requires a depth of 254. A tree of depth 254 means 254 hashes per membership proof. Each nullifier insertion requires a non-membership check followed by an insertion—two trips from leaf to root. This results in 254×2 hashes per insertion. Since the tree is sparse, insertions are random and must be sequential, so hash count scales linearly with nullifier count. This causes constraint counts in rollup circuits to grow rapidly, leading to long proving times. ## Indexed Merkle Tree Constructions[​](#indexed-merkle-tree-constructions "Direct link to Indexed Merkle Tree Constructions") [This paper](https://eprint.iacr.org/2021/1263.pdf) (page 6) introduces indexed merkle trees, which enable efficient non-membership proofs. Each node stores a value v∈Fp​ and pointers to the leaf with the next higher value: leaf={v,inext​,vnext​}. Based on the tree's insertion rules, no leaves exist between the range (v,vnext​). The merkle tree forms a linked list of increasing values. Once inserted, a leaf's pointers can change but its nullifier value cannot. Since leaves are no longer positioned at (index==value), a deep tree is unnecessary—32 levels suffice. This improves insertions by approximately 8x (256/32). *The node that provides the non-membership check is called a "low nullifier".* The insertion protocol: 1. Look for a nullifier's corresponding low\_nullifier where: low\_nullifiernext\_value​>new\_nullifier > if new\_nullifier is the largest use the leaf: low\_nullifiernext\_value​==0 2. Perform membership check of the low nullifier. 3. Perform a range check on the low nullifier's value and next\_value fields: new\_nullifier​>low\_nullifiervalue​&&(new\_nullifier\ * If (low\_nullifiernext\_index​==0): * Special case, the low leaf is at the very end, so the new\_value must be higher than all values in the tree: * assert(low\_nullifiervalue​\ * assert(low\_nullifiervalue​\new\_valuevalue​) This provides significant performance improvement, but since the tree is not sparse, we can also perform batch insertions. ## Batch insertions[​](#batch-insertions "Direct link to Batch insertions") Since nullifiers are inserted deterministically (append-only), we can insert entire subtrees rather than appending nodes individually. However, for every node inserted, low nullifier pointers must be updated. This adds complexity when the low nullifier exists within the subtree being inserted. All impacted low nullifiers must be updated before the subtree insertion. Batch insertion in an append-only merkle tree: 1. Prove the target subtree consists of all empty values 2. Calculate the root of an empty subtree and perform an inclusion proof for this empty root 3. Recreate the subtree within the circuit 4. Use the same sibling path to get the new root after subtree insertion In the following example, a subtree of size 4 is inserted. The subtree is greyed out as "pending". **Legend**: * Green: New Inserted Value * Orange: Low Nullifier **Example** 1. Prepare to insert subtree \[35,50,60,15] ![Preparing to insert subtree with values 35, 50, 60, 15](/assets/ideal-img/subtree-insert-1.04d9287.640.png) 2. Update low nullifier for new nullifier 35 ![Updating low nullifier for value 35](/assets/ideal-img/subtree-insert-2.92b8e75.640.png) 3. Update low nullifier for new nullifier 50 (the low nullifier exists within the pending insertion subtree) ![Updating low nullifier for value 50 from pending subtree](/assets/ideal-img/subtree-insert-3.e86f005.640.png) 4. Update low nullifier for new nullifier 60 ![Updating low nullifier for value 60](/assets/ideal-img/subtree-insert-4.9375e53.640.png) 5. Update low nullifier for new nullifier 15 ![Updating low nullifier for value 15](/assets/ideal-img/subtree-insert-5.0bc9b9a.640.png) 6. Update pointers for new nullifier 15 ![Updating pointers for value 15](/assets/ideal-img/subtree-insert-6.169ec0e.640.png) 7. Insert subtree ![Final state after subtree insertion](/assets/ideal-img/subtree-insert-7.740d73d.640.png) ### Performance gains from subtree insertion[​](#performance-gains-from-subtree-insertion "Direct link to Performance gains from subtree insertion") Sparse nullifier tree insertions require 1 non-membership check (254 hashes) and 1 insertion (254 hashes). For 4 values: 2032 hashes. In a depth-32 indexed tree, each subtree insertion costs 1 non-membership check (32 hashes) and 1 pointer update (32 hashes) per value, plus subtree construction (\~67 hashes). Total: 327 hashes—a significant efficiency gain. *Range check constraint costs are negligible compared to hash costs.* ## Performing subtree insertions in a circuit context[​](#performing-subtree-insertions-in-a-circuit-context "Direct link to Performing subtree insertions in a circuit context") A challenge arises when the low nullifier for a value exists within the subtree being inserted. In this case, you cannot perform a non-membership check against the tree root because the leaf needed for non-membership has not yet been inserted. These are called "pending" insertions. **Circuit Inputs** * `new_nullifiers`: `fr[]` * `low_nullifier_leaf_preimages`: `tuple of {value: fr, next_index: fr, next_value: fr}` * `low_nullifier_membership_witnesses`: A sibling path and a leaf index of low nullifier * `current_nullifier_tree_root`: Current root of the nullifier tree * `next_insertion_index`: `fr`, the tip of the nullifier tree * `subtree_insertion_sibling_path`: A sibling path to check the subtree against the root If the low nullifier does not yet exist in the tree, membership checks fail and no non-membership proof can be produced. To handle this, the circuit must track all values pending insertion: * If `low_nullifier_membership_witness` is invalid (all zeros or leaf index of -1), this indicates a pending low nullifier read request * Loop through all pending insertions to find one with value lower than the nullifier being inserted * If no matching pending insertion is found, the circuit is invalid Pseudocode with pending insertion handling: ``` auto empty_subtree_hash = SOME_CONSTANT_EMPTY_SUBTREE; auto pending_insertion_subtree = []; auto insertion_index = inputs.next_insertion_index; auto root = inputs.current_nullifier_tree_root; // Check nothing exists where we would insert our subtree assert(membership_check(root, empty_subtree_hash, insertion_index >> subtree_depth, inputs.subtree_insertion_sibling_path)); for (i in len(new_nullifiers)) { auto new_nullifier = inputs.new_nullifiers[i]; auto low_nullifier_leaf_preimage = inputs.low_nullifier_leaf_preimages[i]; auto low_nullifier_membership_witness = inputs.low_nullifier_membership_witnesses[i]; if (low_nullifier_membership_witness is garbage) { bool matched = false; // Search for the low nullifier within our pending insertion subtree for (j in range(0, i)) { auto pending_nullifier = pending_insertion_subtree[j]; if (pending_nullifier.is_garbage()) continue; if (pending_nullifier[j].value < new_nullifier && (pending_nullifier[j].next_value > new_nullifier || pending_nullifier[j].next_value == 0)) { // Found matching low nullifier matched = true; // Update pointers auto new_nullifier_leaf = { .value = new_nullifier, .next_index = pending_nullifier.next_index, .next_value = pending_nullifier.next_value } // Update pending subtree pending_nullifier.next_index = insertion_index; pending_nullifier.next_value = new_nullifier; pending_insertion_subtree.push(new_nullifier_leaf); break; } } // could not find a matching low nullifier in the pending insertion subtree assert(matched); } else { // Membership check for low nullifier assert(perform_membership_check(root, hash(low_nullifier_leaf_preimage), low_nullifier_membership_witness)); // Range check low nullifier against new nullifier assert(new_nullifier < low_nullifier_leaf_preimage.next_value || low_nullifier_leaf.next_value == 0); assert(new_nullifier > low_nullifier_leaf_preimage.value); // Update new nullifier pointers auto new_nullifier_leaf = { .value = new_nullifier, .next_index = low_nullifier_preimage.next_index, .next_value = low_nullifier_preimage.next_value }; // Update low nullifier pointers low_nullifier_preimage.next_index = next_insertion_index; low_nullifier_preimage.next_value = new_nullifier; // Update state vals for next iteration root = update_low_nullifier(low_nullifier, low_nullifier_membership_witness); pending_insertion_subtree.push(new_nullifier_leaf); } next_insertion_index += 1; } // insert subtree root = insert_subtree(root, inputs.next_insertion_index >> subtree_depth, pending_insertion_subtree); ``` ## Drawbacks[​](#drawbacks "Direct link to Drawbacks") While indexed merkle trees provide significant circuit performance improvements, they increase computation and storage requirements for nodes. Finding the "low nullifier" for a non-membership proof requires searching existing nodes—a naive implementation uses brute force. Performance improves if nodes maintain a sorted data structure of existing nullifiers, though this increases storage footprint. ## Related resources[​](#related-resources "Direct link to Related resources") * [State Management](/developers/testnet/docs/foundational-topics/state_management.md) - How public and private state work, including nullifiers * [Circuits](/developers/testnet/docs/foundational-topics/advanced/circuits.md) - Core protocol circuits where indexed merkle trees are used * [Note Discovery](/developers/testnet/docs/foundational-topics/advanced/storage/note_discovery.md) - How private notes are discovered and managed * [Original paper](https://eprint.iacr.org/2021/1263.pdf) - Academic reference for indexed merkle trees --- # Note Discovery Note discovery refers to the process of a user identifying and decrypting [notes](/developers/testnet/docs/foundational-topics/state_management.md#notes) that belong to them. ## Alternative approaches[​](#alternative-approaches "Direct link to Alternative approaches") Other protocols have explored different note discovery mechanisms. **Brute force** approaches download all notes and trial-decrypt each one, but this becomes prohibitively expensive as networks grow. **Offchain communication** has the sender share note content directly with recipients, avoiding onchain costs but introducing reliance on side channels. Aztec apps can use offchain communication if they wish, but the default mechanism is note tagging. ## Note tagging[​](#note-tagging "Direct link to Note tagging") Aztec uses note tagging as its default discovery mechanism. When creating a note, the sender *tags* the log with a value that only the sender and recipient can identify. This allows recipients to efficiently query for relevant logs without downloading and attempting to decrypt everything. ### How it works[​](#how-it-works "Direct link to How it works") #### Every log has a tag[​](#every-log-has-a-tag "Direct link to Every log has a tag") In Aztec, each emitted log is an array of fields, e.g. `[tag, x, y, z]`. The first field is a *tag* used to index and identify logs. The Aztec node indexes logs by their tag and exposes an API (`getPrivateLogsByTags()`) that retrieves logs matching specific tags. #### Tag derivation[​](#tag-derivation "Direct link to Tag derivation") Every tag is derived the same way: `poseidon2(secret, index)`. What varies is how the sender and recipient come to share `secret`. This is the [tagging secret strategy](/developers/testnet/docs/aztec-nr/framework-description/note_delivery.md#tagging-secret-strategy), chosen by the wallet. ##### Address-derived secret[​](#address-derived-secret "Direct link to Address-derived secret") When the secret is derived from addresses, the sender and recipient compute a value specific to their pair and the contract through a layered hashing process: The derivation has four stages: 1. **Shared secret**: The sender and recipient compute the same shared secret via Diffie-Hellman key exchange on the Grumpkin curve. Each party uses their [incoming viewing secret key](/developers/testnet/docs/foundational-topics/accounts/keys.md#incoming-viewing-keys) (`ivsk`) and the other party's address point: `S = (preaddress + ivsk) × AddressPoint`. 2. **App tagging secret**: The shared secret is hashed with the contract address to produce a per-contract secret: `poseidon2(S.x, S.y, contract_address)`. This ensures tags from different contracts cannot be linked. 3. **Directional secret**: The app secret is hashed with the recipient address: `poseidon2(appSecret, recipient)`. This makes the secret asymmetric — tags from Alice to Bob differ from tags from Bob to Alice. 4. **Tag**: The directional secret is hashed with an index (a counter that increments for each log the sender emits to this recipient in this contract): `poseidon2(directionalSecret, index)`. When the log is emitted, the protocol kernel **siloes** the tag with the contract address before it appears onchain. This siloed tag is what the node stores and indexes. Both the sender and recipient can independently compute the siloed tags and use them to query the node. #### The sender in note tagging[​](#the-sender-in-note-tagging "Direct link to The sender in note tagging") The "sender" in note tagging is **not necessarily the transaction sender**. It's the **sender for tags**, which the wallet supplies as a default (typically the originating account address). Contracts can override this at message delivery by using `with_sender`, for both constrained and unconstrained delivery, e.g. `MessageDelivery::onchain_constrained().with_sender(address)`. #### Registering known senders[​](#registering-known-senders "Direct link to Registering known senders") To discover notes from a particular sender, the recipient's PXE must know the sender's address in advance so it can compute the shared tagging secret. Register senders using the wallet API: ``` // Register a sender so your PXE can discover notes from them await wallet.registerSender(senderAddress); ``` Notes sent to yourself are always discoverable — the PXE automatically adds all local accounts as implicit senders. ### The sync process[​](#the-sync-process "Direct link to The sync process") The `#[aztec]` macro automatically injects an unconstrained `sync_state` utility function into every contract. This function is invoked by the PXE during note syncing to orchestrate discovery via oracles; manual execution is forbidden by the PXE to prevent inconsistencies. The process works as follows: 1. **Fetch tagged logs**: The contract calls the `fetchTaggedLogs` oracle. The PXE computes tags for every (sender, recipient) pair it knows about, queries the node for matching logs, and returns them to the contract. 2. **Decrypt**: For each log, the contract strips the tag and attempts AES-128 decryption using a symmetric key derived from the recipient's private key (via ECDH). Logs that don't decrypt are silently discarded (they were not intended for this recipient). 3. **Parse message type**: Successfully decrypted messages are dispatched by type — private notes, partial notes, or private events. 4. **Nonce discovery** (for notes): To confirm a decrypted note is valid, the system must match it against the unique note hashes emitted in the same transaction. It iterates the note hashes in the transaction, computes candidate nonces using `compute_note_hash_nonce(first_nullifier, note_index)` (a domain-separated Poseidon2 hash), and checks whether recomputing the unique note hash with each candidate nonce produces a match. A match confirms the note was emitted in this transaction and provides the nonce needed to later nullify it. (Note hash tree inclusion is validated separately.) 5. **Store**: Validated notes are added to the PXE database, making them available for use in future transactions. Developers don't need to implement any of this manually — the `#[aztec]` macro handles it. However, since the discovery logic lives in contract code (called via oracles to the PXE), users can customize or replace the discovery mechanism to suit their needs. #### The sliding window algorithm[​](#the-sliding-window-algorithm "Direct link to The sliding window algorithm") The PXE doesn't scan all possible tag indexes — it uses a window-based approach to efficiently find new logs: * It tracks the **highest aged index**: the highest tag index seen in a block at least 24 hours old (`MAX_TX_LIFETIME`). Once a block is this old, no new transactions can reference it as an anchor, so no new logs can appear at or below that index. * It tracks the **highest finalized index**: the highest tag index seen in any finalized block. * It scans from the aged index to 20 indexes beyond the finalized index, covering both recent and in-flight logs. This means there's a practical limit on how many logs a single sender can emit to the same recipient in the same contract within a short time period. For most applications this limit is not a concern. ### Limitations and solutions[​](#limitations-and-solutions "Direct link to Limitations and solutions") #### You cannot receive address-derived tagged notes from an unknown sender[​](#you-cannot-receive-address-derived-tagged-notes-from-an-unknown-sender "Direct link to You cannot receive address-derived tagged notes from an unknown sender") When the tag's secret is [address-derived](#address-derived-secret), you cannot compute it without knowing the sender's address, so you cannot discover those notes from a sender you haven't registered. This is a limitation of address-derived tagging, not of tagging in general. There are three broad families of solutions to this problem: **a) Brute force search** - Scan every single log and test if it decrypts. This has obvious performance issues as the network grows and becomes prohibitively expensive. **b) Tagging with known sender** - You know who will send you messages and search for those specifically. This is very fast and allows you to remove senders who spam you. However, it cannot be constrained, i.e., it cannot guarantee that the recipient will find the message. It also requires registering each sender's address in advance with `wallet.registerSender(address)`, so you must learn that address first. **c) Tagging with a handshake** - The sender and recipient execute a handshake to agree on a tagging secret, after which regular tagging works, so the recipient can discover messages without having registered the sender in advance. A handshake can be interactive (the two coordinate offchain) or non-interactive (published onchain, which needs no prior coordination but reveals a sender has done a handshake with the recipient). The wallet is the one that determines the type of handshake to use (see [tagging secret strategy](/developers/testnet/docs/aztec-nr/framework-description/note_delivery.md#tagging-secret-strategy)). See the [Note Delivery](/developers/testnet/docs/aztec-nr/framework-description/note_delivery.md) documentation for more details on how the sender is used when delivering notes. ## Advanced cryptography techniques[​](#advanced-cryptography-techniques "Direct link to Advanced cryptography techniques") Beyond the tagging system described above, there are more advanced cryptographic techniques for note discovery: * **Oblivious message retrieval (OMR)**: Allows retrieving messages without the server knowing which messages were accessed * **Private information retrieval (PIR)**: Enables querying a database without revealing which records you're interested in These techniques would solve a privacy leak that exists with the current tagging system: when your PXE queries an Aztec node for logs with specific tags, the node can observe your IP address and correlate it with which tags (and therefore which transactions) you're interested in. Even though the logs are encrypted, this network-level metadata can leak information about your activity. OMR and PIR would eliminate this issue by allowing you to retrieve your logs without the node knowing which ones you requested. However, these methods are currently impractical in production due to computational costs. They represent a long-term goal for achieving stronger privacy guarantees. --- # Storage Slots Storage slots in Aztec serve a similar purpose to Ethereum—they identify where contract state is stored. However, Aztec handles public and private state differently to maintain privacy guarantees while preventing conflicts between contracts. note In the formulas below, `H()` represents a hash function (specifically poseidon2 in the protocol). ## Public State Slots[​](#public-state-slots "Direct link to Public State Slots") As described in [State Model](/developers/testnet/docs/foundational-topics/state_management.md), Aztec public state behaves similarly to public state on Ethereum from a developer's perspective. Behind the scenes, however, the storage is managed differently. Public state uses a single large sparse tree, so we silo slots by hashing them with the contract address: ``` siloed_storage_slot = H(contract_address, storage_slot) ``` You can think of `storage_slot` as the logical position in contract storage, while `siloed_storage_slot` identifies the actual position in the global tree. This siloing is performed by the [kernel circuits](/developers/testnet/docs/foundational-topics/advanced/circuits/private_kernel.md). For structs and arrays, logical storage slots are computed similarly to Ethereum (e.g., a struct with 3 fields uses 3 consecutive logical slots). However, since siloed slots are hashes, the actual tree positions are not consecutive. ## Private State Slots[​](#private-state-slots "Direct link to Private State Slots") Private storage works differently. As described in [State Model](/developers/testnet/docs/foundational-topics/state_management.md), private state is stored as encrypted logs with corresponding commitments in the note hash tree—an append-only structure where each leaf is a note hash. Notes are never updated or deleted; instead, a nullifier is emitted to invalidate a note. This append-only design prevents information leakage that would occur if we updated specific storage slots, even with encrypted values. Because of this, storage slots don't exist in the traditional sense for private state. The note hash tree leaves are simply commitments to note content. Nevertheless, the concept of a storage slot remains useful for application logic. It allows us to reason about distinct pieces of data—for example, ensuring that one account's balance cannot be confused with another's, or with the total supply. ### How Storage Slots Work in Private State[​](#how-storage-slots-work-in-private-state "Direct link to How Storage Slots Work in Private State") Storage slots are included as part of the note hash computation, logically linking all notes that belong to the same slot. For a token balance, this means the balance equals the sum of all non-nullified notes sharing the same storage slot—similar to how a physical wallet's balance is the sum of the bills inside it. The note hash computation includes the storage slot along with other note data (owner, randomness, and note-specific values). This happens in the application circuit. The private state variable wrappers in Aztec.nr (`PrivateSet`, `PrivateMutable`, etc.) handle this automatically. When reading notes, the application circuit constrains which storage slot the notes must belong to, ensuring notes from different slots cannot be mixed. ### Contract Address Siloing[​](#contract-address-siloing "Direct link to Contract Address Siloing") To ensure contracts can only modify their own storage, the kernel circuit performs a second siloing step: ``` siloed_note_hash = H(contract_address, note_hash) ``` This forces all note hashes to be scoped to the contract that created them. The kernel then makes each note hash unique by incorporating a nonce derived from the transaction: ``` unique_note_hash = H(note_nonce, siloed_note_hash) ``` This `unique_note_hash` is what gets inserted into the note hash tree. info Nullifiers are also siloed by contract address at the kernel level to prevent collisions across contracts. ### Privacy Implications[​](#privacy-implications "Direct link to Privacy Implications") With this design, knowing a storage slot is not sufficient to determine what data it contains—unlike public state where the slot directly maps to a value. The note hash tree only contains commitments, and the storage slot is just one component mixed into those commitments. This is a key property that enables private state in Aztec. ## Further Reading[​](#further-reading "Direct link to Further Reading") * [State Model](/developers/testnet/docs/foundational-topics/state_management.md) - Overview of public and private state in Aztec * [Private Kernel Circuits](/developers/testnet/docs/foundational-topics/advanced/circuits/private_kernel.md) - How siloing is enforced at the protocol level --- # Call Types ## What is a Call[​](#what-is-a-call "Direct link to What is a Call") We say that a smart contract is called when one of its functions is invoked and its code is run. This means there'll be: * a caller * arguments * return values * a call status (successful or failed) There are multiple types of calls, and some of the naming can make things **very** confusing. This page lists the different call types and execution modes, pointing out key differences between them. A key property of Aztec calls is that contracts can call each other privately, keeping even the call stack itself private. This two-minute explainer covers the idea before we get into the details (find more on the [video lessons](/developers/testnet/docs/resources/video_lessons.md) page): [What is Private Composability? An Aztec Explainer](https://www.youtube-nocookie.com/embed/idxRuGQnQKs) ## Ethereum Call Types[​](#ethereum-call-types "Direct link to Ethereum Call Types") Aztec's design is heavily influenced by Ethereum, and many APIs and concepts are similar. This section provides background on Ethereum call types for context. If you're already familiar with Ethereum, you can skip to [Aztec Call Types](#aztec-call-types). Ethereum background (click to expand) Broadly speaking, Ethereum contracts can be thought of as executing as a result of three different things: running certain EVM opcodes, running Solidity code (which compiles to EVM opcodes), or via the node JSON-RPC interface (e.g. when executing transactions). ### EVM[​](#evm "Direct link to EVM") Certain opcodes allow contracts to make calls to other contracts, each with different semantics. We're particularly interested in `CALL` and `STATICCALL`, and how those relate to contract programming languages and client APIs. #### `CALL`[​](#call "Direct link to call") This is the most common and basic type of call. It grants execution control to the caller until it eventually returns. No special semantics are in play here. Most Ethereum transactions spend the majority of their time in `CALL` contexts. #### `STATICCALL`[​](#staticcall "Direct link to staticcall") This behaves almost exactly the same as `CALL`, with one key difference: any state-changing operations are forbidden and will immediately cause the call to fail. This includes writing to storage, emitting logs, or deploying new contracts. This call is used to query state on an external contract, e.g. to get data from a price oracle, check for access control permissions, etc. #### Others[​](#others "Direct link to Others") The `CREATE` and `CREATE2` opcodes (for contract deployment) also result in something similar to a `CALL` context, but all that's special about them has to do with how deployments work. `DELEGATECALL` (and `CALLCODE`) are somewhat complicated to understand but don't have any Aztec equivalents, so they are not worth covering. ### Solidity[​](#solidity "Direct link to Solidity") Solidity (and other contract programming languages such as Vyper) compile down to EVM opcodes, but it is useful to understand how they map language concepts to the different call types. #### Mutating External Functions[​](#mutating-external-functions "Direct link to Mutating External Functions") These are functions marked `payable` (which can receive ETH, which is a state change) or with no mutability declaration (sometimes called `nonpayable`). When one of these functions is called on a contract, the `CALL` opcode is emitted, meaning the callee can perform state changes, make further `CALL`s, etc. It is also possible to call such a function with `STATICCALL` manually (e.g. using assembly), but the execution will fail as soon as a state-changing opcode is executed. #### `view`[​](#view "Direct link to view") An external function marked `view` will not be able to mutate state (write to storage, etc.), it can only *view* the state. Solidity will emit the `STATICCALL` opcode when calling these functions, since its restrictions provide added safety to the caller (e.g. no risk of reentrancy). Note that it is entirely possible to use `CALL` to call a `view` function, and the result will be the exact same as if `STATICCALL` had been used. The reason why `STATICCALL` exists is so that *untrusted or unknown* contracts can be called while still being able to reason about correctness. From the [EIP](https://eips.ethereum.org/EIPS/eip-214): > '`STATICCALL` adds a way to call other contracts and restrict what they can do in the simplest way. It can be safely assumed that the state of all accounts is the same before and after a static call.' ### JSON-RPC[​](#json-rpc "Direct link to JSON-RPC") From outside the EVM, calls to contracts are made via [JSON-RPC](https://ethereum.org/en/developers/docs/apis/json-rpc/) methods, typically from some client library that is aware of contract ABIs, such as [ethers.js](https://docs.ethers.org/v5) or [viem](https://viem.sh/). #### `eth_sendTransaction`[​](#eth_sendtransaction "Direct link to eth_sendtransaction") This method is how transactions are sent to a node to get them to be broadcast and eventually included in a block. The specified `to` address will be called in a `CALL` context, with some notable properties: * there are no return values, even if the contract function invoked does return some data * there is no explicit caller: it is instead derived from a provided signature Some client libraries choose to automatically issue `eth_sendTransaction` when calling functions from a contract ABI that are not marked as `view` - [ethers is a good example](https://docs.ethers.org/v5/getting-started/#getting-started--writing). Notably, this means that any return value is lost and not available to the calling client - the library typically returns a transaction receipt instead. If the return value is required, the only option is to simulate the call using `eth_call`. Note that it is possible to call non state-changing functions (i.e. `view`) with `eth_sendTransaction` - this is always meaningless. What transactions do is change the blockchain state, so all calling such a function achieves is for the caller to lose funds by paying for gas fees. The sole purpose of a `view` function is to return data, and `eth_sendTransaction` does not make the return value available. #### `eth_call`[​](#eth_call "Direct link to eth_call") This method is the largest culprit of confusion around calls, but unfortunately requires understanding of all previous concepts in order to be explained. Its name is also quite unhelpful. What `eth_call` does is simulate a transaction (a call to a contract) given the current blockchain state. The behavior will be the exact same as `eth_sendTransaction`, except: * no actual transaction will be created * while gas *will* be measured, there'll be no transaction fees of any kind * no signature is required: the `from` address is passed directly, and can be set to any value (even if the private key is unknown, or if they are contract addresses!) * the return value of the called contract is available `eth_call` is typically used for one of the following: * query blockchain data, e.g. read token balances * preview the state changes produced by a transaction, e.g. the transaction cost, token balance changes, etc Because some libraries ([such as ethers](https://docs.ethers.org/v5/getting-started/#getting-started--reading)) automatically use `eth_call` for `view` functions (which when called via Solidity result in the `STATICCALL` opcode), these concepts can be hard to tell apart. The following bears repeating: **an `eth_call`'s call context is the same as `eth_sendTransaction`, and it is a `CALL` context, not `STATICCALL`.** ## Aztec Call Types[​](#aztec-call-types "Direct link to Aztec Call Types") While Ethereum contracts are defined by bytecode that runs on the EVM, Aztec contracts have multiple modes of execution depending on the function that is invoked. This section covers the main ways contracts can be interacted with, drawing analogies to Ethereum call types where applicable. ### Quick Reference[​](#quick-reference "Direct link to Quick Reference") | Execution Mode | Annotation | Runs On | State Access | Use Case | | -------------- | ------------------------ | --------------- | --------------------- | -------------------------------------------- | | **Private** | `#[external("private")]` | User's device | Private state (notes) | Confidential transactions, private transfers | | **Public** | `#[external("public")]` | Sequencer | Public state | Token balances, access control checks | | **Utility** | `#[external("utility")]` | Offchain client | Both (unconstrained) | Read-only queries, frontend data fetching | ### Private Execution[​](#private-execution "Direct link to Private Execution") Contract functions marked with `#[external("private")]` can only be called privately, and as such 'run' in the user's device. Since they're circuits, their 'execution' is actually the generation of a zk-SNARK proof that'll later be sent to the sequencer for verification. #### Private Calls[​](#private-calls "Direct link to Private Calls") Private functions from other contracts can be called either regularly or statically by using `self.call()` and `self.view()`. They will also be 'executed' (i.e. proved) in the user's device, and `self.view()` will fail if any state changes are attempted (like the EVM's `STATICCALL`). private\_call ``` let _ = self.call(Token::at(stable_coin).burn_private(from, amount, authwit_nonce)); ``` > [Source code: noir-projects/noir-contracts/contracts/app/lending\_contract/src/main.nr#L218-L220](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/lending_contract/src/main.nr#L218-L220) Unlike the EVM however, private execution doesn't revert in the traditional way: in case of error (e.g. a failed assertion, a state changing operation in a static context, etc.) the proof generation simply fails and no transaction request is generated, spending no network gas or user funds. #### Public Calls[​](#public-calls "Direct link to Public Calls") Since public execution can only be performed by the sequencer, public functions cannot be executed in a private context. It is possible however to *enqueue* a public function call during private execution, requesting the sequencer to run it during inclusion of the transaction. It will be [executed in public](#public-execution) normally, including the possibility to enqueue static public calls. Since the public call is made asynchronously, any return values or side effects are not available during private execution. If the public function fails once executed, the entire transaction is reverted including state changes caused by the private part, such as new notes or nullifiers. Note that this does result in gas being spent, like in the case of the EVM. enqueue\_public ``` self.enqueue_self._deposit(AztecAddress::from_field(on_behalf_of), amount, collateral_asset); ``` > [Source code: noir-projects/noir-contracts/contracts/app/lending\_contract/src/main.nr#L104-L106](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/lending_contract/src/main.nr#L104-L106) It is also possible to create public functions that can *only* be invoked by privately enqueueing a call from the same contract, which can be very useful to update public state after private execution (e.g. update a token's supply after privately minting). This is achieved by annotating functions with `#[only_self]`. A common pattern is to enqueue public calls to check some validity condition on public state, e.g. that a deadline has not expired or that some public value is set. enqueueing ``` let selector = comptime { FunctionSelector::from_signature("check_block_number(u8,u32)") }; PublicStaticCall::<18, 2, ()>::new( STANDARD_PUBLIC_CHECKS_ADDRESS, selector, "check_block_number", [operation as Field, value as Field], ) .enqueue_view_incognito(context); ``` > [Source code: noir-projects/aztec-nr/aztec/src/public\_checks.nr#L29-L38](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/aztec-nr/aztec/src/public_checks.nr#L29-L38) Note that this reveals what public function is being called on what contract, and perhaps more importantly which contract enqueued the call during private execution. To prevent this you can enqueue a call to a public function using `self.enqueue_incognito` that behaves the same as `self.enqueue` but conceals the message sender. To address this, we have introduced a `PublicChecks` contract that can be used to perform common checks, such as verifying the timestamp or block number. By having these checks on a contract shared between apps the privacy set increases. An example of how a deadline can be checked using the `PublicChecks` contract follows: call-check-deadline ``` privately_check_timestamp(Comparator.LT, config.deadline, self.context); ``` > [Source code: noir-projects/noir-contracts/contracts/app/crowdfunding\_contract/src/main.nr#L47-L49](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/crowdfunding_contract/src/main.nr#L47-L49) `privately_check_timestamp` and `privately_check_block_number` are helper functions around the call to the `PublicChecks` contract: helper\_public\_checks\_functions ``` /// Asserts that the current timestamp in the enqueued public call enqueued by `check_timestamp` satisfies /// the `operation` with respect to the `value. Preserves privacy by performing the check via the public checks /// contract. /// This conceals an address of the calling contract by setting `context.msg_sender` to the public checks contract /// address. pub fn privately_check_timestamp(operation: u8, value: u64, context: &mut PrivateContext) { let selector = comptime { FunctionSelector::from_signature("check_timestamp(u8,u64)") }; PublicStaticCall::<15, 2, ()>::new( STANDARD_PUBLIC_CHECKS_ADDRESS, selector, "check_timestamp", [operation as Field, value as Field], ) .enqueue_view_incognito(context); } /// Asserts that the current block number in the enqueued public call enqueued by `check_block_number` satisfies /// the `operation` with respect to the `value. Preserves privacy by performing the check via the public checks /// contract. /// This conceals an address of the calling contract by setting `context.msg_sender` to the public checks contract /// address. pub fn privately_check_block_number(operation: u8, value: u32, context: &mut PrivateContext) { let selector = comptime { FunctionSelector::from_signature("check_block_number(u8,u32)") }; PublicStaticCall::<18, 2, ()>::new( STANDARD_PUBLIC_CHECKS_ADDRESS, selector, "check_block_number", [operation as Field, value as Field], ) .enqueue_view_incognito(context); } ``` > [Source code: noir-projects/aztec-nr/aztec/src/public\_checks.nr#L6-L40](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/aztec-nr/aztec/src/public_checks.nr#L6-L40) This is what the implementation of the check timestamp functionality looks like: check\_timestamp ``` /// Asserts that the current timestamp satisfies the `operation` with respect /// to the `value. /// NOTE: signature is hardcoded in `aztec-nr/aztec/src/public_checks.nr`; keep in sync. #[external("public")] #[view] fn check_timestamp(operation: u8, value: u64) { let lhs_field = self.context.timestamp() as Field; let rhs_field = value as Field; assert(compare(lhs_field, operation, rhs_field), "Timestamp mismatch."); } ``` > [Source code: noir-projects/noir-contracts/contracts/standard/public\_checks\_contract/src/main.nr#L14-L25](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/standard/public_checks_contract/src/main.nr#L14-L25) note The `PublicChecks` contract is not part of the [aztec-nr repository](https://github.com/AztecProtocol/aztec-nr). To add it as a dependency, point to the aztec-packages repository: ``` [dependencies] public_checks = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v5.0.0-rc.2", directory = "noir-projects/noir-contracts/contracts/standard/public_checks_contract" } ``` Even with the public checks contract, achieving good privacy is hard. For example, if the value being checked against is unique and stored in the contract's public storage, it's then simple to find private transactions that are using that value in the enqueued public reads, and therefore link them to this contract. For this reason it is encouraged to try to avoid public function calls and instead privately read [Delayed Public Mutable](/developers/testnet/docs/aztec-nr/framework-description/state_variables.md#delayedpublicmutable) state when possible. ### Public Execution[​](#public-execution "Direct link to Public Execution") Contract functions marked with `#[external("public")]` can only be called publicly, and are executed by the sequencer. The computation model is very similar to the EVM: all state, parameters, etc. are known to the entire network, and no data is private. Static execution like the EVM's `STATICCALL` is possible too, with similar semantics (state can be accessed but not modified, etc.). note The AVM supports a subset of Noir's cryptographic operations. Signature verification (ECDSA) is not available in public functions. See [AVM Cryptographic Compatibility](/developers/testnet/docs/foundational-topics/advanced/circuits/avm_compatibility.md) for details. Since private calls are always run in a user's device, it is not possible to perform any private execution from a public context. A reasonably good mental model for public execution is that of an EVM in which some work has already been done privately, and all that is known about it is its correctness and side-effects (new notes and nullifiers, enqueued public calls, etc.). A reverted public execution will also revert the private side-effects. Public functions in other contracts can be called both regularly and statically, just like on the EVM. public\_call ``` self.enqueue(Token::at(config.accepted_asset).transfer_in_public( self.msg_sender(), self.address, max_fee, authwit_nonce, )); ``` > [Source code: noir-projects/noir-contracts/contracts/fees/fpc\_contract/src/main.nr#L153-L160](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/fees/fpc_contract/src/main.nr#L153-L160) note Public functions can be called either directly in a public context (as shown above), or asynchronously by enqueuing from a private context (as shown in the [Public Calls](#public-calls) section). ### Utility[​](#utility "Direct link to Utility") Contract functions marked with `#[external("utility")]` cannot be called as part of a transaction. They are only invoked by applications that interact with contracts for: * **State queries**: Reading from both private and public state via an offchain client * **Local state management**: Modifying contract-related PXE state (e.g., processing logs in Aztec.nr) Since utility execution is unconstrained and relies heavily on oracle calls, no guarantees are made on the correctness of results. However, you can verify that the bytecode being executed is correct, since a contract's address includes a commitment to all of its utility functions. ### aztec.js[​](#aztecjs "Direct link to aztec.js") There are two main ways to execute an Aztec contract function using the `aztec.js` library, with close similarities to their [JSON-RPC counterparts](#json-rpc). #### `simulate`[​](#simulate "Direct link to simulate") This is used to get a result out of an execution, either private or public. It creates no transaction and spends no gas. The mental model is fairly close to that of [`eth_call`](#eth_call), in that it can be used to call any type of function, simulate its execution and get a result out of it. `simulate` is also the only way to run [utility functions](#utility). simulate\_function ``` const { result: balance } = await token.methods .balance_of_public(aliceAddress) .simulate({ from: aliceAddress }); console.log(`Alice's token balance: ${balance}`); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L148-L154](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_connection/index.ts#L148-L154) warning No correctness is guaranteed on the result of `simulate`! Correct execution is entirely optional and left up to the client that handles this request. #### `send`[​](#send "Direct link to send") This creates a transaction, generates proofs for private execution, broadcasts the transaction to the network, and returns a receipt. This is how transactions are sent, getting them to be included in blocks and spending gas. It is similar to [`eth_sendTransaction`](#eth_sendtransaction), except it also performs work on the user's device, namely the production of the proof for the private part of the transaction. send\_tx ``` await contract.methods.buy_pack(seed).send({ from: firstPlayer }); ``` > [Source code: yarn-project/end-to-end/src/e2e\_card\_game.test.ts#L128-L130](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/yarn-project/end-to-end/src/e2e_card_game.test.ts#L128-L130) You can also use `send` to check for execution failures in testing contexts by expecting the transaction to throw: local-tx-fails ``` await expect( claimContract.methods.claim(anotherDonationNote, donorAddress).send({ from: unrelatedAddress }), ).rejects.toThrow('confirmed_note.owner == self.msg_sender()'); ``` > [Source code: yarn-project/end-to-end/src/e2e\_crowdfunding\_and\_claim.test.ts#L222-L226](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/yarn-project/end-to-end/src/e2e_crowdfunding_and_claim.test.ts#L222-L226) ## Next Steps[​](#next-steps "Direct link to Next Steps") * [State Management](/developers/testnet/docs/foundational-topics/state_management.md) - Learn how private and public state works in Aztec * [Transactions](/developers/testnet/docs/foundational-topics/transactions.md) - Understand the transaction lifecycle * [Contract Creation](/developers/testnet/docs/foundational-topics/contract_creation.md) - Deploy and interact with contracts * [Declaring Storage](/developers/testnet/docs/aztec-nr/framework-description/state_variables.md) - Define storage in your contracts --- # Contract Deployment In the Aztec protocol, contracts are created as *instances* of contract *classes*. Unlike Ethereum where deployment is binary (deployed or not), Aztec contracts progress through multiple states before they are fully operational. ## Contract Lifecycle Overview[​](#contract-lifecycle-overview "Direct link to Contract Lifecycle Overview") Aztec contracts go through these states: 1. **Contract Class Registration** - Publishing the contract bytecode to the network 2. **Contract Instance Creation** - Computing a deterministic address from class, salt, and initialization parameters 3. **Initialization** - Running the constructor to set up initial state 4. **Public Deployment** - Broadcasting the instance to the network for public function calls 5. **Private Function Broadcasting** - Sharing private function artifacts offchain (optional) Not every contract needs every state. A private-only contract can skip class registration and public deployment entirely. See the [Contract Deployment Quick Reference](/developers/testnet/docs/aztec-nr/contract_readiness_states.md) for a practical guide on which steps your contract needs. ## Contract Classes[​](#contract-classes "Direct link to Contract Classes") A contract class is a collection of state variable declarations, and related private, public and utility functions. Contract classes don't have state, they just define code (storage structure and function logic). A contract class cannot be called; only a contract instance can be called. ### Key Benefits of Contract Classes[​](#key-benefits-of-contract-classes "Direct link to Key Benefits of Contract Classes") Contract classes simplify code reuse by making implementations a first-class citizen in the protocol. With a single class registration, multiple contract instances can be deployed that reference it, reducing deployment costs. Classes also facilitate upgradability by decoupling state from code, making it easier for an instance to switch to different code while retaining its state. ### Structure of a Contract Class[​](#structure-of-a-contract-class "Direct link to Structure of a Contract Class") A contract class includes: * `artifact_hash`: Hash of the contract artifact * `private_functions_root`: Merkle root of the private functions tree * `packed_public_bytecode`: Packed bytecode representation of the AVM bytecode for all public functions The specification of the artifact hash is not enforced by the protocol. It should include commitments to utility functions code and compilation metadata. It is intended to be used by clients to verify that an offchain fetched artifact matches a registered class. ### Contract Class Registration[​](#contract-class-registration "Direct link to Contract Class Registration") A contract class is published by calling a private `publish` function in a canonical `ContractClassRegistry` contract, which emits a registration nullifier. This process guarantees that the public bytecode for a contract class is publicly available, which is required for deploying contract instances. Contract class registration can be skipped if there are no public functions, and the contract will still be usable privately. However, if you have public functions, you must either register the class before deployment or skip public deployment entirely (only private functions will be callable). ## Contract Instances[​](#contract-instances "Direct link to Contract Instances") A deployed contract is effectively an instance of a contract class. It always references a contract class, which determines what code it executes when called. A contract instance has both private and public state, as well as an address that serves as its identifier. ### Structure of a Contract Instance[​](#structure-of-a-contract-instance "Direct link to Structure of a Contract Instance") A contract instance includes: * `salt`: User-generated pseudorandom value for uniqueness * `deployer`: Optional address of the contract deployer. Zero for universal deployment * `original_contract_class_id`: Identifier of the contract class the instance was deployed with. Updating the instance to a new class via the ContractInstanceRegistry does not change this value, since it is part of the address preimage * `initialization_hash`: Hash of the selector and arguments to the constructor * `immutables_hash`: Hash of the contract's compile-time immutable state * `public_keys`: Public keys participating in address derivation (nullifier, incoming viewing, outgoing viewing, tagging, message-signing, and fallback keys). Only the incoming viewing key is held as an elliptic curve point; the other five are held as their `hash_public_key` digests. ### Instance Address[​](#instance-address "Direct link to Instance Address") The address of a contract instance is computed as the hash of the elements in its structure. This computation is deterministic, allowing users to precompute the expected deployment address of their contract, including account contracts. ### Contract Initialization vs. Public Deployment[​](#contract-initialization-vs-public-deployment "Direct link to Contract Initialization vs. Public Deployment") Aztec makes an important distinction between initialization and public deployment: 1. **Initialization**: A contract instance is considered initialized once it emits an initialization nullifier, meaning it can only be initialized once. The default state for any address is uninitialized. A user who knows the preimage of the address can still issue a private call into a function in the contract, as long as that function doesn't assert that the contract has been initialized. 2. **Public Deployment**: A contract instance is considered publicly deployed when it has been broadcast to the network via the `publish_for_public_execution` function in the canonical `ContractInstanceRegistry` contract, which emits a deployment nullifier. All public function calls to an undeployed address fail, since the contract class is not known to the network. ### Initialization[​](#initialization "Direct link to Initialization") Contract constructors are not enshrined in the protocol, but handled at the application circuit level. Constructors are methods used for initializing a contract, either private or public, and contract classes may declare more than a single constructor. They can be declared by the `#[initializer]` macro. You can read more about how to use them on the [defining initializer functions](/developers/testnet/docs/aztec-nr/framework-description/functions/how_to_define_functions.md#define-initializer-functions) page. A contract must ensure: * It is initialized at most once * It is initialized using the method and arguments defined in its address preimage * It is initialized by its deployer (if non-zero) * Functions dependent on initialization cannot be invoked until the contract is initialized Functions in a contract may skip the initialization check. ## Verification of Executed Code[​](#verification-of-executed-code "Direct link to Verification of Executed Code") When a function is called on a contract instance, the protocol circuits verify that the executed code matches what was registered. For private functions, the circuit checks that the function's verification key hash exists in the `private_functions_root` of the contract class. For public functions, the AVM verifies that the bytecode matches the registered `packed_public_bytecode`. This verification ensures that contracts execute the exact code that was published during class registration. ## Genesis Contracts[​](#genesis-contracts "Direct link to Genesis Contracts") The `ContractInstanceRegistry` and `ContractClassRegistry` contracts are protocol contracts that exist from the genesis of the Aztec Network at predefined addresses. They are necessary for deploying other contracts to the network. ## Private Function Broadcasting[​](#private-function-broadcasting "Direct link to Private Function Broadcasting") Private function artifacts can be shared offchain so others can call your private functions. This is optional—callers who already have the artifacts don't need them broadcast. This step is only necessary when you want external parties to interact with your contract's private functions without having obtained the artifacts through other means. ## Proving Contract States[​](#proving-contract-states "Direct link to Proving Contract States") Your contract can verify the deployment or initialization state of other contracts. This is useful for: * Ensuring a dependency contract is deployed before interacting * Access control based on contract state * Conditional logic based on initialization ``` use aztec::history::deployment::{ assert_contract_bytecode_was_not_published_by, assert_contract_bytecode_was_published_by, assert_contract_was_initialized_by, assert_contract_was_not_initialized_by, }; // Prove a contract's bytecode was published by a given block assert_contract_bytecode_was_published_by(block_header, contract_address); // Prove a contract's bytecode was NOT published by a given block assert_contract_bytecode_was_not_published_by(block_header, contract_address); // Prove a contract was initialized by a given block // (init_hash is the contract's initialization hash, obtainable via get_contract_instance) assert_contract_was_initialized_by(block_header, contract_address, init_hash); // Prove a contract was NOT initialized by a given block assert_contract_was_not_initialized_by(block_header, contract_address, init_hash); ``` These functions prove inclusion or non-inclusion of the corresponding nullifiers in the nullifier tree at a given block. ## Further reading[​](#further-reading "Direct link to Further reading") * [Contract Deployment Quick Reference](/developers/testnet/docs/aztec-nr/contract_readiness_states.md) - Practical guide for which deployment steps your contract needs * [Deploying Contracts](/developers/testnet/docs/aztec-js/how_to_deploy_contract.md) - Deploy contracts using TypeScript * [DApp Development Tutorial](/developers/testnet/docs/tutorials/js_tutorials/aztecjs-getting-started.md) - Build a complete application * [Communicating Cross-Chain](/developers/testnet/docs/aztec-nr/framework-description/ethereum_aztec_messaging.md) - Portal contracts and L1/L2 messaging --- # L1-L2 Communication (Portals) In Aztec, *portals* facilitate communication between L1 and L2. Unlike typical L2 solutions that rely on synchronous communication, Aztec's privacy-first design and the way transactions are processed (kernel proofs built on historical data) make direct calls between L1 and L2 impossible while maintaining privacy. Portals solve this by acting as bridges for asynchronous message passing, transmitting messages from public functions in L1 to private functions in L2 and vice versa. ## Objective[​](#objective "Direct link to Objective") The goal is to set up a minimal-complexity mechanism, that will allow a base-layer (L1) and the Aztec Network (L2) to communicate arbitrary messages such that: * L2 functions can `call` L1 functions. * L1 functions can `call` L2 functions. * Messages have minimal impact on rollup block size. ## High Level Overview[​](#high-level-overview "Direct link to High Level Overview") This document will contain communication abstractions that we use to support interaction between *private* functions, *public* functions and Layer 1 portal contracts. Fundamental restrictions for Aztec: * L1 and L2 have very different execution environments. Operations that are cheap on L1 are often expensive on L2 and vice versa. For example, `keccak256` is cheap on L1 but very expensive on L2. * *Private* function calls are fully "prepared" and proven by the user, which provides the kernel proof along with commitments and nullifiers to the sequencer. * *Public* functions altering public state (updatable storage) must be executed at the current "head" of the chain, which only the sequencer can ensure, so these must be executed separately to the *private* functions. * *Private* and *public* functions within Aztec are therefore ordered such that *private* functions are executed first, then *public* functions. * Messages are consumables, and can only be consumed by the recipient. See [Message Boxes](#message-boxes) for more information. With the aforementioned restrictions taken into account, cross-chain messages can be operated in a similar manner to when *public* functions must transmit information to *private* functions. In such a scenario, a "message" is created and conveyed to the recipient for future use. It is worth noting that any call made between different domains (*private, public, cross-chain*) is unilateral in nature. In other words, the caller is unaware of the outcome of the initiated call until told when some later rollup is executed (if at all). This can be regarded as message passing, providing us with a consistent mental model across all domains, which is convenient. As an illustration, suppose a private function adds a cross-chain call. In such a case, the private function would not have knowledge of the result of the cross-chain call within the same rollup (since it has yet to be executed). Similarly to the ordering of private and public functions, we can also reap the benefits of intentionally ordering messages between L1 and L2. When a message is sent from L1 to L2, it has been "emitted" by an action in the past (an L1 interaction), allowing us to add it to the list of consumables at the "beginning" of the block execution. This practical approach means that a message could be consumed in the same block it is included. In a sophisticated setup, rollup n could send an L2 to L1 message that is then consumed on L1, and the response is added already in n+1. However, messages going from L2 to L1 will be added as they are emitted. info Because everything is unilateral and async, application developers must explicitly handle failure cases so users can gracefully recover. Token bridges are a prime example: it would be very inconvenient if funds are locked on one domain but never minted or unlocked on the other. ## Components[​](#components "Direct link to Components") ### Portal[​](#portal "Direct link to Portal") A "portal" refers to the part of an application residing on L1, which is associated with a particular L2 address (the confidential part of the application). It could be a contract or even an EOA on L1. ### Message Boxes[​](#message-boxes "Direct link to Message Boxes") In a logical sense, a Message Box functions as a one-way message passing mechanism with two ends, one residing on each side of the divide, i.e., one component on L1 and another on L2. Essentially, these boxes are utilized to transmit messages between L1 and L2 via the rollup contract. The boxes can be envisaged as multi-sets that enable the same message to be inserted numerous times, a feature that is necessary to accommodate scenarios where, for instance, "deposit 10 eth to A" is required multiple times. The diagram below provides a detailed illustration of how one can perceive a message box in a logical context. ![](/assets/ideal-img/com-abs-5.017c953.640.png) * Here, a `sender` will insert a message into the `pending` set, the specific constraints of the actions depend on the implementation domain, but for now, say that anyone can insert into the pending set. * At some point, a rollup will be executed, in this step messages are "moved" from pending on Domain A, to ready on Domain B. Note that consuming the message is "pulling & deleting" (or nullifying). The action is atomic, so a message that is consumed from the pending set MUST be added to the ready set, or the state transition should fail. A further constraint is that the `sender` and `recipient` version fields must match the version of their respective inbox/outbox contracts. * When the message has been added to the ready set, the `recipient` can consume the message as part of a function call. A difference when compared to other cross-chain setups, is that Aztec is "pulling" messages, and that the message doesn't need to be calldata for a function call. For other rollups, execution is happening FROM the "message bridge", which then calls the L1 contract. For Aztec, you call the L1 contract, and it should then consume messages from the message box. Why pull instead of push? Privacy. Pushing would require full calldata, which would publicly expose inputs to private functions since L1 → L2 transaction calldata is committed on L1. By instead pulling, we can have the "message" be something that is derived from the arguments instead. This way, a private function to perform second half of a deposit, leaks the "value" deposited and "who" made the deposit (as this is done on L1), but the new owner can be hidden on L2. To support messages in both directions we require two of these message boxes (one in each direction). However, due to the limitations of each domain, the message box for sending messages into the rollup and sending messages out are not fully symmetrical. In reality, the setup looks closer to the following: ![](/assets/ideal-img/com-abs-6.0a38c8b.640.png) info The L2 -> L1 pending messages set only exist logically, as it is practically unnecessary. For anything to happen to the L2 state (e.g., update the pending messages), the state will be updated on L1, meaning that we could just as well insert the messages directly into the ready set. ### Rollup Contract[​](#rollup-contract "Direct link to Rollup Contract") The rollup contract has a few very important responsibilities. The contract must keep track of the *L2 rollup state root*, perform *state transitions* and ensure that the data is available for anyone else to synchronize to the current state. To ensure that *state transitions* are performed correctly, the contract will derive public inputs for the **rollup circuit** based on the input data, and then use a *verifier* contract to validate that inputs correctly transition the current state to the next. All data needed for the public inputs to the circuit must be from the rollup block, ensuring that the block is available. For a valid proof, the *rollup state root* is updated and it will emit an *event* to make it easy for anyone to find the data. As part of *state transitions* where cross-chain messages are included, the contract must "move" messages along the way, e.g., from "pending" to "ready". ### Kernel Circuit[​](#kernel-circuit "Direct link to Kernel Circuit") For L2 to L1 messages, the kernel circuit's public inputs contain a dynamic array of messages, limited to `MAX_L2_TO_L1_MSGS_PER_TX` to ensure transactions can always be included. The circuit scopes each message to the contract address that emitted it, ensuring the sender cannot be spoofed. When consuming L1 to L2 messages, user contracts call `process_l1_to_l2_message()` which verifies the message exists in the L1 to L2 message tree and creates a nullifier to prevent double-consumption. The kernel circuit accumulates these nullifiers in its public inputs. ### Rollup Circuit[​](#rollup-circuit "Direct link to Rollup Circuit") The rollup circuit must ensure that, provided two states S and S′ and the rollup block B, applying B to S using the transition function must give us S′, e.g., T(S,B)↦S′. If this is not the case, the constraints are not satisfied. For cross-chain messages, this means inserting and nullifying L1 → L2 messages in the trees and publishing L2 → L1 messages on chain. ### Messages[​](#messages "Direct link to Messages") While a message could theoretically be arbitrarily long, we want to limit the cost of the insertion on L1 as much as possible. Therefore, we allow the users to send 32 bytes of "content" between L1 and L2. If 32 suffices, no packing required. If the 32 is too "small" for the message directly, the sender should simply pass along a `sha256(content)` instead of the content directly (note that this hash should fit in a field element which is \~254 bits. More info on this below). The content can then either be emitted as an event on L2 or kept by the sender, who should then be the only entity that can "unpack" the message. In this manner, there is some way to "unpack" the content on the receiving domain. The message that is passed along requires the `sender/recipient` pair to be communicated as well (we need to know who should receive the message and be able to check). By having the pending messages be a contract on L1, we can ensure that the `sender = msg.sender` and let only `content` and `recipient` be provided by the caller. We only store the commitment (`sha256(LxToLyMsg)`) on chain or in the trees, so we only need to update a single storage slot per message. See the [Data Structures](/developers/testnet/docs/foundational-topics/ethereum-aztec-messaging/data_structures.md) page for the full message structure definitions (`L1Actor`, `L2Actor`, `L1ToL2Msg`, `L2ToL1Msg`). info The `bytes32` elements for `content` and `secretHash` hold values that must fit in a field element (\~ 254 bits). info The nullifier computation should include the index of the message in the message tree to ensure that it is possible to send duplicate messages (e.g., 2 x deposit of 500 dai to the same account). To make it possible to hide when a specific message is consumed, the `L1ToL2Msg` is extended with a `secretHash` field, where the `secretPreimage` is used as part of the nullifier computation. This way, it is not possible for someone just seeing the `L1ToL2Msg` on L1 to know when it is consumed on L2. ## Combined Architecture[​](#combined-architecture "Direct link to Combined Architecture") The following diagram shows the overall architecture, combining the earlier sections. ![](/assets/ideal-img/com-abs-7.6cb1c07.640.png) ## See also[​](#see-also "Direct link to See also") * [Communicating Cross-Chain](/developers/testnet/docs/aztec-nr/framework-description/ethereum_aztec_messaging.md) - Practical guide with code examples for L1-L2 messaging * [Data Structures](/developers/testnet/docs/foundational-topics/ethereum-aztec-messaging/data_structures.md) - Message and actor type definitions * [Inbox](/developers/testnet/docs/foundational-topics/ethereum-aztec-messaging/inbox.md) - L1 contract for sending messages to L2 * [Outbox](/developers/testnet/docs/foundational-topics/ethereum-aztec-messaging/outbox.md) - L1 contract for consuming messages from L2 --- # Data Structures This page documents the Solidity structs used for L1-L2 message passing in the Aztec protocol. **Source**: [DataStructures.sol](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/l1-contracts/src/core/libraries/DataStructures.sol) ## `L1Actor`[​](#l1actor "Direct link to l1actor") An entity on L1, specifying the address and the chainId. Used when specifying a sender or recipient on L1. l1\_actor ``` /** * @notice Actor on L1. * @param actor - The address of the actor * @param chainId - The chainId of the actor */ struct L1Actor { address actor; uint256 chainId; } ``` > [Source code: l1-contracts/src/core/libraries/DataStructures.sol#L11-L22](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/l1-contracts/src/core/libraries/DataStructures.sol#L11-L22) ## `L2Actor`[​](#l2actor "Direct link to l2actor") An entity on L2, specifying the Aztec address and the protocol version. Used when specifying a sender or recipient on L2. l2\_actor ``` /** * @notice Actor on L2. * @param actor - The aztec address of the actor * @param version - Ahe Aztec instance the actor is on */ struct L2Actor { bytes32 actor; uint256 version; } ``` > [Source code: l1-contracts/src/core/libraries/DataStructures.sol#L24-L35](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/l1-contracts/src/core/libraries/DataStructures.sol#L24-L35) ## `L1ToL2Msg`[​](#l1tol2msg "Direct link to l1tol2msg") A message sent from L1 to L2. The `secretHash` field contains the hash of a secret pre-image that must be known to consume the message on L2. Use [`computeSecretHash`](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/yarn-project/stdlib/src/hash/hash.ts) to compute it from a secret. l1\_to\_l2\_msg ``` /** * @notice Struct containing a message from L1 to L2 * @param sender - The sender of the message * @param recipient - The recipient of the message * @param content - The content of the message (application specific) padded to bytes32 or hashed if larger. * @param secretHash - The secret hash of the message (make it possible to hide when a specific message is consumed on * L2). * @param index - Global leaf index on the L1 to L2 messages tree. */ struct L1ToL2Msg { L1Actor sender; L2Actor recipient; bytes32 content; bytes32 secretHash; uint256 index; } ``` > [Source code: l1-contracts/src/core/libraries/DataStructures.sol#L37-L55](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/l1-contracts/src/core/libraries/DataStructures.sol#L37-L55) ## `L2ToL1Msg`[​](#l2tol1msg "Direct link to l2tol1msg") A message sent from L2 to L1. l2\_to\_l1\_msg ``` /** * @notice Struct containing a message from L2 to L1 * @param sender - The sender of the message * @param recipient - The recipient of the message * @param content - The content of the message (application specific) padded to bytes32 or hashed if larger. * @dev Not to be confused with L2ToL1Message in Noir circuits */ struct L2ToL1Msg { DataStructures.L2Actor sender; DataStructures.L1Actor recipient; bytes32 content; } ``` > [Source code: l1-contracts/src/core/libraries/DataStructures.sol#L57-L70](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/l1-contracts/src/core/libraries/DataStructures.sol#L57-L70) ## See also[​](#see-also "Direct link to See also") * [Inbox](/developers/testnet/docs/foundational-topics/ethereum-aztec-messaging/inbox.md) - L1 contract for sending messages to L2 * [Outbox](/developers/testnet/docs/foundational-topics/ethereum-aztec-messaging/outbox.md) - L1 contract for consuming messages from L2 * [Portal messaging overview](/developers/testnet/docs/foundational-topics/ethereum-aztec-messaging.md) - How L1-L2 messaging works --- # Inbox The `Inbox` is a contract deployed on L1 that handles message passing from L1 to L2. **Links**: [Interface](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/l1-contracts/src/core/interfaces/messagebridge/IInbox.sol), [Implementation](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/l1-contracts/src/core/messagebridge/Inbox.sol). ## `sendL2Message()`[​](#sendl2message "Direct link to sendl2message") Sends a message from L1 to L2. send\_l1\_to\_l2\_message ``` /** * @notice Inserts a new message into the Inbox * @dev Emits `MessageSent` with data for easy access by the sequencer * @param _recipient - The recipient of the message * @param _content - The content of the message (application specific) * @param _secretHash - The secret hash of the message (make it possible to hide when a specific message is consumed * on L2) * @return The key of the message in the set and its leaf index in the tree */ function sendL2Message(DataStructures.L2Actor memory _recipient, bytes32 _content, bytes32 _secretHash) external returns (bytes32, uint256); ``` > [Source code: l1-contracts/src/core/interfaces/messagebridge/IInbox.sol#L33-L46](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/l1-contracts/src/core/interfaces/messagebridge/IInbox.sol#L33-L46) | Name | Type | Description | | ----------- | ------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Recipient | [`L2Actor`](/developers/testnet/docs/foundational-topics/ethereum-aztec-messaging/data_structures.md#l2actor) | The recipient of the message. The recipient's version **MUST** match the inbox version and the actor must be an Aztec contract that is **attached** to the contract making this call. If the recipient is not attached to the caller, the message cannot be consumed by it. | | Content | `field` (\~254 bits) | The content of the message. This is the data that will be passed to the recipient. The content is limited to a single field for rollup purposes. If the content is small enough it can be passed directly, otherwise it should be hashed and the hash passed along (you can use our [`Hash`](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/l1-contracts/src/core/libraries/crypto/Hash.sol) utilities with `sha256ToField` functions). | | Secret Hash | `field` (\~254 bits) | A hash of a secret used when consuming the message on L2. Keep this preimage secret to make the consumption private. To consume the message the caller must know the pre-image (the value that was hashed). Use [`computeSecretHash`](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/yarn-project/stdlib/src/hash/hash.ts) to compute it from a secret. | | ReturnValue | `(bytes32, uint256)` | The message hash (used as an identifier) and the leaf index in the tree. | #### Edge cases[​](#edge-cases "Direct link to Edge cases") * Will revert with `Inbox__ActorTooLarge(bytes32 actor)` if the recipient actor is larger than the field size (\~254 bits). * Will revert with `Inbox__VersionMismatch(uint256 expected, uint256 actual)` if the recipient version doesn't match the inbox version. * Will revert with `Inbox__ContentTooLarge(bytes32 content)` if the content is larger than the field size (\~254 bits). * Will revert with `Inbox__SecretHashTooLarge(bytes32 secretHash)` if the secret hash is larger than the field size (\~254 bits). ## View functions[​](#view-functions "Direct link to View functions") These functions allow you to query the current state of the Inbox. | Function | Returns | Description | | ---------------------------- | ------------ | ------------------------------------------------------------------------------------------------ | | `getRoot(uint256)` | `bytes32` | Returns the root of a message tree for a given checkpoint number. | | `getState()` | `InboxState` | Returns the current inbox state (rolling hash, total messages inserted, in-progress checkpoint). | | `getTotalMessagesInserted()` | `uint64` | Returns the total number of messages inserted into the inbox. | | `getInProgress()` | `uint64` | Returns the checkpoint number currently being filled. | | `getFeeAssetPortal()` | `address` | Returns the address of the Fee Juice portal. | ## Internal functions[​](#internal-functions "Direct link to Internal functions") note The following functions are only callable by the Rollup contract and are documented here for completeness. ### `consume()`[​](#consume "Direct link to consume") Consumes a message tree for a given checkpoint number. consume ``` /** * @notice Consumes the current tree, and starts a new one if needed * @dev Only callable by the rollup contract * @dev In the first iteration we return empty tree root because first checkpoint's messages tree is always * empty because there has to be a 1 checkpoint lag to prevent sequencer DOS attacks * * @param _toConsume - The checkpoint number to consume * * @return The root of the consumed tree */ function consume(uint256 _toConsume) external returns (bytes32); ``` > [Source code: l1-contracts/src/core/interfaces/messagebridge/IInbox.sol#L48-L60](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/l1-contracts/src/core/interfaces/messagebridge/IInbox.sol#L48-L60) | Name | Type | Description | | ----------- | --------- | -------------------------------------- | | \_toConsume | `uint256` | The checkpoint number to consume. | | ReturnValue | `bytes32` | The root of the consumed message tree. | #### Edge cases[​](#edge-cases-1 "Direct link to Edge cases") * Will revert with `Inbox__Unauthorized()` if `msg.sender != ROLLUP`. * Will revert with `Inbox__MustBuildBeforeConsume()` if trying to consume a checkpoint that hasn't been built yet. ## Related pages[​](#related-pages "Direct link to Related pages") * [Outbox](/developers/testnet/docs/foundational-topics/ethereum-aztec-messaging/outbox.md) - L2 to L1 message passing * [Data Structures](/developers/testnet/docs/foundational-topics/ethereum-aztec-messaging/data_structures.md) - Message and actor type definitions --- # Outbox The `Outbox` is a contract deployed on L1 that handles message passing from L2 to L1. Portal contracts call `consume()` to receive and process messages that were sent from L2 contracts. The Rollup contract inserts message roots via `insert()` as proofs land. A proof can cover a prefix of an epoch's checkpoints (a partial proof), so an epoch can have several roots, one per number of checkpoints covered. **Links**: [Interface](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol), [Implementation](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/l1-contracts/src/core/messagebridge/Outbox.sol). ## `insert()`[​](#insert "Direct link to insert") Inserts the root of a merkle tree containing all of the L2 to L1 messages in an epoch, after a proof covering the first `_numCheckpointsInEpoch` checkpoints of that epoch lands. This function is only callable by the Rollup contract. outbox\_insert ``` /** * @notice Inserts the root of a merkle tree containing all of the L2 to L1 messages in an epoch * after a proof covering the first `_numCheckpointsInEpoch` checkpoints of that epoch lands. * @dev Only callable by the rollup contract * @dev Emits `RootAdded` upon inserting the root successfully * @dev Successive inserts for the same epoch with larger `_numCheckpointsInEpoch` values do not * disturb earlier entries, so users with witnesses built against an earlier partial proof can still * consume them. * @param _epoch - The epoch in which the L2 to L1 messages reside * @param _numCheckpointsInEpoch - The number of checkpoints the inserting proof covered in this * epoch. Must be in [1, MAX_CHECKPOINTS_PER_EPOCH]. * @param _root - The merkle root of the tree where all the L2 to L1 messages are leaves */ function insert(Epoch _epoch, uint256 _numCheckpointsInEpoch, bytes32 _root) external; ``` > [Source code: l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol#L28-L43](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol#L28-L43) | Name | Type | Description | | ------------------------ | --------- | --------------------------------------------------------------------------------------------------------- | | `_epoch` | `Epoch` | The epoch in which the L2 to L1 messages reside | | `_numCheckpointsInEpoch` | `uint256` | The number of checkpoints the inserting proof covered in this epoch (in `[1, MAX_CHECKPOINTS_PER_EPOCH]`) | | `_root` | `bytes32` | The merkle root of the tree where all the L2 to L1 messages are leaves | ### Edge cases[​](#edge-cases "Direct link to Edge cases") * Will revert with `Outbox__Unauthorized()` if `msg.sender != ROLLUP_CONTRACT`. ## `consume()`[​](#consume "Direct link to consume") Allows a recipient to consume a message from the `Outbox`. outbox\_consume ``` /** * @notice Consumes an entry from the Outbox * @dev Only useable by portals / recipients of messages * @dev Emits `MessageConsumed` when consuming messages * @param _message - The L2 to L1 message * @param _epoch - The epoch that contains the message we want to consume * @param _numCheckpointsInEpoch - The number of checkpoints in the partial proof whose root this * consume verifies against. The caller's witness path must have been built against the epoch tree * padded to that number of real checkpoints. * @param _leafIndex - The index at the level in the epoch message tree where the message is located * @param _path - The sibling path used to prove inclusion of the message, the _path length depends * on the location of the L2 to L1 message in the epoch message tree. */ function consume( DataStructures.L2ToL1Msg calldata _message, Epoch _epoch, uint256 _numCheckpointsInEpoch, uint256 _leafIndex, bytes32[] calldata _path ) external; ``` > [Source code: l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol#L45-L66](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol#L45-L66) | Name | Type | Description | | ------------------------ | ----------- | ----------------------------------------------------------------------------------------------------- | | `_message` | `L2ToL1Msg` | The L2 to L1 message to consume | | `_epoch` | `Epoch` | The epoch that contains the message to consume | | `_numCheckpointsInEpoch` | `uint256` | The number of checkpoints in the partial proof whose root this consume verifies against | | `_leafIndex` | `uint256` | The index inside the merkle tree where the message is located | | `_path` | `bytes32[]` | The sibling path used to prove inclusion of the message (built against the tree for that proof depth) | ### Edge cases[​](#edge-cases-1 "Direct link to Edge cases") * Will revert with `Outbox__PathTooLong()` if the path length is >= 256. * Will revert with `Outbox__LeafIndexOutOfBounds(uint256 leafIndex, uint256 pathLength)` if the leaf index exceeds the tree capacity for the given path length. * Will revert with `Outbox__VersionMismatch(uint256 expected, uint256 actual)` if the message version does not match the Outbox version. * Will revert with `Outbox__InvalidRecipient(address expected, address actual)` if `msg.sender != _message.recipient.actor`. * Will revert with `Outbox__InvalidChainId()` if `block.chainid != _message.recipient.chainId`. * Will revert with `Outbox__NothingToConsumeAtEpoch(Epoch epoch)` if the root for the epoch has not been set. * Will revert with `Outbox__AlreadyNullified(Epoch epoch, uint256 leafIndex)` if the message has already been consumed. * Will revert with `MerkleLib__InvalidIndexForPathLength()` if the leaf index has bits set beyond the tree height. * Will revert with `MerkleLib__InvalidRoot(bytes32 expected, bytes32 actual, bytes32 leaf, uint256 leafIndex)` if the merkle proof verification fails. ## `hasMessageBeenConsumedAtEpoch()`[​](#hasmessagebeenconsumedatepoch "Direct link to hasmessagebeenconsumedatepoch") Checks if an L2 to L1 message in a specific epoch has been consumed. outbox\_has\_message\_been\_consumed\_at\_epoch\_and\_index ``` /** * @notice Checks to see if an L2 to L1 message in a specific epoch has been consumed * @dev - This function does not throw. Out-of-bounds access is considered valid, but will always return false * @param _epoch - The epoch that contains the message we want to check * @param _leafId - The unique id of the message leaf */ function hasMessageBeenConsumedAtEpoch(Epoch _epoch, uint256 _leafId) external view returns (bool); ``` > [Source code: l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol#L68-L76](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol#L68-L76) | Name | Type | Description | | --------- | --------- | -------------------------------------------- | | `_epoch` | `Epoch` | The epoch that contains the message to check | | `_leafId` | `uint256` | The unique id of the message leaf | ### Edge cases[​](#edge-cases-2 "Direct link to Edge cases") * This function does not throw. Out-of-bounds access is considered valid, but will always return false. ## `getRootData()`[​](#getrootdata "Direct link to getrootdata") Returns the merkle root for a given epoch and partial-proof depth. Returns `bytes32(0)` if no proof covering that number of checkpoints has been inserted. ``` function getRootData(Epoch _epoch, uint256 _numCheckpointsInEpoch) external view returns (bytes32); ``` | Name | Type | Description | | ------------------------ | --------- | ------------------------------------------------------------------ | | `_epoch` | `Epoch` | The epoch to fetch the root data for | | `_numCheckpointsInEpoch` | `uint256` | The number of checkpoints in the partial proof whose root to fetch | **Returns**: The merkle root of the L2 to L1 message tree for that epoch and proof depth, or `bytes32(0)` if not proven. There is also `getRoots(Epoch _epoch)`, which returns every root stored for an epoch: slot `i` of the returned array holds the root for `numCheckpointsInEpoch = i + 1`. ## Related pages[​](#related-pages "Direct link to Related pages") * [Inbox](/developers/testnet/docs/foundational-topics/ethereum-aztec-messaging/inbox.md) - L1 to L2 message passing * [Data Structures](/developers/testnet/docs/foundational-topics/ethereum-aztec-messaging/data_structures.md) - Message struct definitions * [L1-L2 Communication (Portals)](/developers/testnet/docs/foundational-topics/ethereum-aztec-messaging.md) - Overview of cross-chain messaging --- # Registry The Registry is a contract deployed on L1 that tracks canonical and historical rollup instances. It allows you to query the current rollup contract and look up prior deployments by version. **Links**: [Interface](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/l1-contracts/src/governance/interfaces/IRegistry.sol), [Implementation](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/l1-contracts/src/governance/Registry.sol). ## `numberOfVersions()`[​](#numberofversions "Direct link to numberofversions") Retrieves the number of versions that have been deployed. registry\_number\_of\_versions ``` function numberOfVersions() external view returns (uint256); ``` > [Source code: l1-contracts/src/governance/interfaces/IRegistry.sol#L25-L27](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/l1-contracts/src/governance/interfaces/IRegistry.sol#L25-L27) | Name | Description | | ----------- | ---------------------------------------------- | | ReturnValue | The number of versions that have been deployed | ## `getCanonicalRollup()`[​](#getcanonicalrollup "Direct link to getcanonicalrollup") Retrieves the current rollup contract. registry\_get\_canonical\_rollup ``` function getCanonicalRollup() external view returns (IHaveVersion); ``` > [Source code: l1-contracts/src/governance/interfaces/IRegistry.sol#L17-L19](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/l1-contracts/src/governance/interfaces/IRegistry.sol#L17-L19) | Name | Description | | ----------- | ------------------ | | ReturnValue | The current rollup | ## `getRollup(uint256 _version)`[​](#getrollupuint256-_version "Direct link to getrollupuint256-_version") Retrieves the rollup contract for a specific version. registry\_get\_rollup ``` function getRollup(uint256 _chainId) external view returns (IHaveVersion); ``` > [Source code: l1-contracts/src/governance/interfaces/IRegistry.sol#L21-L23](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/l1-contracts/src/governance/interfaces/IRegistry.sol#L21-L23) | Name | Description | | ----------- | ------------------------------------ | | `_version` | The version identifier of the rollup | | ReturnValue | The rollup for the specified version | ## Other view functions[​](#other-view-functions "Direct link to Other view functions") | Function | Returns | Description | | ------------------------ | -------------------- | ------------------------------------------------------------------------------------ | | `getVersion(uint256)` | `uint256` | Returns the version number stored at the given index in the historical versions list | | `getGovernance()` | `address` | Returns the governance contract address (owner) | | `getRewardDistributor()` | `IRewardDistributor` | Returns the reward distributor contract | ## Related pages[​](#related-pages "Direct link to Related pages") * [Inbox](/developers/testnet/docs/foundational-topics/ethereum-aztec-messaging/inbox.md) - L1 to L2 message passing * [Outbox](/developers/testnet/docs/foundational-topics/ethereum-aztec-messaging/outbox.md) - L2 to L1 message passing * [L1-L2 Communication (Portals)](/developers/testnet/docs/foundational-topics/ethereum-aztec-messaging.md) - Overview of cross-chain messaging --- # Fees Fees are an integral part of any protocol's design. Proper fee pricing contributes to the longevity and security of a network, and the fee payment mechanisms available inform the types of applications that can be built. In a nutshell, the pricing of transactions transparently accounts for: * L1 costs, including L1 execution of a block, and data availability via blobs, * L2 node operating costs, including proving This is achieved through multiple variables and calculations. ## Terminology[​](#terminology "Direct link to Terminology") Familiar terms from Ethereum mainnet as referred to on the Aztec network: | Ethereum Mainnet | Aztec | Description | | ---------------- | ------------------ | -------------------------------------------------------------- | | gas | mana | Unit measuring computational effort for transaction operations | | fee per gas | Fee Juice per mana | Price per unit of mana | | fee (wei) | Fee Juice | Total fee paid for a transaction | ## What is mana?[​](#what-is-mana "Direct link to What is mana?") Mana is Aztec's unit of computational effort, equivalent to gas on Ethereum. Every transaction consumes mana based on the operations it performs. Mana has two dimensions: * **Data Availability (DA) mana**: Cost of publishing transaction data to the data availability layer * **L2 mana**: Cost of executing the transaction on Aztec The total transaction fee is calculated as: ``` fee = (daMana × feePerDaMana) + (l2Mana × feePerL2Mana) ``` note The SDK and protocol code use "gas" in variable names (e.g., `daGas`, `l2Gas`, `feePerDaGas`, `feePerL2Gas`) rather than "mana". When reading code, `Gas` and mana refer to the same concept. ## What is Fee Juice?[​](#what-is-fee-juice "Direct link to What is Fee Juice?") Fee Juice is the native fee token on Aztec, used to pay for transaction fees. It is bridged Aztec tokens from Ethereum and is **non-transferable** on Aztec - it can only be used to pay fees, not sent between accounts. Aztec borrows ideas from EIP-1559, including congestion multipliers and the ability to specify base and priority fees per mana. ## Factors affecting fees[​](#factors-affecting-fees "Direct link to Factors affecting fees") Other fields used in mana and fee calculations are determined in various ways: * hard-coded constants (eg congestion update fraction) * values assumed constant (eg L1 gas cost of publishing a block, blobs per block) * informed from previous block header and/or L1 rollup contract (eg base fee per mana) * informed via an oracle (eg wei per mana) Most constants are defined by the protocol, while others are part of the rollup contract on L1. ### User-defined settings[​](#user-defined-settings "Direct link to User-defined settings") Users can define the following settings as part of a transaction: gas\_settings\_vars ``` /** Gas usage and fees limits set by the transaction sender for different dimensions and phases. */ export class GasSettings { constructor( public readonly gasLimits: Gas, public readonly teardownGasLimits: Gas, public readonly maxFeesPerGas: GasFees, public readonly maxPriorityFeesPerGas: GasFees, ) {} ``` > [Source code: yarn-project/stdlib/src/gas/gas\_settings.ts#L19-L28](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/yarn-project/stdlib/src/gas/gas_settings.ts#L19-L28) The `Gas` and `GasFees` types each specify Data availability and L2 cost components, so the settings are: * gasLimits: DA and L2 gas limits * teardownGasLimits: DA and L2 gas limits for a txs optional teardown operation * maxFeesPerGas: maximum DA and L2 fees-per-gas * maxPriorityFeesPerGas: maximum priority DA and L2 fees-per-gas ## Fee payment[​](#fee-payment "Direct link to Fee payment") A fee payer obtains Fee Juice by bridging Aztec tokens from Ethereum. The fee payer can be the account itself or a fee-paying contract (FPC), which functions similarly to a paymaster on Ethereum. On Aztec, Fee Juice is non-transferable and only deducted by the protocol to pay for fees. A user can claim bridged Fee Juice and use it to pay for transaction fees in the same transaction. Fee Juice uses an enshrined `FeeJuicePortal` contract on Ethereum for bridging, unlike user-deployed token portals. The underlying cross-chain messaging mechanism is similar to other tokens - for more on this concept see the [Token Bridge Tutorial](/developers/testnet/docs/tutorials/js_tutorials/token_bridge.md) which describes portal contracts and [cross-chain messaging](/developers/testnet/docs/aztec-nr/framework-description/ethereum_aztec_messaging.md). ### Payment methods[​](#payment-methods "Direct link to Payment methods") An account with Fee Juice can pay for its transactions directly. A new account can even pay for its own deployment transaction, provided Fee Juice was bridged to its address before deployment. Alternatively, accounts can use [fee-paying contracts (FPCs)](/developers/testnet/docs/aztec-js/how_to_pay_fees.md#use-fee-payment-contracts) to pay for transactions. An FPC holds its own Fee Juice balance to pay the protocol, and can accept other tokens from users in exchange. The **Sponsored FPC** pays fees unconditionally, enabling free transactions. It is available on testnet, devnet, and local network. On mainnet, ecosystem-deployed FPCs are the practical option: the built-in reference FPC contract does not work on mainnet alpha because custom token class IDs are not included in the default public setup allowlist. As an example, Nethermind's [Private Multi Asset FPC](https://github.com/NethermindEth/aztec-fpc) demonstrates one such design; it accepts multiple tokens and routes fee payments as private notes. ### How FPCs work[​](#how-fpcs-work "Direct link to How FPCs work") An FPC acts as a fee payer on the user's behalf. Simpler FPCs (like the Sponsored FPC) just call `set_as_fee_payer()` with no user payment at all. More sophisticated FPCs accept user tokens in exchange for paying Fee Juice. A common pattern for quote-based FPCs works as follows: 1. **Quote.** The user requests a fee quote from the FPC operator, specifying the token they want to pay with and the estimated gas cost. The operator signs the quote, binding it to the user, asset, amounts, and an expiry. 2. **Authorization.** The user creates an [authentication witness](/developers/testnet/docs/foundational-topics/advanced/authwit.md) authorizing the FPC to transfer tokens from their balance. This is the same authwit mechanism used for any delegated token transfer. 3. **Setup phase (non-revertible).** The FPC's entrypoint runs during the transaction's [setup phase](/developers/testnet/docs/foundational-topics/transactions.md#setup-phase-non-revertible). It verifies the quote, collects the user's payment, declares itself as the fee payer via `set_as_fee_payer()`, and calls `end_setup()` to mark the boundary between non-revertible and revertible execution. Because this runs in the non-revertible phase, the payment is **irrevocably committed** before application logic executes: the user pays regardless of whether the app-logic phase reverts. 4. **App phase (revertible).** The user's actual transaction logic runs here. In the common fee-entrypoint flow the FPC is not involved in this phase, though some FPC designs (such as cold-start flows) perform additional app-phase work like claiming bridged tokens. Setup-phase allowlist Setup-phase execution is restricted by a protocol-level allowlist of permitted public function calls. Public token functions such as `transfer_in_public` and `_increase_public_balance` have been removed from the default allowlist; custom FPCs may only call protocol-contract setup functions (for example those on `AuthRegistry` and `FeeJuice`). See the [migration note](/developers/testnet/docs/resources/migration_notes.md#custom-token-fpcs-removed-from-default-public-setup-allowlist) for details. Key properties for developers integrating with an FPC: * **Authwit scope.** The authwit authorizes a specific action hash (typically covering the transfer amount). A nonce can be included when otherwise-identical actions need to be distinguishable. The authwit is single-use, consumed by a nullifier onchain. * **Token interface.** On networks that use the default setup allowlist, an FPC cannot call arbitrary public token functions during setup (see the callout above). Fee collection for quote-based FPCs therefore typically happens in the private domain: the user privately transfers an agreed amount to the FPC (or its operator) under the authwit from step 2, and the FPC separately declares itself the fee payer during setup using only protocol-contract calls. Any token that implements the standard Aztec token interface with authwit verification is compatible with this private-note pattern. * **Gas estimation first.** The Fee Juice amount in the quote is typically derived from the transaction's gas estimate. Simulate and estimate gas *before* requesting a quote, then pass the estimated cost to the FPC operator. * **Quote expiry.** Quotes are time-bound and single-use. Fetch a fresh quote per transaction. * **Cold-start variant.** Some FPCs offer a cold-start entrypoint where a brand-new account can bridge tokens from L1, claim them on L2, and pay the fee in one transaction, with no prior L2 balance or authwit needed, because the FPC itself claims and distributes the bridged tokens. The user still needs L1 tokens and ETH for the initial bridge transaction. Fee payments themselves can also be made private via a fully private FPC that holds Fee Juice internally and nominates itself as the fee payer during the setup phase, without revealing who initiated the transaction. See [Pay Fees Privately](/developers/testnet/docs/aztec-js/how_to_use_private_fee_juice.md) for how this pattern works and an example implementation. ### Teardown phase[​](#teardown-phase "Direct link to Teardown phase") Transactions can optionally have a "teardown" phase as part of their public execution, during which the "transaction fee" is available to public functions. This is useful to transactions/contracts that need to compute a "refund", e.g. contracts that facilitate fee abstraction. This enables FPCs to calculate the actual transaction cost and refund any overpayment to the user. Not all FPC designs use the teardown phase; some charge a fixed quoted amount with no refund, keeping unused Fee Juice in the FPC's balance for future transactions. ### Operator rewards[​](#operator-rewards "Direct link to Operator rewards") The calculated fee of a transaction is deducted from the fee payer (nominated account or fee-paying contract), then pooled together across transactions, blocks, and epochs. Once an epoch is proven, the total collected fees (minus any burnt congestion amount) are distributed to the provers and block proposers that contributed to the epoch. ## Next steps[​](#next-steps "Direct link to Next steps") For a guide on paying fees programmatically, see [How to Pay Fees](/developers/testnet/docs/aztec-js/how_to_pay_fees.md). --- # Private Execution Environment (PXE) This page describes the Private Execution Environment (PXE, pronounced "pixie"), a client-side library for the execution of private operations. It is a TypeScript library that can be run within Node.js, inside wallet software or a browser. The PXE generates proofs of private function execution, and sends these proofs along with public function execution requests to the sequencer. Private inputs never leave the client-side PXE. The PXE is responsible for: * storing secrets (e.g. encryption keys, notes, tagging secrets for note discovery) and exposing an interface for safely accessing them * orchestrating private function (circuit) execution and proof generation, including implementing [oracles](/developers/testnet/docs/aztec-nr/framework-description/advanced/protocol_oracles.md) needed for transaction execution * syncing users' relevant network state, obtained from an Aztec node * safely handling multiple accounts with siloed data and permissions One PXE can handle data and secrets for multiple accounts, while also providing isolation between them as required. ## System architecture[​](#system-architecture "Direct link to System architecture") Privacy consideration When the PXE queries the node for world state (e.g., to check if a nullifier exists), the node learns which data the user is interested in. This is a known tradeoff. Users can mitigate this by running their own node. ## Components[​](#components "Direct link to Components") ### Contract Function Simulator[​](#contract-function-simulator "Direct link to Contract Function Simulator") An application prompts the user's PXE to execute a transaction (e.g. execute function X with arguments Y from account Z). The application or wallet may handle gas estimation. The contract function simulator handles execution of smart contract functions by simulating transactions. It generates the required data and inputs for these functions, including partial witnesses and public inputs. By default, the simulator runs in a [kernelless mode](/developers/testnet/docs/foundational-topics/pxe/kernelless_simulations.md): it executes the private bytecode and computes the values the private kernels would have produced in TypeScript, instead of running the kernel circuits themselves. This is faster than a full simulation and lets the wallet capture authentication witness requests as offchain effects without prompting the user to sign during simulation. ### Proof Generation[​](#proof-generation "Direct link to Proof Generation") After simulation, the wallet calls `proveTx` on the PXE with all of the data generated during simulation and any [authentication witnesses](/developers/testnet/docs/foundational-topics/advanced/authwit.md) (for allowing contracts to act on behalf of the user's account contract). Once proven, the wallet sends the transaction to the network and sends the transaction hash back to the application. ### Database[​](#database "Direct link to Database") The PXE database stores various types of data locally: * **Notes**: Data representing users' private state. Notes are stored onchain as encrypted logs. Once discovered via [note tagging](/developers/testnet/docs/foundational-topics/advanced/storage/note_discovery.md), notes are decrypted and stored locally in the PXE. * **Authentication Witnesses**: Data used to approve others for executing transactions on your behalf. The PXE provides this data to transactions on-demand during transaction simulation via oracles. * **Capsules**: Per-contract non-volatile local storage for caching computation results and persisting data across transactions. See [Using Capsules](/developers/testnet/docs/aztec-nr/framework-description/advanced/how_to_use_capsules.md) for more details. * **Address Book**: Complete addresses (address + public keys) for registered accounts and known senders. This enables the PXE to sync private logs tagged with registered sender addresses. Note discovery is handled by Aztec contracts, not the PXE. This allows users to customize or update their note discovery mechanism as needed. ### Contract management[​](#contract-management "Direct link to Contract management") Applications can add contract code required for a user to interact with the application to the user's PXE. The PXE will check whether the required contracts have already been registered. There are no getters to check whether a contract has been registered, as this could leak privacy (e.g. a dapp could check whether specific contracts have been registered in a user's PXE and infer information about their interaction history). ### Keystore[​](#keystore "Direct link to Keystore") The keystore securely stores cryptographic keys for registered accounts, including: * **Nullifier keys**: Used to create nullifiers that invalidate notes when spent * **Incoming viewing keys**: Used to decrypt notes sent to the account * **Outgoing viewing keys**: Used to decrypt notes sent by the account * **Tagging keys**: Used for note discovery via the tagging protocol ### Oracles[​](#oracles "Direct link to Oracles") Oracles are pieces of data that are injected into a smart contract function from the client side. Learn more about [how oracles work](/developers/testnet/docs/aztec-nr/framework-description/advanced/protocol_oracles.md). ## Oracle versioning[​](#oracle-versioning "Direct link to Oracle versioning") The set of oracles that the PXE exposes to private and utility functions is versioned, so that contracts can declare which oracles they expect to be available. Every contract compiled with `Aztec.nr` records the oracle version it was built against, and the PXE checks this version before executing any oracle call. The version uses two components, `major.minor`, with the following compatibility rules: * **`major`** must match exactly. A major bump is a breaking change: oracles were removed or their signatures changed, and a PXE on a different major cannot safely run the contract. * **`minor`** indicates additive changes (new oracles). The PXE uses a best-effort approach here: a contract compiled against a higher `minor` than the PXE supports is still allowed to run, and an error is only thrown if the contract actually invokes an oracle the PXE does not know about. In practice, a contract built with a newer Aztec.nr may not use any of the newly added oracles at all, in which case it runs fine on an older PXE. The canonical version constants live in the PXE (`ORACLE_VERSION_MAJOR` / `ORACLE_VERSION_MINOR` in `yarn-project/pxe/src/oracle_version.ts`) and in Aztec.nr (`noir-projects/aztec-nr/aztec/src/oracle/version.nr`). The two are kept in lockstep as part of each release. ### Resolving a version mismatch[​](#resolving-a-version-mismatch "Direct link to Resolving a version mismatch") If you see an error like *"Oracle '…' not found. … The contract was compiled with Aztec.nr oracle version X.Y, but this private execution environment only supports up to A.B"*, the contract uses one or more oracles from a newer Aztec.nr than your PXE supports. To fix it, upgrade the software that ships the PXE (sandbox, wallet, or whatever embeds `@aztec/pxe`) to a release whose Aztec.nr version is at least as new as the one the contract was compiled with. If the PXE reports a version that *should* include every oracle the contract needs but an oracle is still missing, that is a contract bug rather than a version problem and you should likely report it to the app developer. ## For developers[​](#for-developers "Direct link to For developers") To learn how to develop on top of the PXE, refer to these guides: * [Using capsules for local storage](/developers/testnet/docs/aztec-nr/framework-description/advanced/how_to_use_capsules.md) * [Using oracles in smart contracts](/developers/testnet/docs/aztec-nr/framework-description/advanced/protocol_oracles.md) * [Authentication witnesses](/developers/testnet/docs/foundational-topics/advanced/authwit.md) ## Next steps[​](#next-steps "Direct link to Next steps") * [Wallets](/developers/testnet/docs/foundational-topics/wallets.md) - Learn how wallets interact with the PXE * [State management](/developers/testnet/docs/foundational-topics/state_management.md) - Understand how private state is managed * [Note discovery](/developers/testnet/docs/foundational-topics/advanced/storage/note_discovery.md) - Learn how notes are discovered and synced --- # Execution hooks Execution hooks are callbacks that the PXE invokes during client-side simulation when an operation needs a decision from the wallet. They let the wallet apply its own policies before execution proceeds, such as prompting the user, consulting a dynamic allowlist, or inspecting call arguments. All hooks are optional; when a hook is absent, the PXE applies a conservative default: for example it avoids privacy leaks (such as revealing a message's recipient onchain) unless specifically told otherwise. ## Configuring hooks[​](#configuring-hooks "Direct link to Configuring hooks") Pass a `hooks` object when creating the PXE: ``` import { createPXE } from "@aztec/pxe/server"; const pxe = await createPXE(node, config, { hooks: { // Allow calls to a known helper contract, deny everything else. authorizeUtilityCall: async (request) => { return request.target.equals(trustedHelper) ? { authorized: true } : { authorized: false, reason: "Unknown target" }; }, // When no onchain handshake is registered for the recipient, fall back to a non-interactive handshake. resolveTaggingSecretStrategy: async () => ({ type: "non-interactive-handshake" }), }, }); ``` ## `authorizeUtilityCall`[​](#authorizeutilitycall "Direct link to authorizeutilitycall") Called whenever a utility function makes a cross-contract call. A call made by a malicious contract could leak private information, so the hook lets the wallet decide, per call, whether to allow it. A static allowlist would not work here because neither the app nor the wallet can predict ahead of time which contracts will be invoked during execution: permission must be asked after execution has begun. Calls to standard contracts (such as the HandshakeRegistry, which is queried during every contract's sync) bypass this hook and are always authorized. Unlike [authentication witnesses (authwits)](/developers/testnet/docs/aztec-js/how_to_use_authwit.md), the hook is invoked live, while execution is underway. Authwits can be recorded during simulation and signed once at the end, but the PXE cannot predict what a utility call would return, so it must ask before continuing. Most of the time the wallet should answer on its own, for example against a list of audited or previously trusted contracts, to avoid interrupting execution multiple times asking the user for confirmation. ### Deciding what to authorize[​](#deciding-what-to-authorize "Direct link to Deciding what to authorize") Private state is siloed per contract: a utility function runs on your device with access to its own contract's private state, and nothing else. Reading your own balance through a token contract's utility function is fine, and the hook never fires, because no contract boundary is crossed. The risk appears only when one contract's utility function calls into a *different* contract, because that call can reach private state the caller could not read on its own. Consider a single cross-contract operation, reading your token balance, made by two different callers. When a DeFi router calls the token's balance utility to quote you a swap, that is a legitimate cross-contract read, and you want it allowed. When an unknown, possibly malicious contract makes the very same call to snoop your balance, you want it denied. The exposed data is identical in both cases; the only thing that differs is *who is making the call*, which is exactly the decision the hook delegates to the wallet. The wallet makes that decision by inspecting the request, which identifies the caller and target by both address and contract class ID, to judge whether the call is safe to authorize. ### In Noir tests[​](#in-noir-tests "Direct link to In Noir tests") When testing cross-contract utility calls in Noir using `TestEnvironment`, use `with_authorized_utility_call_targets` on your call options: ``` // For private calls: env.call_private_opts( account, CallPrivateOptions::new().with_authorized_utility_call_targets([target_address]), MyContract::at(caller).some_private_fn(), ); // For private view calls: env.view_private_opts( account, ViewPrivateOptions::new().with_authorized_utility_call_targets([target_address]), MyContract::at(caller).some_view_fn(), ); // For utility calls: env.execute_utility_opts( ExecuteUtilityOptions::new().with_authorized_utility_call_targets([target_address]), MyContract::at(caller).some_utility_fn(), ); ``` ### In production[​](#in-production "Direct link to In production") Pass an `authorizeUtilityCall` hook when [creating the PXE](#configuring-hooks). It receives a `UtilityCallAuthorizationRequest` with the caller and target addresses, their contract class IDs, the function selector, the function name, the arguments, and the caller context (`'private'`, `'private view'`, or `'utility'`). Return `{ authorized: true }` to allow the call, or `{ authorized: false, reason: '...' }` to deny it with a message. When the hook is absent, cross-contract utility calls are denied. See [Cross-contract utility call denied](/developers/testnet/docs/aztec-nr/debugging.md#cross-contract-utility-call-denied) for the resulting error. ## `resolveTaggingSecretStrategy`[​](#resolvetaggingsecretstrategy "Direct link to resolvetaggingsecretstrategy") Called as a fallback for message delivery: a registered onchain handshake's secret is reused directly, so this hook only fires when the sender-recipient pair has none yet. The wallet returns a concrete `TaggingSecretStrategy` (and any material the chosen derivation needs); see [Tagging secret strategy](/developers/testnet/docs/aztec-nr/framework-description/note_delivery.md#tagging-secret-strategy) for the variants, the trade-offs, and the defaults in each environment. ### In Noir tests[​](#in-noir-tests-1 "Direct link to In Noir tests") When testing in Noir, leaving the strategy unset makes `TestEnvironment` fall back to the bare PXE default. Set a strategy when creating the environment to exercise a specific one; it affects message delivery in private executions: ``` let env = TestEnvironment::new_opts( TestEnvironmentOptions::new().with_tagging_secret_strategy(TaggingSecretStrategy::non_interactive_handshake()), ); ``` ### In production[​](#in-production-1 "Direct link to In production") Pass a `resolveTaggingSecretStrategy` hook when [creating the PXE](#configuring-hooks). It receives a `TaggingSecretStrategyRequest` with the executing contract's address and the message's sender, recipient, and delivery mode (`'constrained'` or `'unconstrained'`), so a wallet can apply per-application or per-recipient policies, or surface the decision to the user, instead of returning a fixed value. When the hook is absent, the PXE applies a privacy-safe default: unconstrained delivery uses an [address-derived shared secret](/developers/testnet/docs/aztec-nr/framework-description/note_delivery.md#tagging-secret-strategy), which leaves no onchain trace, while constrained delivery fails rather than silently revealing the recipient through a [non-interactive handshake](/developers/testnet/docs/aztec-nr/framework-description/note_delivery.md#tagging-secret-strategy). --- # Kernelless simulations This page explains what kernelless simulation is in the Private eXecution Environment (PXE), how it differs from a full simulation, and where it does and does not apply. If you are looking for the recipe to make `.simulate()` succeed without signing prompts, see [Simulate without signing prompts](/developers/testnet/docs/aztec-js/how_to_simulate_without_signing.md). ## Overview[​](#overview "Direct link to Overview") A "full" simulation in the PXE runs the user's private function bytecode, then runs every private kernel circuit (init, inner, reset, tail) over the resulting execution trace. The kernels enforce protocol rules such as side-effect counter sequencing. A **kernelless simulation** runs the same private bytecode, but skips the kernel circuits. Instead, the PXE computes the values the kernel would have produced in TypeScript via `generateSimulatedProvingResult`. The output of a kernelless simulation is the same shape as a full simulation, so callers can read return values, offchain effects, and gas estimates from it without caring which path produced them. Kernelless simulation is the **default** for `PXE.simulateTx`. The `skipKernels` option in `SimulateTxOpts` defaults to `true`, and `BaseWallet` inherits that default. In normal use, every call to `.simulate()` on a contract method already runs without the kernels. The main consequence is speed. Skipping the kernels removes the most expensive part of simulation, so a kernelless run is faster than a full run on typical transactions. ## What the PXE still does[​](#what-the-pxe-still-does "Direct link to What the PXE still does") A kernelless simulation is not a partial execution. The PXE still: * runs the real ACIR bytecode for every private function in the call chain * executes oracles, decrypts notes, builds nullifiers, and captures offchain effects * simulates public calls against an ephemeral fork of public state * runs `node.isValidTx` against the resulting transaction, unless `skipTxValidation` is set * at the raw `PXE.simulateTx` level, enforces fee payer presence unless `skipFeeEnforcement` is set. Contract `.simulate()` calls through `BaseWallet` already pass `skipFeeEnforcement: true` for estimation, so you do not need to provide a fee block for normal read simulations What it skips with `skipKernels: true`: * the private kernel init, inner, reset, and tail circuits * the proof generation associated with those kernels The kernels themselves do not check authentication witnesses. Authwit validity is checked by user-contract code (the `is_valid` call that the `#[authorize_once]` macro injects into the called function). What lets a kernelless simulation skip the signing prompt is the **stub-account override**, not the absence of the kernels: replacing the caller's account contract with a stub whose `is_valid` always returns true lets that user-contract check pass without a signature. ## Simulation overrides[​](#simulation-overrides "Direct link to Simulation overrides") A kernelless simulation accepts an optional `SimulationOverrides` payload that lets you replace pieces of the state the PXE would otherwise read from chain. The shape (in `yarn-project/stdlib/src/tx/simulated_tx.ts`) is: ``` type ContractOverrides = Record< string, { instance: ContractInstanceWithAddress } >; class SimulationOverrides { publicStorage?: PublicStorageOverride[]; contracts?: ContractOverrides; } ``` Two parts: * `publicStorage` rewrites slots in the ephemeral public-data fork before simulation. This is compatible with kernel execution; you can use it with or without `skipKernels`. * `contracts` swaps a contract instance in the simulator's contract DB by replacing its `currentContractClassId`. There is no `artifact` field; the new class id must already be registered with the PXE via `pxe.registerContractClass(...)` so the simulator can resolve the bytecode. This requires `skipKernels: true`: PXE explicitly rejects contract overrides combined with kernel execution, because the kernels would fail validations against the swapped class. The `contracts` override path is what makes "simulate without signing" possible. Replacing the caller's account contract with a stub whose `is_valid` always returns true lets the simulation reach `#[authorize_once]` call sites without prompting the user to sign anything. For the cheat that simulates a contract as if it had already been upgraded to a new class, see [`fastForwardContractUpdate`](/developers/testnet/docs/aztec-js/how_to_test.md#fast-forwarding-a-contract-update), which returns a `SimulationOverrides` covering both the registry storage rewrite and the upgraded instance entry. ## Stub account contracts[​](#stub-account-contracts "Direct link to Stub account contracts") The stub-account pattern is the standard way to drive a kernelless simulation without authwit prompts. The Noir sources live at `noir-projects/noir-contracts/contracts/account/simulated_schnorr_account_contract/` and `simulated_ecdsa_account_contract/`. Both implement `is_valid` to always return `IS_VALID_SELECTOR`, so authwit validity checks pass without a real signature. Their constructors deliberately emit the same shape of side effects as the real account contracts (one nullifier for the contract init, one nullifier for the `SinglePrivateImmutable` signing-key state, one note hash for the key note, and a private log) so that gas estimation against the stub produces the same numbers as the real account. ## Authwit requests come from the app, not the stub[​](#authwit-requests-come-from-the-app-not-the-stub "Direct link to Authwit requests come from the app, not the stub") The `CallAuthorizationRequest` offchain effects you see during a kernelless simulation are emitted by the **app or token contract's `#[authorize_once]` macro** during private execution. The stub account's only job is to let the validity check pass so the simulation can reach those call sites in the first place. The wallet then collects the requests via `collectOffchainEffects(privateExecutionResult)`, filters them by `CallAuthorizationRequest.getSelector()`, and decodes each one to build a real `AuthWitness`. ## Where kernelless does not apply[​](#where-kernelless-does-not-apply "Direct link to Where kernelless does not apply") The default applies to `simulateTx`, but not to every entry point that looks like simulation: * **`profileTx`** is not kernelless. `PXE.profileTx` always runs the private kernels after private execution; `skipProofGeneration` controls only whether a proof is produced, not whether kernel logic runs. If you call `.profile()` to measure circuit gates, expect the full kernel cost. * **Public-only fast path**. When `BaseWallet.simulateTx` detects a leading run of public static calls, it sends them straight to `node.simulatePublicCalls` through `simulateViaNode`, bypassing the PXE private path entirely. There is no kernel to skip. `SimulationOverrides` still applies on that path. * **Utility functions**. `FunctionType.UTILITY` calls go through `wallet.executeUtility`, not `pxe.simulateTx`. `ContractFunctionInteraction.simulate` rejects `overrides.publicStorage` and `overrides.contracts` for utility functions. ## Gas estimation parity[​](#gas-estimation-parity "Direct link to Gas estimation parity") If the real transaction will pay through a fee payment contract (FPC) with private side effects (the FPC emits notes during fee payment), include that FPC in the simulation's fee options. The FPC's side effects feed into gas estimation, and you can run kernelless with the FPC attached to get both the speed benefit and accurate gas numbers. The default of "omit the fee block" only produces accurate gas for transactions whose fee path has no private side effects. ## Multi-account scopes[​](#multi-account-scopes "Direct link to Multi-account scopes") A simulation can run with multiple scoped accounts via `additionalScopes`. If you build a stub-account override for the sender only, the simulation will still prompt for authwits from any other in-scope account it touches. The override map must cover every account in scope, not just `from`. The canonical implementation is `EmbeddedWallet.buildAccountOverrides` in `yarn-project/wallets/src/embedded/embedded_wallet.ts`: for each scoped address, fetch the live contract instance from the PXE, copy it, and rewrite `currentContractClassId` to point at the stub class id registered at wallet startup. When implementing overrides in your own wallet, follow this pattern and make sure the scope list you build against matches the one the simulation will run with. ## When you might still want a full simulation[​](#when-you-might-still-want-a-full-simulation "Direct link to When you might still want a full simulation") Kernelless is the right default. Reach for `skipKernels: false` only when you are validating kernel-level behavior itself. For everything else, including accurate gas estimation through a fee payment contract with private side effects, run kernelless with the appropriate fee options. ## Related[​](#related "Direct link to Related") * [Simulate without signing prompts](/developers/testnet/docs/aztec-js/how_to_simulate_without_signing.md) for the recipe-oriented version of this page. * [Reading contract data](/developers/testnet/docs/aztec-js/how_to_read_data.md) for the basic `.simulate()` API. * [Wallets](/developers/testnet/docs/foundational-topics/wallets.md) for the wallet's role in capturing private authorizations during simulation. --- # State Management Aztec has a hybrid public/private state model. Contract developers can specify which data is public and which is private, as well as the functions that operate on that data. Private and public data are stored in two separate trees: a **public data tree** and a **note hashes tree**. Both trees store state for all accounts on the network directly as leaves, unlike Ethereum where a state trie contains smaller tries for individual accounts. This means storage must be carefully allocated to prevent collisions. Storage is *siloed* to each contract, though the exact siloing mechanism differs slightly between public and private storage. ## Public State[​](#public-state "Direct link to Public State") Public state in Aztec works similarly to other blockchains. It is transparent and managed by smart contract logic. The sequencer stores and updates public state. It executes state transitions, generates proofs of correct execution (or delegates to the prover network), and publishes data to Ethereum. ## Private State[​](#private-state "Direct link to Private State") Private state is encrypted and owned by users who hold the decryption keys. It uses an append-only data structure since updating records directly would leak information about the transaction graph. To "delete" private state, you add an associated nullifier to a nullifier set. The nullifier is computed such that observers cannot link a state record to its nullifier without the owner's keys. Modifying state is accomplished by nullifying the existing record and creating a new one. This gives private state an intrinsic UTXO (unspent transaction output) structure. ## Notes[​](#notes "Direct link to Notes") Private state uses UTXOs, commonly called **notes**. Notes are encrypted pieces of data that only their owner can decrypt. ### How Notes Work[​](#how-notes-work "Direct link to How Notes Work") In Ethereum's account-based model, each account maps to a specific storage location. In Aztec's UTXO model, notes specify their owner and have no fixed relationship between accounts and data locations. Rather than storing entire notes, the protocol stores **note commitments** (hashes) in a Merkle tree called the note hash tree. Users prove they know the note preimage when updating private state. When a note is consumed, Aztec creates a nullifier from the note data and may create new notes with updated information. This decouples the actions of creating, updating, and deleting private state. ![](/assets/ideal-img/public-and-private-state-diagram.8ac73af.640.png) Notes work like cash. To spend a 5 dollar note on a $3.50 purchase, you nullify the $5 note and create two new notes: $1.50 for yourself and $3.50 for the recipient. Only you and the recipient know about the $3.50 transfer. ### Sending Notes[​](#sending-notes "Direct link to Sending Notes") When creating notes for a recipient, you need a way to deliver them: **Onchain (encrypted logs):** The standard method. Emit an encrypted log as part of your transaction. The encrypted note data is posted onchain, allowing recipients to find notes through [note discovery](/developers/testnet/docs/foundational-topics/advanced/storage/note_discovery.md). **Offchain:** If you know the recipient directly, share the note data with them. They store it in their PXE and can spend it later. **Self-created notes:** Notes you create for yourself don't need broadcasting. Store them in your PXE to prove ownership and spend them later. ### Abstracting Notes[​](#abstracting-notes "Direct link to Abstracting Notes") Users don't need to think about individual notes. The Aztec.nr library abstracts notes by letting developers define custom note types that specify how notes are created, nullified, transferred, and displayed. Aztec.nr also handles [note discovery](/developers/testnet/docs/foundational-topics/advanced/storage/note_discovery.md) for notes encrypted to a user's account. ## Technical Details[​](#technical-details "Direct link to Technical Details") ### Storage Slots[​](#storage-slots "Direct link to Storage Slots") Public storage uses literal storage slots. Private storage uses logical storage slots that associate multiple notes together. See [storage slots](/developers/testnet/docs/foundational-topics/advanced/storage/storage_slots.md) for details. ### Contract Address Siloing[​](#contract-address-siloing "Direct link to Contract Address Siloing") The contract address is included when computing note hashes to ensure different contracts don't produce identical hashes. The protocol handles this automatically. ### Note Types[​](#note-types "Direct link to Note Types") Aztec.nr provides several note types: * **`PrivateSet`** - A collection of notes, useful for balances represented as multiple value notes * **`PrivateMutable`** - A single note representing one value that can be replaced * **`PrivateImmutable`** - A single note that cannot be changed after initialization These state variables must be wrapped in an `Owned<>` type that specifies the note owner. The `Owned<>` wrapper binds a note collection to a specific owner address, ensuring notes are correctly associated with their owner for nullifier computation and access control. ``` #[storage] struct Storage { balance: Owned, Context>, } ``` Notes can also be custom types storing any values your application needs. Use the `#[note]` macro for standard notes or `#[custom_note]` for notes requiring custom hash or nullifier computation. ### Built-in Note Types[​](#built-in-note-types "Direct link to Built-in Note Types") **`UintNote`** - Stores a numeric value (`u128`). Supports partial notes for scenarios where the value is determined in public execution. uint\_note\_def ``` #[derive(Deserialize, Eq, Serialize, Packable)] #[custom_note] pub struct UintNote { /// The number stored in the note. pub value: u128, } ``` > [Source code: noir-projects/aztec-nr/uint-note/src/uint\_note.nr#L29-L36](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/aztec-nr/uint-note/src/uint_note.nr#L29-L36) **`FieldNote`** - Stores a single `Field` value. ### Creating and Destroying Notes[​](#creating-and-destroying-notes "Direct link to Creating and Destroying Notes") The [lifecycle module](https://github.com/AztecProtocol/aztec-packages/tree/v5.0.0-rc.2/noir-projects/aztec-nr/aztec/src/note/lifecycle.nr) contains functions for note management: * `create_note` - Creates a new note, computing its hash and pushing it to the context * `destroy_note` - Nullifies a note by computing and emitting its nullifier Notes created and nullified within the same transaction are called **transient notes**. The kernel circuits automatically squash these, avoiding unnecessary tree insertions and improving efficiency. ### Note Interface[​](#note-interface "Direct link to Note Interface") Notes must implement the `NoteHash` trait from [note\_interface.nr](https://github.com/AztecProtocol/aztec-packages/tree/v5.0.0-rc.2/noir-projects/aztec-nr/aztec/src/note/note_interface.nr): * `compute_note_hash(self, owner, storage_slot, randomness)` - Computes the note's commitment * `compute_nullifier(self, context, owner, note_hash_for_nullification)` - Computes the nullifier for consumption * `compute_nullifier_unconstrained(self, owner, note_hash_for_nullification)` - Unconstrained nullifier computation The `#[note]` macro generates default implementations using `poseidon2_hash_with_separator`. ### Reading Notes[​](#reading-notes "Direct link to Reading Notes") Only users with appropriate keys can read private values they have permission to access. Notes can be read offchain without modifying onchain state. When reading a note in a transaction, subsequent reads of the same note would reveal a link between transactions. To preserve privacy, notes read in transactions are typically "consumed" (nullified) and new notes created. With `PrivateSet`, a private variable's value can be interpreted as the sum of all notes at that storage slot. Nullifying is done by inserting a nullifier into the nullifier tree, not by deleting the note hash. ### Updating Notes[​](#updating-notes "Direct link to Updating Notes") To update a value, nullify the existing note hash(es) and insert a new note hash for the updated value. The PXE tracks note state locally while the note hash tree records the cryptographic commitments. ## Further Reading[​](#further-reading "Direct link to Further Reading") * [High level network architecture](/developers/testnet/docs/foundational-topics.md) * [Transaction lifecycle](/developers/testnet/docs/foundational-topics/transactions.md#simple-example-of-the-private-transaction-lifecycle) * [Storage slots](/developers/testnet/docs/foundational-topics/advanced/storage/storage_slots.md) * [Note discovery](/developers/testnet/docs/foundational-topics/advanced/storage/note_discovery.md) --- # Transactions On this page you'll learn: * The step-by-step process of sending a transaction on Aztec * The role of components like PXE, Aztec Node, and the sequencer * The private and public kernel circuits and how they execute function calls * The call stacks for private and public functions and how they determine a transaction's completion For a two-minute visual overview of how a single transaction spans private and public execution, watch this explainer (find more on the [video lessons](/developers/testnet/docs/resources/video_lessons.md) page): [One Transaction, Two Worlds: Private and Public State on Aztec](https://www.youtube-nocookie.com/embed/MayopgQ1FjI) ## Simple Example of the (Private) Transaction Lifecycle[​](#simple-example-of-the-private-transaction-lifecycle "Direct link to Simple Example of the (Private) Transaction Lifecycle") The transaction lifecycle for an Aztec transaction is fundamentally different from the lifecycle of an Ethereum transaction. The introduction of the Private eXecution Environment (PXE) provides a safe environment for the execution of sensitive operations, ensuring that decrypted data are not accessible to unauthorized applications. However, the PXE exists client-side on user devices, which creates a different model for imagining what the lifecycle of a typical transaction might look like. The existence of a sequencing network also introduces some key differences between the Aztec transaction model and the transaction model used for other networks. The accompanying diagram illustrates the flow of interactions between a user, their wallet, the PXE, the node operators (sequencers / provers), and the L1 chain. ![](/assets/ideal-img/transaction-lifecycle.266635e.640.png) 1. **The user initiates a transaction** – In this example, the user decides to privately send 10 DAI to gudcause.eth. After inputting the amount and the receiving address, the user clicks the confirmation button on their wallet. 2. **The PXE executes transfer locally** – The PXE, running locally on the user's device, executes the transfer method on the DAI token contract on Aztec and computes the state difference based on the user's intention. At this point, the transaction exists solely within the context of the PXE. 3. **The PXE proves correct execution** – The PXE proves correct execution (via zero-knowledge proofs) of the authorization and of the private transfer method. Once the proofs have been generated, the PXE sends the proofs and required inputs (new note commitments and nullifiers) to the sequencer. 4. **The sequencer processes the transaction** – The pseudorandomly-selected sequencer validates the transaction proofs along with required inputs for this private transfer. The sequencer also executes public functions and updates state: public state is updated by directly modifying entries in the sparse Merkle tree, while private state is updated by adding the newly created note commitments and nullifiers to the indexed Merkle trees. The sequencer then computes the new state root and posts the block to L1. 5. **The transaction settles to L1** – The block is posted to L1, and later, provers submit epoch proofs to the verifier contract on Ethereum. Once the epoch proof is verified, the state transitions are considered final and the private transfer has settled. ### Detailed Diagram[​](#detailed-diagram "Direct link to Detailed Diagram") The following diagram provides a more detailed overview of the transaction execution process, highlighting three different types of transaction execution: contract deployments, private transactions, and public transactions. ![](/assets/ideal-img/local_network_sending_a_tx.48faac8.640.png) See the page on [call types](/developers/testnet/docs/foundational-topics/call_types.md) for more context on transaction execution. ### Transaction Requests[​](#transaction-requests "Direct link to Transaction Requests") Transaction requests are how transactions are constructed and sent to the network. In Aztec.js: constructor ``` constructor( /** Sender. */ public origin: AztecAddress, /** Pedersen hash of function arguments. */ public argsHash: Fr, /** Transaction context. */ public txContext: TxContext, /** Function data representing the function to call. */ public functionData: FunctionData, /** A salt to make the hash difficult to predict. The hash is used as the first nullifier if there is no nullifier emitted throughout the tx. */ public salt: Fr, ) {} ``` > [Source code: yarn-project/stdlib/src/tx/tx\_request.ts#L15-L28](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/yarn-project/stdlib/src/tx/tx_request.ts#L15-L28) Where: * `origin` is the account contract where the transaction is initiated from. * `argsHash` is the hash of the arguments of the entrypoint call. The complete set of arguments is passed to the PXE as part of the `TxExecutionRequest` and checked against this hash. * `txContext` contains the chain id, version, and gas settings. * `functionData` contains the function selector and indicates whether the function is private or public. * `salt` is used to make the transaction request hash difficult to predict. The hash is used as the first nullifier if no nullifier is emitted throughout the transaction. The `TxExecutionRequest` class: tx\_execution\_request\_class ``` export class TxExecutionRequest { constructor( /** * Sender. */ public origin: AztecAddress, /** * Selector of the function to call. */ public functionSelector: FunctionSelector, /** * The hash of arguments of first call to be executed (usually account entrypoint). * @dev This hash is a pointer to `argsOfCalls` unordered array. */ public firstCallArgsHash: Fr, /** * Transaction context. */ public txContext: TxContext, /** * An unordered array of packed arguments for each call in the transaction. * @dev These arguments are accessed in Noir via oracle and constrained against the args hash. The length of * the array is equal to the number of function calls in the transaction (1 args per 1 call). */ public argsOfCalls: HashedValues[], /** * Transient authorization witnesses for authorizing the execution of one or more actions during this tx. * These witnesses are not expected to be stored in the local witnesses database of the PXE. */ public authWitnesses: AuthWitness[], /** * Read-only data passed through the oracle calls during this tx execution. */ public capsules: Capsule[], /** * A salt to make the tx request hash difficult to predict. * The hash is used as the first nullifier if there is no nullifier emitted throughout the tx. */ public salt = Fr.random(), ) {} ``` > [Source code: yarn-project/stdlib/src/tx/tx\_execution\_request.ts#L23-L64](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/yarn-project/stdlib/src/tx/tx_execution_request.ts#L23-L64) An account contract validates that the transaction request has been authorized via its specified authorization mechanism, via the `is_valid_impl` function. Here is an example using an ECDSA signature: is\_valid\_impl ``` #[contract_library_method] fn is_valid_impl(context: &mut PrivateContext, outer_hash: Field) -> bool { // Load public key from storage let storage = Storage::init(context); let public_key = storage.signing_public_key.get_note(); // Safety: The witness is only used as a "magical value" that makes the signature verification below pass. // Hence it's safe. let signature: [u8; 64] = unsafe { get_auth_witness_as_bytes(outer_hash) }; // Verify payload signature using Ethereum's signing scheme // Note that noir expects the hash of the message/challenge as input to the ECDSA verification. let outer_hash_bytes: [u8; 32] = outer_hash.to_be_bytes(); let hashed_message: [u8; 32] = sha256::digest(outer_hash_bytes); std::ecdsa_secp256k1::verify_signature(public_key.x, public_key.y, signature, hashed_message) } ``` > [Source code: noir-projects/noir-contracts/contracts/account/ecdsa\_k\_account\_contract/src/main.nr#L55-L72](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/account/ecdsa_k_account_contract/src/main.nr#L55-L72) Transaction requests are simulated in the PXE in order to generate the necessary inputs for generating proofs. Once transactions are proven, a `Tx` object is created and can be sent to the network to be included in a block: tx\_class ``` export class Tx extends Gossipable { static override p2pTopic = TopicType.tx; private calldataMap: Map | undefined; constructor( /** * Identifier of the tx. * It's a hash of the public inputs of the tx's proof. * This claimed hash is reconciled against the tx's public inputs (`this.data`) in data_validator.ts. */ public readonly txHash: TxHash, /** * Output of the private kernel circuit for this tx. */ public readonly data: PrivateKernelTailCircuitPublicInputs, /** * Proof from the private kernel circuit. */ public readonly chonkProof: ChonkProof, /** * Contract class log fields emitted from the tx. * Their order should match the order of the log hashes returned from `this.data.getNonEmptyContractClassLogsHashes`. * This claimed data is reconciled against a hash of this data (that is contained within * the tx's public inputs (`this.data`)), in data_validator.ts. */ public readonly contractClassLogFields: ContractClassLogFields[], /** * An array of calldata for the enqueued public function calls and the teardown function call. * This claimed data is reconciled against hashes of this data (that are contained within * the tx's public inputs (`this.data`)), in data_validator.ts. */ public readonly publicFunctionCalldata: HashedValues[], ) { super(); } ``` > [Source code: yarn-project/stdlib/src/tx/tx.ts#L39-L76](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/yarn-project/stdlib/src/tx/tx.ts#L39-L76) #### Contract Interaction Methods[​](#contract-interaction-methods "Direct link to Contract Interaction Methods") Most transaction requests are created as interactions with specific contracts. The exception is transactions that deploy contracts. Here are the main methods for interacting with contracts related to transactions. 1. [`simulate`](#simulate) 2. [`send`](#send) ##### `simulate`[​](#simulate "Direct link to simulate") simulate ``` /** * Simulate a transaction and get information from its execution. * Differs from prove in a few important ways: * 1. It returns the values of the function execution, plus additional metadata if requested * 2. It supports `utility`, `private` and `public` functions * * @param options - An optional object containing additional configuration for the simulation. * @returns Depending on the simulation options, this method directly returns the result value of the executed * function or a rich object containing extra metadata, such as estimated gas costs (if requested via options), * execution statistics and emitted offchain effects */ public async simulate( options: SimulateInteractionOptions = {} as SimulateInteractionOptions, ): Promise { ``` > [Source code: yarn-project/aztec.js/src/contract/contract\_function\_interaction.ts#L114-L129](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/yarn-project/aztec.js/src/contract/contract_function_interaction.ts#L114-L129) ##### `send`[​](#send "Direct link to send") send ``` /** * Sends a transaction to the contract function with the specified options. * By default, waits for the transaction to be mined and returns the receipt (or custom type). * @param options - An object containing 'from' property representing * the AztecAddress of the sender, optional fee configuration, and optional wait settings * @returns TReturn (if wait is undefined/WaitOpts) or TxHash (if wait is NO_WAIT) */ // Overload for when wait is not specified at all - returns { receipt: TReturn, offchainEffects } public send(options: SendInteractionOptionsWithoutWait): Promise>; // Generic overload for explicit wait values // eslint-disable-next-line jsdoc/require-jsdoc public send( options: SendInteractionOptions, ): Promise>; // eslint-disable-next-line jsdoc/require-jsdoc public async send( options: SendInteractionOptions, ): Promise> { ``` > [Source code: yarn-project/aztec.js/src/contract/base\_contract\_interaction.ts#L37-L56](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/yarn-project/aztec.js/src/contract/base_contract_interaction.ts#L37-L56) ### Batch Transactions[​](#batch-transactions "Direct link to Batch Transactions") Batched transactions are a way to send multiple transactions in a single call. They are created by the `BatchCall` class in Aztec.js. This allows a batch of function calls from a single wallet to be sent as a single transaction through a wallet. batch\_call\_class ``` export class BatchCall extends BaseContractInteraction { constructor( wallet: Wallet, protected interactions: (BaseContractInteraction | ExecutionPayload)[], private extraHashedArgs: HashedValues[] = [], ) { super(wallet); } ``` > [Source code: yarn-project/aztec.js/src/contract/batch\_call.ts#L17-L26](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/yarn-project/aztec.js/src/contract/batch_call.ts#L17-L26) ### Enabling Transaction Semantics[​](#enabling-transaction-semantics "Direct link to Enabling Transaction Semantics") There are two kernel circuits in Aztec, the private kernel and the public kernel. Each circuit validates the correct execution of a particular function call. A transaction is built up by generating proofs for multiple recursive iterations of kernel circuits. Each call in the call stack is modeled as a new iteration of the kernel circuit and is managed by a [FIFO](https://en.wikipedia.org/wiki/FIFO_\(computing_and_electronics\)) queue containing pending function calls. There are two call stacks, one for private calls and one for public calls. One iteration of a kernel circuit will pop a call off of the stack and execute the call. If the call triggers subsequent contract calls, these are pushed onto the stack. Private kernel proofs are generated first. The transaction is ready to move to the next phase when the private call stack is empty. The public kernel circuit takes in proof of a public/private kernel circuit with an empty private call stack, and operates recursively until the public call stack is also empty. A transaction is considered complete when both call stacks are empty. The only information leaked about the transaction is: 1. The number of private state updates triggered 2. The set of public calls generated The addresses of all private calls are hidden from observers. ## Transaction phases[​](#transaction-phases "Direct link to Transaction phases") An Aztec transaction is split into up to three phases at execution time. The boundaries matter mostly when integrating with fee-paying contracts (FPCs): which phase a call runs in determines whether it can revert, which public functions it is allowed to call, and when its side effects become final. ### Setup phase (non-revertible)[​](#setup-phase-non-revertible "Direct link to Setup phase (non-revertible)") The setup phase runs before the user's application logic. Fee-related bookkeeping happens here: * The fee payer is nominated via a call to the protocol's `set_as_fee_payer()` function. An FPC typically calls this in its entrypoint; a user paying directly with Fee Juice does it implicitly via the default entrypoint. * `end_setup()` is called to mark the boundary between the non-revertible and revertible phases. Everything committed before `end_setup()` stands regardless of whether later phases revert. Because the setup phase is non-revertible, the protocol restricts which public function calls are allowed during it. The default allowlist permits a small set of trusted setup functions (for example those on `AuthRegistry` and `FeeJuice`); in v4.2.0, public token functions such as `transfer_in_public` and `_increase_public_balance` were removed from it. See the [migration note](/developers/testnet/docs/resources/migration_notes.md#custom-token-fpcs-removed-from-default-public-setup-allowlist) for details. Practical consequences: * A fee payment committed during setup is charged to the payer even if the app phase later reverts. * An FPC cannot collect payment by directly calling an arbitrary user token's public transfer during setup. It either works purely in the private domain, or relies on a token function the network operator has added to the allowlist. ### App phase (revertible)[​](#app-phase-revertible "Direct link to App phase (revertible)") The app phase runs the user's actual transaction logic. Private execution has already happened locally in the PXE before the transaction was submitted (producing the proof, nullifiers, and note commitments included with the transaction); what runs in this phase is the public call stack. It starts with the public calls that private execution enqueued, and grows as those public calls themselves enqueue further public calls. If any public call in this phase reverts, all state changes from the phase are discarded, but fees committed during setup are still paid. ### Teardown phase (optional)[​](#teardown-phase-optional "Direct link to Teardown phase (optional)") Transactions can optionally include a teardown phase after app execution. During teardown, the final transaction fee is available to public functions, which is useful for FPCs that want to refund unused gas to the user. Not every FPC uses teardown; some charge a fixed quoted amount with no refund, retaining any surplus in the FPC's Fee Juice balance. ## Next Steps[​](#next-steps "Direct link to Next Steps") * Learn about [accounts](/developers/testnet/docs/foundational-topics/accounts.md) and how they authorize transactions * Understand [state management](/developers/testnet/docs/foundational-topics/state_management.md) and how transaction effects are stored * Explore the [PXE](/developers/testnet/docs/foundational-topics/pxe.md) in more detail * Understand the [performance impact of kernel circuits](/developers/testnet/docs/foundational-topics/advanced/circuits/private_kernel.md#performance-impact) on proving time --- # Wallets This page covers the main responsibilities of a wallet in the Aztec network. Wallets are the applications through which users manage their accounts. Users rely on wallets to browse through their accounts, monitor their balances, and create new accounts. Wallets also store seed phrases and private keys, or interact with external keystores such as hardware wallets. Wallets also provide an interface for dapps. Dapps may request access to see the user accounts, in order to show the state of those accounts in the context of the application, and request to send transactions from those accounts as the user interacts with the dapp. In addition to these usual responsibilities, wallets in Aztec also need to track private state. This implies keeping a local database of all private notes encrypted for any of the user's accounts, so dapps and contracts can query the user's private state. Aztec wallets are also responsible for producing local proofs of execution for private functions. ## Account setup[​](#account-setup "Direct link to Account setup") The first step for any wallet is to let the user set up their [accounts](/developers/testnet/docs/foundational-topics/accounts.md). An account in Aztec is represented onchain by its corresponding account contract that the user must deploy to begin interacting with the network. This account contract dictates how transactions are authenticated and executed. A wallet must support at least one specific account contract implementation, which means being able to deploy such a contract, as well as interacting with it when sending transactions. Code-wise, this requires [implementing the `AccountContract` interface](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/yarn-project/aztec.js/src/account/account_contract.ts). Note that users must be able to receive funds in Aztec before deploying their account. A wallet should let a user generate a [deterministic complete address](/developers/testnet/docs/foundational-topics/accounts/keys.md#address-derivation) without having to interact with the network, so they can share it with others to receive funds. This requires that the wallet pins a specific contract implementation, its initialization arguments, a deployment salt, and the user's keys. These values yield a deterministic address, so when the account contract is actually deployed, it is available at the precalculated address. Once the account contract is deployed, the user can start sending transactions using it as the transaction origin. ## Transaction lifecycle[​](#transaction-lifecycle "Direct link to Transaction lifecycle") Every transaction in Aztec is broadcast to the network as a zero-knowledge proof of correct execution, in order to preserve privacy. This means that transaction proofs are generated on the wallet and not on a remote node. This is one of the biggest differences with regard to EVM chain wallets. A wallet is responsible for **creating** an *execution request* out of one or more *function calls* requested by a dapp. For example, a dapp may request a wallet to "invoke the `transfer` function on the contract at `0x1234` with the following arguments", in response to a user action. The wallet turns that into an execution request with the signed instructions to execute that function call from the user's account contract. In an [ECDSA-based account](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/account/ecdsa_k_account_contract/src/main.nr), for instance, this is an execution request that encodes the function call in the *entrypoint payload*, and includes its ECDSA signature with the account's signing private key. Once the *execution request* is created, the wallet is responsible for **simulating** and **proving** the execution of its private functions. The simulation yields an execution trace, which can be used to provide the user with a list of side effects of the private execution of the transaction. During this simulation, the wallet is responsible for providing data to the virtual machine, such as private notes, encryption keys, or nullifier secrets. This execution trace is fed into the prover, which returns a zero-knowledge proof that guarantees correct execution and hides all private information. The output of this process is a *transaction object*. info Private functions use a UTXO model, so their execution trace is determined entirely by the input notes. Since notes are immutable, simulation results match mined results exactly. However, a transaction may be dropped if it tries to consume a note that was nullified by another transaction first. Public functions use an account model (like Ethereum), so their execution trace depends on chain state at inclusion time, which may differ from simulation. Before sending, the wallet may run a **simulation**, a lightweight execution using a stub account contract that avoids expensive kernel circuit execution. This simulation estimates gas limits for the transaction and captures any required private authorization data (see [Authorizing actions](#authorizing-actions) below). The `EmbeddedWallet` runs this step automatically on every send. For details on what the PXE skips in this mode and how to wire it up in your own wallet, see [Kernelless simulations](/developers/testnet/docs/foundational-topics/pxe/kernelless_simulations.md). Finally, the wallet **sends** the resulting *transaction* object, which includes the proof of execution, to an Aztec Node. The transaction is then broadcasted through the peer-to-peer network, to be eventually picked up by a sequencer and included in a block. ## Authorizing actions[​](#authorizing-actions "Direct link to Authorizing actions") Account contracts in Aztec expose an interface for other contracts to validate [whether an action is authorized by the account or not](/developers/testnet/docs/foundational-topics/accounts.md#authentication-witnesses-authwit). For example, an application contract may want to transfer tokens on behalf of a user, in which case the token contract will check with the account contract whether the application is authorized to do so. These actions may be carried out in private or in public functions, and in transactions originated by the user or by someone else. Wallets should manage these authorizations, prompting the user when they are requested by an application. Authorizations in private executions come in the form of *auth witnesses*, which are usually signatures over an identifier for an action. Applications can request the wallet to produce an auth witness via the `createAuthWit` call. In public functions, authorizations are pre-stored in the account contract storage, which is handled by a call to an internal function in the account contract implementation. Wallets can automate private authorization by capturing authorization requests during simulation. The `EmbeddedWallet`, for example, detects which private authwits a transaction needs and generates them automatically, so dapps don't need to explicitly create or manage private authorizations. Public authorizations still require explicit setup, as they involve onchain state changes that must occur before the authorized action. ## Key management[​](#key-management "Direct link to Key management") As in EVM-based chains, wallets are expected to manage user keys, or provide an interface to hardware wallets or alternative key stores. Keep in mind that in Aztec each account requires [multiple key pairs](/developers/testnet/docs/foundational-topics/accounts/keys.md): protocol keys (nullifier and incoming viewing keys) are mandated by the protocol and used for spending notes and decryption, whereas signing keys are dependent on the account contract implementation rolled out by the wallet. Should the account contract support it, wallets must provide the user with the means to rotate or recover their signing keys. info Due to limitations in the current architecture, protocol keys need to be available in the wallet software itself and cannot be delegated to an external keystore. This restriction may be lifted in a future release. ## Recipient address management[​](#recipient-address-management "Direct link to Recipient address management") Wallets are also expected to manage the public encryption keys of any recipients of local transactions. When creating an encrypted note for a recipient given their address, the wallet needs to provide their [complete address](/developers/testnet/docs/foundational-topics/accounts/keys.md#address-derivation). Recipients broadcast their complete addresses when deploying their account contracts, and wallets collect this information and save it in a local registry for easy access when needed. Note that, in order to interact with a recipient who has not yet deployed their account contract (and thus not broadcasted their complete address), it must also be possible to manually add an entry to a wallet's local registry of complete addresses. ## Private state[​](#private-state "Direct link to Private state") Wallets also store the user's private state. Aztec uses a [note tagging system](/developers/testnet/docs/foundational-topics/advanced/storage/note_discovery.md) that allows users to efficiently discover notes that belong to them. When a note is created, the sender tags it with a value derived from a shared secret, allowing the recipient's wallet to query for relevant notes without attempting to decrypt every note on the network. Once discovered, notes are decrypted and added to the corresponding account's private state. To discover notes from a sender, the wallet must first register that sender's address with the PXE. This allows the wallet to compute the shared secrets needed for note tagging. Wallets typically register senders when users add contacts or interact with new counterparties. Wallets must also scan for private state in blocks prior to the deployment of a user's account contract, since users may have received notes before deployment. Private state can be encrypted and broadcast through the network, then committed to L1. While tags allow wallets to query the network for relevant notes, the tags themselves don't reveal the recipient - only the sender and recipient can compute and recognize them. This means wallets need to maintain a local database of their accounts' private state to answer queries efficiently. Dapps may require access to the user's private state, in order to show information relevant to the current application. For instance, a dapp for a token may require access to the user's private notes in the token contract in order to display the user's balance. It is the responsibility of the wallet to require authorization from the user before disclosing private state to a dapp. ## Account interface[​](#account-interface "Direct link to Account interface") The account interface is used for creating an *execution request* out of one or more *function calls* requested by a dapp, as well as creating an *auth witness* for a given message hash. Account contracts are expected to handle multiple function calls per transaction, since dapps may choose to batch multiple actions into a single request to the wallet. account-interface ``` /** * Minimal interface for transaction execution and authorization. */ export type Account = EntrypointInterface & AuthorizationProvider & { /** Returns the complete address for this account. */ getCompleteAddress(): CompleteAddress; /** Returns the address for this account. */ getAddress(): AztecAddress; }; ``` > [Source code: yarn-project/aztec.js/src/account/account.ts#L23-L34](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/yarn-project/aztec.js/src/account/account.ts#L23-L34) --- # Community Calls **Build with us, live.** Every week you can join office hours and ecosystem calls to get unblocked, learn from maintainers, and connect with other builders. Pick the call that fits your needs, add it to your calendar, and show up with questions. *** ## Ecosystem Call[​](#ecosystem-call "Direct link to Ecosystem Call") * **When:** Biweekly · Wednesdays · 16:00 - 16:45 UTC * **Where:** [Google Meet](https://meet.google.com/tnk-phse-bmz) * **For:** The Ecosystem Call is the place to be if you're building on Aztec. Get updates on libraries and devnet, watch demo apps, peek at what's coming next, and connect with the community. Building an app? Don't miss it. *** ## Aztec & Noir Developer Office Hours[​](#aztec--noir-developer-office-hours "Direct link to Aztec & Noir Developer Office Hours") * **When:** Thursdays · 14:00 - 15:00 UTC * **Where:** [Google Meet](https://meet.google.com/vev-waao-mab) * **For:** Developers building with Aztec.nr smart contracts or writing and debugging Noir. Bring your questions about syntax, tooling, patterns, or protocol-level topics. Share a project you're working on, or just hang out with the Aztec Labs Dev Rel team and other devs. *** ## Community[​](#community "Direct link to Community") * Follow discussions in the [Forum](https://forum.aztec.network) * Ask daily questions in [Discord](https://discord.gg/aztec) *** ## One calendar, all calls[​](#one-calendar-all-calls "Direct link to One calendar, all calls") Save time, add everything with one click: [Subscribe to the Aztec Builder Calendar](https://calendar.google.com/calendar/u/0?cid=Y19kMTdhMTEzNmU3NDEwNDNiNDJkMTZlYWU2ZDUzODg4YjlhYTVhNzA5NzNkNjVkNDU3YTA1ZTc3NWNhMGEwNWY5QGdyb3VwLmNhbGVuZGFyLmdvb2dsZS5jb20) --- # Limitations The Aztec stack is a work in progress. Packages have been released early to gather feedback on the capabilities of the protocol and user experiences. ## What to expect[​](#what-to-expect "Direct link to What to expect") * Regular breaking changes * Missing features * Bugs * An "unpolished" UX * Missing information ## Why participate[​](#why-participate "Direct link to Why participate") Front-run the future! Help shape and define: * Previously-impossible smart contracts and applications * Network tooling * Network standards * Smart contract syntax * Educational content * Core protocol improvements ## Limitations developers need to know about[​](#limitations-developers-need-to-know-about "Direct link to Limitations developers need to know about") * The Aztec stack is unaudited and under active development. See the [Alpha Network](/participate/alpha.md) page for details on what this means. * `msg_sender` is leaked by default when making private -> public calls. * `self.enqueue(...)` sets `msg_sender` to the private caller's address, which is publicly visible. * Use `self.enqueue_incognito(...)` to hide the sender. The called public function must use `maybe_msg_sender()` instead of `msg_sender()` to handle the null sender. * The initial `msg_sender` is `-1`, which can be problematic for some contracts. * Some side-effect counts are still visible in a transaction. Note hashes, nullifiers, and private logs are padded to hide their true counts, but the number of public function calls and L2->L1 messages remains visible. Privacy sets to further reduce leakage are still under development. * A transaction can only emit a limited number of side-effects (notes, nullifiers, logs, L2->L1 messages). See [circuit limitations](#circuit-limitations). * We have not settled on the final constants, since we are still in a testing phase. You could find that certain compositions of nested private function calls (for example, call stacks that are dynamic in size, based on runtime data) could accumulate so many side-effects as to exceed transaction limits. Such transactions would then be unprovable. Please open an issue if you encounter this, as it will help us decide on adequate sizes for our constants. * Not all Noir cryptographic primitives work in public (AVM) functions. Signature verification (ECDSA secp256k1/r1), AES-128, Blake2s, and Blake3 are not supported. See [AVM Cryptographic Compatibility](/developers/testnet/docs/foundational-topics/advanced/circuits/avm_compatibility.md) for details and workarounds. * There are many features that we still want to implement. Check out GitHub and the forum for details. If you would like a feature, please open an issue on GitHub. ## WARNING[​](#warning "Direct link to WARNING") Do not use real, meaningful secrets on Aztec networks. Some privacy features are still in development, including ensuring a secure "zk" property. Since the Aztec stack is still being developed, there are no guarantees that real secrets will remain secret. ## Limitations[​](#limitations "Direct link to Limitations") There are plans to resolve all of the below. ### It is not audited[​](#it-is-not-audited "Direct link to It is not audited") None of the Aztec stack is audited. It is being iterated on every day. It will not be audited for quite some time. ### Under-constrained[​](#under-constrained "Direct link to Under-constrained") Some of our more complex circuits are still in development, so they are still under-constrained. #### What are the consequences?[​](#what-are-the-consequences "Direct link to What are the consequences?") Sound proofs are really only needed as a protection against malicious behavior, which we are not testing for at this stage. ### Keys and addresses may change in future rollup versions[​](#keys-and-addresses-may-change-in-future-rollup-versions "Direct link to Keys and addresses may change in future rollup versions") The key derivation scheme is documented and stable within the current rollup version, but it may change in future rollup upgrades. Applications should not hardcode assumptions about the specific derivation algorithm. Please open new discussions on [Discourse](https://discourse.aztec.network) or open issues on [GitHub](https://github.com/AztecProtocol/aztec-packages) if you have requirements that are not being met by the current key derivation scheme. ### No privacy-preserving queries to nodes[​](#no-privacy-preserving-queries-to-nodes "Direct link to No privacy-preserving queries to nodes") Ethereum has a notion of a "full node" which keeps up with the blockchain and stores the full chain state. Many users do not wish to run full nodes, so they rely on third-party "full-node-as-a-service" infrastructure providers who service blockchain queries from their users. This pattern is likely to develop in Aztec as well, except there is a problem: privacy. If a privacy-seeking user makes a query to a third-party full node, that user might leak data about who they are, about their historical network activity, or about their future intentions. One solution to this problem is "always run a full node", but pragmatically, not everyone will. To protect less-advanced users' privacy, research is underway to explore how a privacy-seeking user may request and receive data from a third-party node without revealing what that data is, nor who is making the request. ### Limited private data authentication[​](#limited-private-data-authentication "Direct link to Limited private data authentication") The PXE supports a `scopes` parameter that restricts which accounts' notes a function call can access. However, this is caller-specified: the app chooses its own scopes. There is no mandatory, protocol-enforced authorization layer where the PXE denies an app access to another app's private data. A wallet can restrict scope on behalf of the user, but this is not yet standardized or enforced by default. ### No client-side bytecode validation[​](#no-client-side-bytecode-validation "Direct link to No client-side bytecode validation") Public bytecode is validated at the protocol level when contract classes are registered (the Contract Class Registry verifies encoding and commitments). However, the PXE and wallets do not yet validate that the bytecode a user is about to execute matches their stated intentions (function signature and contract address). #### What are the consequences?[​](#what-are-the-consequences-1 "Direct link to What are the consequences?") If incorrect or malicious bytecode is executed, it could read private data from another contract and emit it publicly. Client-side bytecode validation is planned to close this gap. ### Insecure hashes[​](#insecure-hashes "Direct link to Insecure hashes") We are planning a full assessment of the protocol's hashes, including rigorous domain separation. #### What are the consequences?[​](#what-are-the-consequences-2 "Direct link to What are the consequences?") Collisions and other hash-related attacks might be possible. This is unlikely to cause problems at this early stage, but is a known area of ongoing work. ### New privacy standards are required[​](#new-privacy-standards-are-required "Direct link to New privacy standards are required") There are many [patterns](/developers/testnet/docs/resources/considerations/privacy_considerations.md) which can leak privacy, even on Aztec. Standards have not been developed yet to encourage best practices when designing private smart contracts. #### What are the consequences?[​](#what-are-the-consequences-3 "Direct link to What are the consequences?") For example, until community standards are developed to reduce the uniqueness of ["Tx Fingerprints"](/developers/testnet/docs/resources/considerations/privacy_considerations.md#function-fingerprints-and-tx-fingerprints), app developers might accidentally forfeit some function privacy. ## Smart contract limitations[​](#smart-contract-limitations "Direct link to Smart contract limitations") We will never be done with all the features we want to add to Aztec.nr. We have many features that we still want to implement. Please check out GitHub and open new issues with any feature requests you might have. ## Circuit limitations[​](#circuit-limitations "Direct link to Circuit limitations") ### Upper limits on function outputs and transaction outputs[​](#upper-limits-on-function-outputs-and-transaction-outputs "Direct link to Upper limits on function outputs and transaction outputs") Due to the rigidity of zk-SNARK circuits, there are upper bounds on the amount of computation a circuit can perform, and on the amount of data that can be passed into and out of a function. > Blockchain developers are no stranger to restrictive computational environments. Ethereum has gas limits, local variable stack limits, call stack limits, contract deployment size limits, log size limits, etc. Here are the current constants: constants ``` // TREES RELATED CONSTANTS pub global ARCHIVE_HEIGHT: u32 = 30; // 4-second blocks for 100 years. pub global VK_TREE_HEIGHT: u32 = 7; pub global FUNCTION_TREE_HEIGHT: u32 = 7; // The number of private functions in a contract is therefore 128. pub global NOTE_HASH_TREE_HEIGHT: u32 = 42; // 64 notes/tx (static because of base rollup insertion), 15tps, for 100 years. pub global PUBLIC_DATA_TREE_HEIGHT: u32 = 40; // Average of 16 updates/tx (guess), 15tps, 100 years. pub global NULLIFIER_TREE_HEIGHT: u32 = NOTE_HASH_TREE_HEIGHT; pub global L1_TO_L2_MSG_TREE_HEIGHT: u32 = 36; // 1024 messages per checkpoint, with 72 seconds per checkpoint, for 100 years. pub global OUT_HASH_TREE_HEIGHT: u32 = 5; // 32 (MAX_CHECKPOINTS_PER_EPOCH) checkpoints per epoch, each has 1 out hash. pub global ARTIFACT_FUNCTION_TREE_MAX_HEIGHT: u32 = FUNCTION_TREE_HEIGHT; // The number of unconstrained functions in a contract. Set to equal the number of private functions in a contract. pub global NULLIFIER_TREE_ID: Field = 0; pub global NOTE_HASH_TREE_ID: Field = 1; pub global PUBLIC_DATA_TREE_ID: Field = 2; pub global L1_TO_L2_MESSAGE_TREE_ID: Field = 3; pub global ARCHIVE_TREE_ID: Field = 4; pub global NOTE_HASH_TREE_LEAF_COUNT: u64 = 1 << (NOTE_HASH_TREE_HEIGHT as u64); pub global L1_TO_L2_MSG_TREE_LEAF_COUNT: u64 = 1 << (L1_TO_L2_MSG_TREE_HEIGHT as u64); pub global OUT_HASH_TREE_LEAF_COUNT: u32 = 1 << OUT_HASH_TREE_HEIGHT; // SUB-TREES RELATED CONSTANTS pub global NOTE_HASH_SUBTREE_HEIGHT: u32 = 6; pub global NULLIFIER_SUBTREE_HEIGHT: u32 = 6; pub global PUBLIC_DATA_SUBTREE_HEIGHT: u32 = 6; pub global L1_TO_L2_MSG_SUBTREE_HEIGHT: u32 = 10; pub global NOTE_HASH_SUBTREE_ROOT_SIBLING_PATH_LENGTH: u32 = NOTE_HASH_TREE_HEIGHT - NOTE_HASH_SUBTREE_HEIGHT; pub global NULLIFIER_SUBTREE_ROOT_SIBLING_PATH_LENGTH: u32 = NULLIFIER_TREE_HEIGHT - NULLIFIER_SUBTREE_HEIGHT; pub global L1_TO_L2_MSG_SUBTREE_ROOT_SIBLING_PATH_LENGTH: u32 = L1_TO_L2_MSG_TREE_HEIGHT - L1_TO_L2_MSG_SUBTREE_HEIGHT; // Maximum number of subtrees a L2ToL1Msg unbalanced tree can have. Used when calculating the out hash of a tx. pub global MAX_L2_TO_L1_MSG_SUBTREES_PER_TX: u32 = 3; // ceil(log2(MAX_L2_TO_L1_MSGS_PER_TX)) // "PER TRANSACTION" CONSTANTS pub global MAX_NOTE_HASHES_PER_TX: u32 = 1 << NOTE_HASH_SUBTREE_HEIGHT; pub global MAX_NULLIFIERS_PER_TX: u32 = 1 << NULLIFIER_SUBTREE_HEIGHT; pub global MAX_PRIVATE_CALL_STACK_LENGTH_PER_TX: u32 = 16; pub global MAX_ENQUEUED_CALLS_PER_TX: u32 = 32; pub global PROTOCOL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX: u32 = 1; // This is the fee_payer's fee juice balance. pub global MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX: u32 = (1 as u8 << PUBLIC_DATA_SUBTREE_HEIGHT as u8) as u32; pub global MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX: u32 = MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX - PROTOCOL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX; pub global MAX_PUBLIC_DATA_READS_PER_TX: u32 = 64; pub global MAX_L2_TO_L1_MSGS_PER_TX: u32 = 8; // Leave at 8, because it results in sha256 hashing in the Tx Base Rollup pub global MAX_PRIVATE_LOGS_PER_TX: u32 = MAX_NOTE_HASHES_PER_TX; pub global MAX_CONTRACT_CLASS_LOGS_PER_TX: u32 = 1; pub global MAX_NOTE_HASH_READ_REQUESTS_PER_TX: u32 = 64; pub global MAX_NULLIFIER_READ_REQUESTS_PER_TX: u32 = 64; // Key validation requests are not only for app-siloed _nullifier_ secret keys: app-siloed tagging shared secrets might require this mechanism, // hence why it's higher than you might expect (roughly (but not quite) enough for a tagging shared secret per private log + 1 nsk). pub global MAX_KEY_VALIDATION_REQUESTS_PER_TX: u32 = MAX_PRIVATE_LOGS_PER_TX; // "PER CALL" CONSTANTS pub global MAX_NOTE_HASHES_PER_CALL: u32 = 16; pub global MAX_NULLIFIERS_PER_CALL: u32 = 16; pub global MAX_PRIVATE_CALL_STACK_LENGTH_PER_CALL: u32 = 8; pub global MAX_ENQUEUED_CALLS_PER_CALL: u32 = MAX_ENQUEUED_CALLS_PER_TX; pub global MAX_L2_TO_L1_MSGS_PER_CALL: u32 = MAX_L2_TO_L1_MSGS_PER_TX; pub global MAX_PRIVATE_LOGS_PER_CALL: u32 = MAX_NOTE_HASHES_PER_CALL; pub global MAX_CONTRACT_CLASS_LOGS_PER_CALL: u32 = 1; pub global MAX_NOTE_HASH_READ_REQUESTS_PER_CALL: u32 = 16; pub global MAX_NULLIFIER_READ_REQUESTS_PER_CALL: u32 = 16; pub global MAX_KEY_VALIDATION_REQUESTS_PER_CALL: u32 = MAX_PRIVATE_LOGS_PER_CALL; ``` > [Source code: noir-projects/noir-protocol-circuits/crates/types/src/constants.nr#L33-L100](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr#L33-L100) #### What are the consequences?[​](#what-are-the-consequences-4 "Direct link to What are the consequences?") When you write an Aztec.nr function, there will be upper bounds on the following: * The number of public state reads and writes; * The number of note reads and nullifications; * The number of new notes that may be created; * The number of encrypted logs that may be emitted; * The number of unencrypted logs that may be emitted; * The number of L1->L2 messages that may be consumed; * The number of L2->L1 messages that may be submitted to L1; * The number of private function calls; * The number of public function calls that may be enqueued; Not only are there limits on a *per function* basis, there are also limits on a *per transaction* basis. **In particular, these *per-transaction* limits will limit transaction call stack depths**. This means if a function call results in a cascade of nested function calls, and each of those function calls outputs many state reads and writes, or logs, then all of that accumulated output data might exceed the per-transaction limits that we currently have. This would cause such transactions to fail. There are plans to relax some of this rigidity by providing many "sizes" of circuit. > **In the meantime**, if you encounter a per-transaction limit when testing, please open an issue to explain what you were trying to do - we would love to hear about it. And if you are feeling adventurous, you could modify the PXE to increase the limits. **However**, the limits cannot be increased indefinitely. Although we do anticipate that we will be able to increase them slightly, do not provide yourself with 1 million state transitions per transaction. That would be as unrealistic as artificially increasing Ethereum gas limits to 1 trillion. ## There is more[​](#there-is-more "Direct link to There is more") See the [GitHub issues](https://github.com/AztecProtocol/aztec-packages/issues) for all known bug fixes and features currently being worked on. --- # Privacy Considerations Privacy is a core value of Aztec Protocol. Keeping information private is difficult, and once information is leaked, it cannot be unleaked. This page outlines key privacy considerations that developers should understand when building applications on Aztec. ## What can Aztec keep private?[​](#what-can-aztec-keep-private "Direct link to What can Aztec keep private?") Aztec provides a set of tools to enable developers to build private smart contracts. The following can be kept private: **Private persistent state** Store state variables in an encrypted form, so that no one can see what those variables are, except those with the decryption key. **Private events and messages** Emit encrypted events, or encrypted messages from a private smart contract function. Only those with the decryption key will learn the message. **Private function execution** Execute a private function without the world knowing which function you've executed. **Private bytecode** The bytecode of private functions does not need to be distributed to the world; much like real-world contracts. danger Privacy is not guaranteed without care. Although Aztec provides the tools for private smart contracts, information can still be leaked unless you are careful. Aztec is still under development, so real-world, meaningful, valuable secrets *should not* be entrusted to the system. This page outlines some best practices to help you build privacy-preserving applications. *** ## Leaky practices[​](#leaky-practices "Direct link to Leaky practices") There are many caveats to the above. Since Aztec also enables interaction with the *public* world (public L2 functions and L1 functions), private information can be accidentally leaked if developers aren't careful. ### Crossing the private to public boundary[​](#crossing-the-private-to-public-boundary "Direct link to Crossing the private to public boundary") Any time a private function makes a call to a public function, information is leaked. Now, that might be perfectly fine in some use cases (it's up to the smart contract developer). Indeed, most interesting apps will require some public state. But let's have a look at some leaky patterns: * Calling a public function from a private function. The public function execution will be publicly visible. * Calling a public function from a private function and revealing the `msg_sender` of that call (the `msg_sender` will be publicly visible). You can hide the sender by using `self.enqueue_incognito(...)` instead of `self.enqueue(...)`, which sets `msg_sender` to a null address. The called function must use `maybe_msg_sender()` to handle this. * Passing arguments to a public function from a private function. All of those arguments will be publicly visible. * Calling an internal public function from a private function. The fact that the call originated from a private function of that same contract will be trivially known. * Emitting unencrypted events from a private function. The unencrypted event name and arguments will be publicly visible. * Sending L2->L1 messages from a private function. The entire message, and the resulting L1 function execution will all be publicly visible. ### Crossing the public to private boundary[​](#crossing-the-public-to-private-boundary "Direct link to Crossing the public to private boundary") If a public function sends a message to be consumed by a private function, the act of consuming that message might be leaked if not following recommended patterns. ### Timing of transactions[​](#timing-of-transactions "Direct link to Timing of transactions") Information about the nature of a transaction can be leaked based on the timing of that transaction. If a transaction is executed at 8am GMT, it's much less likely to have been made by someone in the USA. If there's a spike in transactions on the last day of every month, those might be salaries. These minor details are information that can disclose much more information about a user than the user might otherwise expect. Suppose that every time Alice sends Bob a private token, 1 minute later a transaction is always submitted to the tx pool with the same kind of 'fingerprint'. Alice might deduce that these transactions are automated reactions by Bob. (Here, 'fingerprint' is an intentionally vague term. It could be a public function call, or a private tx proof with a particular number of nonzero public inputs, or some other discernible pattern that Alice sees). In short, you should think about the *timing* of user transactions and how this might leak information. ### Function Fingerprints and Tx Fingerprints[​](#function-fingerprints-and-tx-fingerprints "Direct link to Function Fingerprints and Tx Fingerprints") A 'Function Fingerprint' is any data which is exposed by a function to the outside world. A 'Tx Fingerprint' is any data which is exposed by a tx to the outside world. We're interested in minimizing leakages of information from private txs. The leakiness of a Tx Fingerprint depends on the leakiness of its constituent functions' Function Fingerprints *and* on the appearance of the tx's Tx Fingerprint as a whole. For a private function (and by extension, for a private tx), the following information *could* be leaked (depending on the function, of course): * All calls to public functions. * The contract address of the private function (if it calls an internal public function). * This could be the address of the transactor themselves, if the calling contract is an account contract. * All arguments which are passed to public functions. * All calls to L1 functions (in the form of L2 -> L1 messages). * The contents of L2 -> L1 messages. * All public logs (topics and arguments). * The roots of all trees which have been read from. * The *number* of some ['side effects'](https://en.wikipedia.org/wiki/Side_effect_\(computer_science\)). Note hashes, nullifiers, and private logs are padded to hide their true counts, but the following remain visible: * \# public function calls * \# L2->L1 messages > Note: many of these were mentioned in the ["Crossing the private to public boundary"](#crossing-the-private-to-public-boundary) section. > Note: a transaction's Tx Fingerprint is the combined set of publicly observable data listed above (for example: the number of public function calls, the number of L2->L1 messages, the contents of public logs, and which tree roots were read). Anyone watching the L2 transaction pool can see this fingerprint for every transaction that is submitted, and transactions with distinctive fingerprints can be linked to specific contracts or to patterns of user behavior. #### Standardizing Fingerprints[​](#standardizing-fingerprints "Direct link to Standardizing Fingerprints") If each private function were to have a unique Fingerprint, then all private functions would be distinguishable from each other, and all of the efforts of the Aztec Protocol to enable private function execution would have been pointless. Standards need to be developed to encourage smart contract developers to adhere to a restricted set of Tx Fingerprints. For example, a standard might propose that the number of new note hashes, nullifiers, logs, etc. must always be equal, and must always equal a power of two. Such a standard would effectively group private functions and transactions into "privacy sets," where all functions and transactions in a particular privacy set would look indistinguishable from each other when executed. ### Data queries[​](#data-queries "Direct link to Data queries") It's not just the broadcasting of transactions to the network that can leak data. Ethereum has a notion of a "full node" which keeps up with the blockchain and stores the full chain state. Many users don't wish to run full nodes, so they rely on third-party "full-node-as-a-service" infrastructure providers, who service blockchain queries from their users. This pattern is likely to develop in Aztec as well, except there's a problem: privacy. If a privacy-seeking user makes a query to a third-party full node, that user might leak data about who they are, their historical network activity, or their future intentions. One solution to this problem is to always run a full node, but pragmatically, not everyone will. To protect less-advanced users' privacy, research is underway to explore how a privacy-seeking user may request and receive data from a third-party node without revealing what that data is, nor who is making the request. You should be aware of this avenue for private data leakage. **Whenever an app requests information from a node, the entity running that node is unlikely to be your user.** #### What kind of queries can be leaky?[​](#what-kind-of-queries-can-be-leaky "Direct link to What kind of queries can be leaky?") ##### Querying for up-to-date note sibling paths[​](#querying-for-up-to-date-note-sibling-paths "Direct link to Querying for up-to-date note sibling paths") To read a private state is to read a note from the note hash tree. To read a note is to prove existence of that note in the note hash tree. And to prove existence is to re-compute the root of the note hash tree using the leaf value, the leaf index, and the sibling path of that leaf. This computed root is then exposed to the world, as a way of saying "This note exists", or more precisely "This note has existed at least since this historical snapshot time". If an old historical snapshot is used, then that old historical root will be exposed, and this leaks some information about the nature of your transaction: it leaks that your note was created before the snapshot date. It shrinks the 'privacy set' of the transaction to a smaller window of time than the entire history of the network. So for maximal privacy, it's in a user's best interest to read from the very-latest snapshot of the data tree. Naturally, the note hash tree is continuously changing as new transactions take place and their new notes are appended. Most notably, the sibling path for every leaf in the tree changes every time a new leaf is appended. If a user runs their own node, there's no problem: they can query the latest sibling path for their note(s) from their own machine without leaking any information to the outside world. But if a user is not running their own node, they would need to query the very-latest sibling path of their note(s) from some third-party node. In order to query the sibling path of a leaf, the leaf's index needs to be provided as an argument. Revealing the leaf's index to a third party trivially reveals exactly the note(s) you're about to read. And since those notes were created in some prior transaction, the third party will be able to link you with that prior transaction. Suppose then that the third party also serviced the creator of said prior transaction: they will slowly be able to link more and more transactions, and gain more and more insight into a network which is meant to be private. We're researching cryptographic ways to enable users to retrieve sibling paths from third parties without revealing leaf indices. > \* Note: due to the non-uniformity of Aztec transactions, the 'privacy set' of a transaction might not be the entire set of transactions that came before. ##### Any query[​](#any-query "Direct link to Any query") Any query to a node leaks information to that node. We're researching cryptographic ways to enable users to query any data privately. --- # Glossary ### ACIR (Abstract Circuit Intermediate Representation)[​](#acir-abstract-circuit-intermediate-representation "Direct link to ACIR (Abstract Circuit Intermediate Representation)") ACIR bytecode is the compilation target of private functions. ACIR expresses arithmetic circuits and has no control flow: any control flow in functions is either unrolled (for loops) or flattened (by inlining and adding predicates). ACIR contains different types of opcodes including arithmetic operations, BlackBoxFuncCall (for efficient operations like hashing), Brillig opcodes (for unconstrained hints), and MemoryOp (for dynamic array access). Private functions compiled to ACIR are executed by the ACVM (Abstract Circuit Virtual Machine) and proved using Barretenberg. ### AVM (Aztec Virtual Machine)[​](#avm-aztec-virtual-machine "Direct link to AVM (Aztec Virtual Machine)") The Aztec Virtual Machine (AVM) executes the public section of a transaction. It is conceptually similar to the Ethereum Virtual Machine (EVM) but designed specifically for Aztec's needs. Public functions are compiled to AVM bytecode and executed by sequencers in the AVM. The AVM uses a flat memory model with tagged memory indexes to track maximum potential values and bit sizes. It supports control flow (if/else) and includes specific opcodes for blockchain operations like timestamp and address access, but doesn't allow arbitrary oracles for security reasons. ### Aztec[​](#aztec "Direct link to Aztec") Aztec is a privacy-first Layer 2 rollup on Ethereum. It supports smart contracts with both private & public state and private & public execution. `aztec` is a CLI tool (with an extensive set of parameters) that enables users to perform a wide range of tasks. It can: compile and test contracts, run a node, run a local network, execute tests, generate contract interfaces for javascript and more. Full reference [here](/developers/testnet/docs/cli/aztec_cli_reference.md). ### Aztec Wallet[​](#aztec-wallet "Direct link to Aztec Wallet") The Aztec Wallet is a CLI wallet, `aztec-wallet`, that allows a user to manage accounts and interact with an Aztec network. It includes a PXE. Full reference [here](/developers/testnet/docs/cli/aztec_wallet_cli_reference.md). ### `aztec-up`[​](#aztec-up "Direct link to aztec-up") `aztec-up` updates the local aztec executables to the latest version (default behavior) or to a specified version. ### Aztec.js[​](#aztecjs "Direct link to Aztec.js") A [Node package](https://www.npmjs.com/package/@aztec/aztec.js) to help make Aztec dApps. Read more and review the source code [here](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/yarn-project/aztec.js). ### Aztec.nr[​](#aztecnr "Direct link to Aztec.nr") [Aztec.nr](https://github.com/AztecProtocol/aztec-packages/tree/v5.0.0-rc.2/noir-projects/aztec-nr) is a Noir framework for writing Aztec smart contracts that abstracts away state management, handling note generation, state trees, and more. Read more and review the source code [here](https://aztec.nr). ### Barretenberg[​](#barretenberg "Direct link to Barretenberg") Aztec's cryptography back-end. Refer to the graphic at the top of [this page](https://medium.com/aztec-protocol/explaining-the-network-in-aztec-network-166862b3ef7d) to see how it fits in the Aztec architecture. Barretenberg's source code can be found [here](https://github.com/AztecProtocol/barretenberg). ### bb / bb.js[​](#bb--bbjs "Direct link to bb / bb.js") `bb` (CLI) and its corresponding `bb.js` (node module) are tools that prove and verify circuits. It also has helpful functions such as: writing solidity verifier contracts, checking a witness, and viewing a circuit's gate count. ### Commitment[​](#commitment "Direct link to Commitment") A cryptographic commitment is a hash of some data (plus randomness) that hides the original value but allows you to later prove you committed to that specific value, by proving knowledge of a valid preimage, without being able to change it. In Aztec, a commitment refers to a cryptographic hash of a note. Rather than storing entire notes in a data tree, note commitments (hashes of the notes) are stored in a merkle tree called the note hash tree. Users prove that they have the note pre-image information when they update private state in a contract. This allows the network to verify the existence of private data without revealing its contents. ### Merkle Tree[​](#merkle-tree "Direct link to Merkle Tree") A Merkle tree is a binary tree data structure where adjacent nodes are hashed together recursively to produce a single node called the root hash. Merkle trees in Aztec are used to store cryptographic commitments. They are used across five Aztec Merkle trees: the note hash tree (stores commitments to private notes), the nullifier tree (stores nullifiers for spent notes), the public data tree (stores public state), the contract tree, and the archive tree. All trees use domain-separated Poseidon2 hashing with specific tree identifiers and layer separation to ensure security and prevent cross-tree attacks. ### `nargo`[​](#nargo "Direct link to nargo") With `nargo`, you can start new projects, compile, execute, and test your Noir programs. The Aztec installer ships its own pinned `nargo` and exposes it as the `aztec-nargo` wrapper on `PATH` (bare `nargo` is intentionally not provided so it does not shadow your own install). For Aztec contract work, prefer `aztec compile` and `aztec test`; for plain Noir commands, use `aztec-nargo` (or your own `nargo` install). You can find more information in the nargo installation docs [here](https://noir-lang.org/docs/getting_started/quick_start#installation) and the nargo command reference [here](https://noir-lang.org/docs/reference/nargo_commands). ### Noir[​](#noir "Direct link to Noir") Noir is a Domain Specific Language (DSL) for SNARK proving systems. It is used for writing smart contracts in Aztec because private functions on Aztec are implemented as SNARKs to support privacy-preserving operations. ### Noir Language Server[​](#noir-language-server "Direct link to Noir Language Server") The Noir Language Server can be used in vscode to facilitate writing programs in Noir by providing syntax highlighting, circuit introspection and an execution interface. The Noir LSP addon allows the dev to choose their tool, nargo or `aztec`, when writing a pure Noir program or an Aztec smart contract. You can find more info about the LSP [in the Noir docs](https://noir-lang.org/docs/tooling/language_server). ### Node[​](#node "Direct link to Node") A node is a computer running Aztec software that participates in the Aztec network. A specific type of node is a sequencer. Nodes run the public execution environment (AVM), validate proofs, and maintain the five state Merkle trees (note hash, nullifier, public state, L1-L2 message, and archive trees). The Aztec testnet rolls up to Ethereum Sepolia. To run your own node see [here](/operate/operators.md). ### Note[​](#note "Direct link to Note") In Aztec, a note is like an envelope containing private data. A commitment (hash) of this note is stored in an append-only Merkle tree maintained by all nodes in the network. Notes can be encrypted to be shared with other users. The data in a note represents a variable's state at a specific point in time. ### Note Discovery[​](#note-discovery "Direct link to Note Discovery") Note discovery refers to the process of a user identifying and decrypting the encrypted notes that belong to them. Aztec uses a note tagging system where senders tag encrypted onchain logs containing notes in a way that only the sender and recipient can identify. The tag is derived from a shared secret and an index (a shared counter that increments each time the sender creates a note for the recipient). This allows users to efficiently find their notes without brute force decryption or relying on offchain communication. ### Nullifier[​](#nullifier "Direct link to Nullifier") A nullifier is a unique value that, once posted publicly, proves something has been used or consumed without revealing what that thing was. In the context of Aztec, a nullifier is derived from a note and signifies the note has been "spent" or consumed without revealing which specific note was spent. When a note is updated or spent in Aztec, the protocol creates a nullifier from the note data using the note owner's nullifier key. This nullifier is inserted into the nullifier Merkle tree. The nullifier mechanism prevents double-spending while maintaining privacy by not requiring deletion of the original note commitment, which would leak information. ### Partial Notes[​](#partial-notes "Direct link to Partial Notes") Partial notes are a concept that allows users to commit to an encrypted value, and allows a counterparty to update that value without knowing the specific details of the encrypted value. They are notes that are created in a private function with values that are not yet considered finalized (e.g., `amount` in a `UintNote`). The partial note commitment is computed using multi scalar multiplication on an elliptic curve, then passed to a public function where another party can add value to the note without knowing its private contents. This enables use cases like private fee payments, DEX swaps, and lending protocols. ### Programmable Privacy[​](#programmable-privacy "Direct link to Programmable Privacy") Aztec achieves programmable privacy through its hybrid architecture that supports both private and public smart contract execution. Private functions run client-side with zero-knowledge proofs, while public functions run onchain. This allows developers to program custom privacy logic, choosing what data remains private and what becomes public, with composability between private and public state and execution contexts. ### Provers[​](#provers "Direct link to Provers") The Prover in a ZK system is the entity proving they have knowledge of a valid witness that satisfies a statement. In the context of Aztec, this is the entity that creates the proof that some computation was executed correctly. Here, the statement would be "I know the inputs and outputs that satisfy the requirements for the computation, and I did the computation correctly." Aztec launched with a fully permissionless proving network that anyone can participate in. The proving network produces proofs for valid rollup state transitions. How this works will be discussed via a future RFP process on Discourse, similarly to the Sequencer RFP. ### Proving Key[​](#proving-key "Direct link to Proving Key") A key that is used to generate a proof. In the case of Aztec, these are compiled from Noir smart contracts. ### Private Execution Environment (PXE)[​](#private-execution-environment-pxe "Direct link to Private Execution Environment (PXE)") The private execution environment is where private computation occurs. This is local to your device or browser. Read more [here](/developers/testnet/docs/foundational-topics/pxe.md). ### Local Network[​](#local-network "Direct link to Local Network") The local network is a development Aztec network that runs on your machine and interacts with a development Ethereum node. It allows you to develop and deploy Noir smart contracts without interacting with testnet or mainnet. Included in the local network: * Local Ethereum network (Anvil) * Deployed Aztec protocol contracts (for L1 and L2) * A set of test accounts with some test tokens to pay fees * Development tools to compile contracts and interact with the network (`aztec` and `aztec-wallet`) ### Sequencer[​](#sequencer "Direct link to Sequencer") A sequencer is a specialized node that is generally responsible for: * Selecting pending transactions from the mempool * Ordering transactions into a block * Verifying all private transaction proofs and executing all public transactions to check their validity * Computing the ROLLUP\_BLOCK\_REQUEST\_DATA * Computing state updates for messages between L2 & L1 * Broadcasting the ROLLUP\_BLOCK\_REQUEST\_DATA to the prover network via the proof pool for parallelizable computation. * Building a rollup proof from completed proofs in the proof pool * Tagging the pending block with an upgrade signal to facilitate forks * Publishing completed block with proofs to Ethereum as an ETH transaction Aztec will be launched with a fully permissionless sequencer network that anyone can participate in. How this works is being discussed actively in the [Discourse forum](https://discourse.aztec.network/t/request-for-proposals-decentralized-sequencer-selection/350/). Once this discussion process is completed, we will update the glossary and documentation with specifications and instructions for how to run. Previously in [Aztec Connect](https://medium.com/aztec-protocol/sunsetting-aztec-connect-a786edce5cae) there was a single sequencer, and you can find the Typescript reference implementation called Falafel [here](https://github.com/AztecProtocol/aztec-connect/tree/master/yarn-project/falafel). ### Smart Contracts[​](#smart-contracts "Direct link to Smart Contracts") Programs that run on the Aztec network are called smart contracts, similar to [programs](https://ethereum.org/en/developers/docs/smart-contracts/) that run on Ethereum. However, these will be written in the [Noir](https://noir-lang.org/) programming language, and may optionally include private state and private functions. ### Statement[​](#statement "Direct link to Statement") A statement in Aztec's zero-knowledge context refers to the public assertion being proved about a private computation. For example, a statement might be "I know the inputs and outputs that satisfy the requirements for this computation, and I executed the computation correctly." The statement defines what is being proven without revealing the private details (the witness) that prove it. In Aztec, statements typically involve proving correct execution of private functions, valid note ownership, or proper state transitions. ### Verifier[​](#verifier "Direct link to Verifier") The entity responsible for verifying the validity of a ZK proof. In the context of Aztec, this is: * **The sequencers**: verify that private functions were executed correctly. * **The Ethereum L1 smart contract**: verifies batches of transactions were executed correctly. ### Verification Key[​](#verification-key "Direct link to Verification Key") A key that is used to verify the validity of a proof generated from a proving key from the same smart contract. ### Witness[​](#witness "Direct link to Witness") In the context of Aztec's zero-knowledge proofs, a witness refers to the private inputs and intermediate values that satisfy the constraints of a circuit. When executing a private function, the ACVM generates the witness of the execution - the complete set of values that prove the computation was performed correctly. The witness includes both the secret inputs provided by the user and all intermediate computational steps, but is never revealed publicly. Only a cryptographic proof of the witness's validity is shared. ### Zero-knowledge (ZK) proof[​](#zero-knowledge-zk-proof "Direct link to Zero-knowledge (ZK) proof") Zero-knowledge proofs in Aztec are cryptographic proofs that allow someone to prove they know certain information or have performed a computation correctly without revealing the underlying data. Aztec uses various ZK-SNARK protocols including UltraPlonk and Honk. These proofs enable private execution where users can prove they executed a private function correctly and that they own certain notes, without revealing the function inputs, note contents, or internal computation details. The proofs are verified onchain to ensure the integrity of private state transitions. --- # 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. ## 5.0.0-rc.2[​](#500-rc2 "Direct link to 5.0.0-rc.2") ### \[Aztec.js] `getPublicEvents` is now cursor-paginated[​](#aztecjs-getpublicevents-is-now-cursor-paginated "Direct link to aztecjs-getpublicevents-is-now-cursor-paginated") `getPublicEvents` returns a single page of events (at most `MAX_LOGS_PER_TAG`, the node's per-tag page size) and pages instead of the `maxLogsHit` flag, which didn't provide any way to fetch the next page of events: * The result's `maxLogsHit` boolean is replaced by `nextCursor`. When `nextCursor` is present, more events might exist; pass it as the next query's `afterEvent` to fetch the following page. When it is absent, the range is exhausted. * The filter's `afterLog` cursor is renamed to `afterEvent`. * Both cursors are the new `EventCursor` type (exported from `@aztec/aztec.js/events`), not the node-layer `LogCursor`. **Migration:** ``` - const { events, maxLogsHit } = await getPublicEvents(node, MyContract.events.MyEvent, { contractAddress }); + // One page: + const { events, nextCursor } = await getPublicEvents(node, MyContract.events.MyEvent, { contractAddress }); + + // All events: + const all = []; + let afterEvent; + do { + const page = await getPublicEvents(node, MyContract.events.MyEvent, { contractAddress, afterEvent }); + all.push(...page.events); + afterEvent = page.nextCursor; + } while (afterEvent); ``` **Impact**: Reading `maxLogsHit` or passing `afterLog` no longer compiles. Previously a single call was silently capped at `MAX_LOGS_PER_TAG` events with no usable way to continue, so the old API was unusable anyway. You can now page through the full set with `afterEvent`/`nextCursor`. ### \[PXE] Sender and shared-secret registration unified into `TaggingSecretSource`[​](#pxe-sender-and-shared-secret-registration-unified-into-taggingsecretsource "Direct link to pxe-sender-and-shared-secret-registration-unified-into-taggingsecretsource") The PXE methods for registering tagging-secret sources have been replaced by a single set that takes a `TaggingSecretSource` discriminated union. `registerSender`/`getSenders`/`removeSender` and `registerSharedSecret`/`removeSharedSecret` are gone; use `registerTaggingSecretSource`/`removeTaggingSecretSource`/`getTaggingSecretSources` instead. The `Wallet` interface (`wallet.registerSender`, `getAddressBook`) is unchanged, so this only affects code that talks to a `PXE` instance directly. | Before | After | | --------------------------------------------- | ---------------------------------------------------------------------------------- | | `pxe.registerSender(address)` | `pxe.registerTaggingSecretSource({ kind: 'address-derived', sender: address })` | | `pxe.removeSender(address)` | `pxe.removeTaggingSecretSource({ kind: 'address-derived', sender: address })` | | `pxe.getSenders()` | `pxe.getTaggingSecretSources({ kind: 'address-derived' })` | | `pxe.registerSharedSecret(recipient, secret)` | `pxe.registerTaggingSecretSource({ kind: 'arbitrary-secret', recipient, secret })` | | `pxe.removeSharedSecret(recipient, secret)` | `pxe.removeTaggingSecretSource({ kind: 'arbitrary-secret', recipient, secret })` | ### \[Aztec.js] Unchecked `AztecAddress` constructors renamed with an `Unsafe` suffix[​](#aztecjs-unchecked-aztecaddress-constructors-renamed-with-an-unsafe-suffix "Direct link to aztecjs-unchecked-aztecaddress-constructors-renamed-with-an-unsafe-suffix") The synchronous `AztecAddress` constructors that build an address from a raw value do not verify that the value is a valid address (the x-coordinate of a point on the Grumpkin curve, which is what allows it to be encrypted to). An invalid value is accepted silently and only fails later, when a transaction is sent. To make this obvious at the call site, they now carry an `Unsafe` suffix: | Before | After | | ------------------------- | ------------------------------- | | `AztecAddress.fromField` | `AztecAddress.fromFieldUnsafe` | | `AztecAddress.fromBigInt` | `AztecAddress.fromBigIntUnsafe` | | `AztecAddress.fromNumber` | `AztecAddress.fromNumberUnsafe` | | `AztecAddress.fromString` | `AztecAddress.fromStringUnsafe` | **Migration:** ``` - const address = AztecAddress.fromBigInt(123n); + const address = AztecAddress.fromBigIntUnsafe(123n); ``` For a random, genuinely valid address in tests use `AztecAddress.random()`, and to check an untrusted value use `address.isValid()`. The serialization constructors `fromBuffer` and `fromFields` keep their names (they are part of the (de)serialization interface and read addresses from already-validated data), but their docs now note that they perform no validation either. ### Cross-contract utility calls now have a `msg_sender`[​](#cross-contract-utility-calls-now-have-a-msg_sender "Direct link to cross-contract-utility-calls-now-have-a-msg_sender") A utility function called by another contract (utility to utility, or private to utility) can read the calling contract's address via `self.msg_sender()`, mirroring private and public functions. A top-level utility call (e.g. invoked directly by a wallet or dapp) has no caller: `self.msg_sender()` panics, and `self.context.maybe_msg_sender()` returns `Option::none()`. `msg_sender` is only set for cross-contract calls, where it is taken from the call graph and so cannot be forged. A directly-invoked utility (from a wallet or dapp) has no verifiable caller, so it exposes none rather than trusting a value the caller could pick freely. #### \[Aztec.nr] `ExecuteUtilityOptions` gains `with_from` to simulate a cross-contract caller in tests[​](#aztecnr-executeutilityoptions-gains-with_from-to-simulate-a-cross-contract-caller-in-tests "Direct link to aztecnr-executeutilityoptions-gains-with_from-to-simulate-a-cross-contract-caller-in-tests") In `TestEnvironment`, use `execute_utility_opts` with the new `ExecuteUtilityOptions::with_from` builder method to set the `msg_sender` a utility observes, simulating a cross-contract caller without routing through an actual nested call (by default it observes no caller): ``` let secret = env.execute_utility_opts( ExecuteUtilityOptions::new().with_from(caller), Registry::at(registry_address).get_app_siloed_secret(sender, recipient, mode), ); ``` ### \[Prover Node JSON-RPC] Prover API moved to the admin endpoint; `getL2Tips`/`getWorldStateSyncStatus` removed[​](#prover-node-json-rpc-prover-api-moved-to-the-admin-endpoint-getl2tipsgetworldstatesyncstatus-removed "Direct link to prover-node-json-rpc-prover-api-moved-to-the-admin-endpoint-getl2tipsgetworldstatesyncstatus-removed") The prover node's JSON-RPC methods (`prover_*`) have moved off the public node RPC server and onto the admin RPC server. They now require the admin API key and are served on the admin port (8880) instead of the public port (8080). In addition, `prover_getL2Tips` and `prover_getWorldStateSyncStatus` have been removed from the prover API. They duplicated data already served by the node: use `aztec_getChainTips` (same shape as the old `getL2Tips`) and `aztec_getWorldStateSyncStatus` instead. If you call the prover RPC directly (e.g. via `curl`), point at the admin endpoint with the API key and use the remaining methods: * `prover_startProof` — schedule proving for an epoch * `prover_getJobs` — list proving jobs A client factory `createProverNodeAdminClient(url, versions?, fetch?, apiKey?)` is now exported from `@aztec/stdlib/interfaces/server`, and the CLI exposes `aztec prover start-proof --epoch --admin-url --api-key ` and `aztec prover get-jobs` (the API key defaults to `AZTEC_ADMIN_API_KEY`). ### \[Aztec.nr] `ContractInstance.contract_class_id` renamed to `original_contract_class_id`[​](#aztecnr-contractinstancecontract_class_id-renamed-to-original_contract_class_id "Direct link to aztecnr-contractinstancecontract_class_id-renamed-to-original_contract_class_id") The `contract_class_id` field of the `ContractInstance` struct (returned by `get_contract_instance`) has been renamed to `original_contract_class_id`. The struct is the contract's *address preimage*, so this field is the class id the contract was deployed with: for contracts whose class was later updated via the `ContractInstanceRegistry`, it is NOT the class currently executing. The rename makes that explicit. **Migration:** ``` let instance = get_contract_instance(address); - let class_id = instance.contract_class_id; + let class_id = instance.original_contract_class_id; ``` Note that this value is not available during public execution, which only has access to the *current* contract class. ### \[Aztec.nr] `get_contract_instance_class_id_avm` renamed to `get_contract_instance_current_class_id_avm`[​](#aztecnr-get_contract_instance_class_id_avm-renamed-to-get_contract_instance_current_class_id_avm "Direct link to aztecnr-get_contract_instance_class_id_avm-renamed-to-get_contract_instance_current_class_id_avm") The AVM contract-instance class id getter has been renamed to make explicit that it returns the *current* class id, i.e. it reflects updates performed via the `ContractInstanceRegistry`. **Migration:** ``` - use aztec::oracle::get_contract_instance::get_contract_instance_class_id_avm; + use aztec::oracle::get_contract_instance::get_contract_instance_current_class_id_avm; - let class_id = get_contract_instance_class_id_avm(address); + let class_id = get_contract_instance_current_class_id_avm(address); ``` ### \[Aztec.nr] `for_each` visits elements in order; removing during iteration no longer supported[​](#aztecnr-for_each-visits-elements-in-order-removing-during-iteration-no-longer-supported "Direct link to aztecnr-for_each-visits-elements-in-order-removing-during-iteration-no-longer-supported") `CapsuleArray::for_each` and `EphemeralArray::for_each` previously iterated backwards (from the last element to the first) so that the callback could safely remove the current element. They now visit elements in order, from first to last, as is usually expected in other languages. Structurally mutating the array (e.g. via `push` or `remove`) from inside the callback is no longer supported. For `EphemeralArray`, replace remove-during-iteration with `filter`: ``` - array.for_each(|index, value| { - if should_remove(value) { - array.remove(index); - } - }); + let kept = array.filter(|value| !should_remove(value)); ``` `filter` collects the kept elements into a fresh array at a new slot. If the original slot matters (e.g. a `TransientArray` slot shared with other call frames), rebuild it from the filtered result: ``` let kept = array.filter(|value| !should_remove(value)); let _ = array.clear(); kept.for_each(|_index, value| array.push(value)); ``` `EphemeralArray`'s are cheap and by nature not persistent though, so in most cases you probably can just work with the new copy instead of going through this hassle. `CapsuleArray` has no `filter`, so iterate manually, backwards. Removing the current element is safe in a backward loop because it only shifts elements at higher indices: ``` let mut i = array.len(); while i > 0 { i -= 1; if should_remove(array.get(i)) { array.remove(i); } } ``` ## 5.0.0-rc.1[​](#500-rc1 "Direct link to 5.0.0-rc.1") ### \[Aztec.js] Prefunded local network test accounts are now initializerless[​](#aztecjs-prefunded-local-network-test-accounts-are-now-initializerless "Direct link to \[Aztec.js] Prefunded local network test accounts are now initializerless") The genesis-funded test accounts in the local network (sandbox), returned by `getInitialTestAccountsData()`, are now initializerless Schnorr accounts (`schnorr_initializerless`). An initializerless account has no onchain deployment transaction: its address commits to the signing public key (through `immutables_hash`) and its contract state is materialized locally in the PXE, so these accounts are usable right away. Because their address is derived differently from a regular Schnorr account, register them with `createSchnorrInitializerlessAccount` rather than `createSchnorrAccount`. **Migration:** ``` const [alice] = await getInitialTestAccountsData(); - await wallet.createSchnorrAccount(alice.secret, alice.salt); + await wallet.createSchnorrInitializerlessAccount(alice.secret, alice.salt); ``` ### \[Aztec.js] `AccountContract` interface adds `getImmutablesHash()`[​](#aztecjs-accountcontract-interface-adds-getimmutableshash "Direct link to aztecjs-accountcontract-interface-adds-getimmutableshash") The `AccountContract` interface now declares `getImmutablesHash(): Promise`, which returns the hash of the account's immutable instantiation parameters committed into its address, or `undefined` if the feature is not used. `getAccountContractAddress()` and `AccountManager.create()` call it to derive the address when an `immutablesHash` is not passed explicitly, so the address of an initializerless account is now resolved from the contract itself. Account contracts that extend `DefaultAccountContract` inherit a default implementation that returns `undefined` and need no changes. ### \[Aztec.js] Wallets validate declared gas limits against the network's per-tx admission limit[​](#aztecjs-wallets-validate-declared-gas-limits-against-the-networks-per-tx-admission-limit "Direct link to \[Aztec.js] Wallets validate declared gas limits against the network's per-tx admission limit") Wallets now reject a transaction whose declared `gasLimits` exceed the network's per-tx admission limit (the node-advertised `txsLimits.gas`), throwing before the tx is sent — e.g. `Declared DA gas limit (X) exceeds the maximum this network allows per tx (Y)`. When you declare no gas limits, the wallet fills in the network's admission limits for you. This mirrors the node's inbound `GasLimitsValidator`, surfacing the rejection locally instead of on submission. **Impact**: Transactions that previously over-declared gas (and were silently skipped by the proposer) now fail fast with a descriptive error. Declare limits at or below `txsLimits.gas`, or declare none and let the wallet fill them in. ### \[Aztec.js] `estimateGas` / `estimatedGasPadding` simulate options and the `estimatedGas` result field removed[​](#aztecjs-estimategas--estimatedgaspadding-simulate-options-and-the-estimatedgas-result-field-removed "Direct link to aztecjs-estimategas--estimatedgaspadding-simulate-options-and-the-estimatedgas-result-field-removed") The `estimateGas` and `estimatedGasPadding` fee options are gone, and the `estimatedGas` field on simulation results is replaced by `gasUsed` (the raw gas the simulation consumed). Apps that want explicit gas limits read `gasUsed` and pad it themselves; otherwise the wallet fills in the network's admission limits automatically. **Migration:** ``` - const { estimatedGas } = await contract.methods.foo(args).simulate({ - from, - fee: { estimateGas: true, estimatedGasPadding: 0.1 }, - }); - const gasLimits = estimatedGas.gasLimits; + const { gasUsed } = await contract.methods.foo(args).simulate({ from, includeMetadata: true }); + const gasLimits = gasUsed.totalGas.mul(1.1); // pad yourself ``` ### \[Aztec.js] `getGasLimits` moved to `@aztec/wallet-sdk` and is no longer exported from `@aztec/aztec.js`[​](#aztecjs-getgaslimits-moved-to-aztecwallet-sdk-and-is-no-longer-exported-from-aztecaztecjs "Direct link to aztecjs-getgaslimits-moved-to-aztecwallet-sdk-and-is-no-longer-exported-from-aztecaztecjs") `getGasLimits` is no longer exported from `@aztec/aztec.js`. It now lives in `@aztec/wallet-sdk/base-wallet`, takes the simulated `gasUsed` and the network's per-tx admission limit, and clamps the padded estimates to it. If the simulated usage already exceeds the network limit, it throws immediately rather than returning a limit the node would reject. **Migration:** ``` - import { getGasLimits } from '@aztec/aztec.js'; - const { gasLimits, teardownGasLimits } = getGasLimits(simulationResult, 0.1); + import { getGasLimits } from '@aztec/wallet-sdk/base-wallet'; + const { txsLimits } = await node.getNodeInfo(); + const { gasLimits, teardownGasLimits } = getGasLimits(simulationResult.gasUsed, Gas.from(txsLimits.gas), 0.1); ``` ### \[Aztec.js / PXE] `NodeInfo.txsLimits` is now required[​](#aztecjs--pxe-nodeinfotxslimits-is-now-required "Direct link to aztecjs--pxe-nodeinfotxslimits-is-now-required") `NodeInfo` now carries a required `txsLimits` field: every node advertises the maximum gas a single tx may declare (`{ gas: { daGas, l2Gas } }`) and wallets rely on it for fallback gas limits. Clients built against this version cannot talk to nodes that predate the field. ### \[Aztec.js] `GasSettings.fallback` requires explicit `gasLimits`[​](#aztecjs-gassettingsfallback-requires-explicit-gaslimits "Direct link to aztecjs-gassettingsfallback-requires-explicit-gaslimits") `GasSettings.fallback` no longer supplies a default value for `gasLimits`. Callers must pass the network's per-tx admission limit explicitly — read it from the node's `txsLimits.gas`. **Migration:** ``` - const settings = GasSettings.fallback({ maxFeesPerGas }); + const { txsLimits } = await node.getNodeInfo(); + const settings = GasSettings.fallback({ gasLimits: Gas.from(txsLimits.gas), maxFeesPerGas }); ``` ### \[Aztec.js / stdlib] Removed legacy fallback gas constants[​](#aztecjs--stdlib-removed-legacy-fallback-gas-constants "Direct link to \[Aztec.js / stdlib] Removed legacy fallback gas constants") The following exports have been removed from `@aztec/stdlib`: * `APPROXIMATE_MAX_DA_GAS_PER_BLOCK` * `FALLBACK_TEARDOWN_L2_GAS_LIMIT` * `FALLBACK_TEARDOWN_DA_GAS_LIMIT` **Impact**: Any code that imported these symbols must switch to the live node-advertised limits via the node's `txsLimits.gas`. ### \[Aztec.nr] `messages::message_delivery` module moved to `messages::delivery`[​](#aztecnr-messagesmessage_delivery-module-moved-to-messagesdelivery "Direct link to aztecnr-messagesmessage_delivery-module-moved-to-messagesdelivery") The `message_delivery` module has been renamed to `delivery`. Update imports accordingly: ``` - use aztec::messages::message_delivery::MessageDelivery; + use aztec::messages::delivery::MessageDelivery; ``` ### \[Node JSON-RPC] Method prefixes changed to `aztec_*` and `aztecAdmin_*`[​](#node-json-rpc-method-prefixes-changed-to-aztec_-and-aztecadmin_ "Direct link to node-json-rpc-method-prefixes-changed-to-aztec_-and-aztecadmin_") All Aztec node JSON-RPC method prefixes have changed: * `node_*` → `aztec_*` (public node methods, port 8080) * `nodeAdmin_*` → `aztecAdmin_*` (admin methods, port 8880) * `nodeDebug_*` → `aztecDebug_*` (debug methods, port 8080, local-network or `--node-debug` only) * `p2p_*` namespace removed; P2P queries are on `aztec_*`: `getPeers`, `getCheckpointAttestationsForSlot`, `getProposalsForSlot` * New archiver sync helpers on `aztec_*`: `getL1Constants`, `getSyncedL2SlotNumber`, `getSyncedL2EpochNumber`, `getSyncedL1Timestamp` If you call the node RPC directly (e.g. via `curl` or a custom client), update all method names accordingly. Clients created via `createAztecNodeClient`, `createAztecNodeAdminClient`, and `createAztecNodeDebugClient` are updated automatically. ### \[Aztec.nr] `get_pending_tagged_logs` oracle interface updated (oracle version 28)[​](#aztecnr-get_pending_tagged_logs-oracle-interface-updated-oracle-version-28 "Direct link to aztecnr-get_pending_tagged_logs-oracle-interface-updated-oracle-version-28") The `aztec_utl_getPendingTaggedLogs` oracle now takes an additional `provided_secrets` parameter of type `EphemeralArray`. This lets apps pass tagging secrets that PXE cannot derive on its own (e.g. handshake-derived secrets) alongside the secrets PXE manages internally. ### \[Aztec.nr] `set_sender_for_tags` oracle removed[​](#aztecnr-set_sender_for_tags-oracle-removed "Direct link to aztecnr-set_sender_for_tags-oracle-removed") The `set_sender_for_tags` oracle has been removed. Contracts that used it to override the sender for discovery tag derivation should now use the `with_sender` builder method on `MessageDelivery`: ``` - use aztec::oracle::notes::set_sender_for_tags; + use aztec::messages::delivery::MessageDelivery; - unsafe { set_sender_for_tags(some_address) }; - note.deliver(MessageDelivery::onchain_constrained()); + note.deliver(MessageDelivery::onchain_constrained().with_sender(some_address)); ``` When `with_sender` is not called, `MessageDelivery` uses the wallet-supplied default sender. The wallet SDK supplies that default from the transaction's `from` address, with an optional `sendMessagesAs` override for flows that have no signing account (e.g. self-paid deploys, which `DeployAccountMethod` sets automatically). Account contracts therefore no longer need to call `set_sender_for_tags(self.address)` in their entrypoints: those calls have been removed from the standard Schnorr and ECDSA account contracts, and you can drop them — along with the old `get` → `set(self.address)` → work → `set(prev)` save/restore idiom in constructors — from any custom account contract. ### \[Aztec.nr] `MessageDelivery` API syntax change[​](#aztecnr-messagedelivery-api-syntax-change "Direct link to aztecnr-messagedelivery-api-syntax-change") `MessageDelivery` variants are now accessed via constructor functions instead of dot notation: ``` - MessageDelivery.OFFCHAIN + MessageDelivery::offchain() - MessageDelivery.ONCHAIN_UNCONSTRAINED + MessageDelivery::onchain_unconstrained() - MessageDelivery.ONCHAIN_CONSTRAINED + MessageDelivery::onchain_constrained() ``` ### \[Aztec.js] `getTxReceipt` returns a lifecycle union and takes `GetTxReceiptOptions`[​](#aztecjs-gettxreceipt-returns-a-lifecycle-union-and-takes-gettxreceiptoptions "Direct link to aztecjs-gettxreceipt-returns-a-lifecycle-union-and-takes-gettxreceiptoptions") `AztecNode.getTxReceipt` now takes an optional `GetTxReceiptOptions` argument and returns a `PendingTxReceipt | DroppedTxReceipt | MinedTxReceipt` union instead of a single `TxReceipt` class. Mined-only fields (`transactionFee`, `blockHash`, `blockNumber`, `txIndexInBlock`, and the execution result) are only guaranteed once the transaction is mined, so narrow with `isMined()`, `isPending()`, or `isDropped()` before reading variant-specific fields. The full `TxEffect` is attached only when you request it via `{ includeTxEffect: true }`. `AztecNode.getTxEffect` is deprecated. Replace direct calls with `getTxReceipt(txHash, { includeTxEffect: true })` and read the `.txEffect` field. `TxReceipt.empty()` and `TxReceipt.schema` no longer exist, since `TxReceipt` is now a union type rather than a class. Use `DroppedTxReceipt.empty()` and `TxReceiptSchema` instead. ``` - const effect = await node.getTxEffect(txHash); - if (effect) { - console.log(effect.data.nullifiers.length); - } + const receipt = await node.getTxReceipt(txHash, { includeTxEffect: true }); + if (receipt.isMined() && receipt.txEffect) { + console.log(receipt.txEffect.nullifiers.length); + } - const empty = TxReceipt.empty(); - const schema = TxReceipt.schema; + const empty = DroppedTxReceipt.empty(); + const schema = TxReceiptSchema; ``` **Impact**: This is a breaking change to the public node RPC with no wire back-compat. Callers that read mined fields off a bare receipt must narrow with the `is*` guards first, callers of `getTxEffect` should migrate to `getTxReceipt`, and references to `TxReceipt.empty()` / `TxReceipt.schema` must move to `DroppedTxReceipt.empty()` / `TxReceiptSchema`. ### \[Aztec.js] `ExtendedDirectionalAppTaggingSecret` renamed to `AppTaggingSecret`[​](#aztecjs-extendeddirectionalapptaggingsecret-renamed-to-apptaggingsecret "Direct link to aztecjs-extendeddirectionalapptaggingsecret-renamed-to-apptaggingsecret") `ExtendedDirectionalAppTaggingSecret` has been renamed to `AppTaggingSecret`. **Migration:** ``` - import { ExtendedDirectionalAppTaggingSecret } from '@aztec/stdlib/logs'; + import { AppTaggingSecret } from '@aztec/stdlib/logs'; - ExtendedDirectionalAppTaggingSecret.fromString(value) + AppTaggingSecret.fromString(value) ``` **Impact**: Code importing or referencing `ExtendedDirectionalAppTaggingSecret` should update to `AppTaggingSecret`. ### \[Protocol] Remaining protocol-contract addresses compacted to 1-3[​](#protocol-remaining-protocol-contract-addresses-compacted-to-1-3 "Direct link to \[Protocol] Remaining protocol-contract addresses compacted to 1-3") After the `auth_registry`, `multi_call_entrypoint`, and `public_checks` demotions freed up address slots `1`, `4`, and `6`, the three remaining protocol contracts have been compacted into the lowest slots: `ContractClassRegistry` moves from `3` to `1`, `ContractInstanceRegistry` stays at `2`, and `FeeJuice` moves from `5` to `3`. Code that hardcoded the previous values must be updated. `MAX_PROTOCOL_CONTRACTS` is unchanged (still `11`); only the assigned addresses moved. ### \[Aztec.nr] `multi_call_entrypoint` demoted from protocol contract[​](#aztecnr-multi_call_entrypoint-demoted-from-protocol-contract "Direct link to aztecnr-multi_call_entrypoint-demoted-from-protocol-contract") `multi_call_entrypoint` is no longer a protocol contract; its address is derived from its artifact rather than hardcoded at `4`, and PXE no longer auto-registers it. It is now a standard contract that PXE *preloads*: both `createPXE` and `EmbeddedWallet` preload the standard MultiCallEntrypoint automatically (and `EmbeddedWallet` additionally preloads `AuthRegistry`). **If you use the standard PXE or `EmbeddedWallet`, no changes are needed** — multicall keeps working out of the box. To preload a different set of standard contracts (for example to also preload `PublicChecks`, which is not preloaded by default), a wallet or app passes its own `preloadedContractsProvider` through the wallet's PXE options: ``` const wallet = await EmbeddedWallet.create(node, { pxe: { preloadedContractsProvider: { // EmbeddedWallet's built-in default preloads only MultiCallEntrypoint + AuthRegistry. // A custom provider REPLACES that default (it is not additive), so re-list the ones you // still want and add the extras — here, PublicChecks. getPreloadedContracts: async () => [ await getStandardMultiCallEntrypoint(), await getStandardAuthRegistry(), await getStandardPublicChecks(), ], }, }, }); ``` The provider *replaces* the default list (it is not additive), so include every standard contract you want available. ### \[Aztec.nr] `public_checks` demoted from protocol contract[​](#aztecnr-public_checks-demoted-from-protocol-contract "Direct link to aztecnr-public_checks-demoted-from-protocol-contract") `public_checks` is no longer a protocol contract. Its address is now derived from its artifact rather than hardcoded at `6`. The aztec-nr constant has moved and been renamed: ``` - use protocol_types::constants::PUBLIC_CHECKS_ADDRESS; + use crate::standard_addresses::STANDARD_PUBLIC_CHECKS_ADDRESS; ``` Unlike MultiCallEntrypoint and AuthRegistry, `PublicChecks` is **not** preloaded by `createPXE` or `EmbeddedWallet`. If your contract uses `privately_check_timestamp` or `privately_check_block_number`, `PublicChecks` must be deployed and made available to your PXE — either include it in a custom `preloadedContractsProvider` (see the `multi_call_entrypoint` note above) or register it directly: ``` import { getStandardPublicChecks } from "@aztec/standard-contracts/public-checks"; const { instance, artifact } = await getStandardPublicChecks(); await pxe.registerContract({ instance, artifact }); ``` For browser bundles, import from `@aztec/standard-contracts/public-checks/lazy` instead. Deploy `PublicChecks` once per fresh rollup: `aztec-wallet deploy public_checks_contract@PublicChecks --salt 1 --universal -f `. ### \[Aztec.nr] `auth_registry` demoted from protocol contract[​](#aztecnr-auth_registry-demoted-from-protocol-contract "Direct link to aztecnr-auth_registry-demoted-from-protocol-contract") `auth_registry` is no longer a protocol contract. Its address is now derived from its artifact rather than hardcoded at `1`. The aztec-nr constant has moved and been renamed: ``` - use protocol_types::constants::CANONICAL_AUTH_REGISTRY_ADDRESS; + use crate::standard_addresses::STANDARD_AUTH_REGISTRY_ADDRESS; ``` PXE no longer auto-registers `AuthRegistry` on startup. `EmbeddedWallet` preloads it automatically (alongside the MultiCallEntrypoint — see the `multi_call_entrypoint` note above), so apps using the standard wallet need no changes. If you use a PXE setup that doesn't preload it, add it to a custom `preloadedContractsProvider` or register it explicitly: ``` import { getStandardAuthRegistry } from "@aztec/standard-contracts/auth-registry"; const { instance, artifact } = await getStandardAuthRegistry(); await pxe.registerContract({ instance, artifact }); ``` For browser bundles, import from `@aztec/standard-contracts/auth-registry/lazy` instead. Deploy `AuthRegistry` once per fresh rollup: `aztec-wallet deploy auth_registry_contract@AuthRegistry --salt 1 --universal -f `. ### \[Aztec.nr] `public_checks` helpers moved to `aztec-nr`[​](#aztecnr-public_checks-helpers-moved-to-aztec-nr "Direct link to aztecnr-public_checks-helpers-moved-to-aztec-nr") The `privately_check_timestamp`, `privately_check_block_number`, and related caller helpers previously in `noir-contracts/contracts/protocol/public_checks_contract/src/utils.nr` are now in `aztec-nr/aztec/src/public_checks.nr`. Consumer contracts should update their imports: ``` - use public_checks::utils::privately_check_timestamp; + use aztec::public_checks::privately_check_timestamp; ``` ### \[Aztec Node / Aztec.js / CLI] Log retrieval API consolidated to two tag-based methods[​](#aztec-node--aztecjs--cli-log-retrieval-api-consolidated-to-two-tag-based-methods "Direct link to \[Aztec Node / Aztec.js / CLI] Log retrieval API consolidated to two tag-based methods") The four log-retrieval methods on `AztecNode` have been collapsed into two. `getContractClassLogs` and the `LogFilter`-shaped `getPublicLogs` are removed entirely; the surviving methods are `getPrivateLogsByTags(query)` and `getPublicLogsByTags(query)`, both taking a single query object and returning `LogResult[][]` (one inner array per requested tag, in input order). **Removed methods on `AztecNode`:** | Removed | Replacement | | ------------------------------------------------------------------------- | ----------------------------------------------------- | | `getPublicLogs(filter: LogFilter)` | `getPublicLogsByTags({ contractAddress, tags, ... })` | | `getContractClassLogs(filter: LogFilter)` | none — RPC removed; no production consumer existed | | `getPrivateLogsByTags(tags, page?, referenceBlock?)` | `getPrivateLogsByTags({ tags, ... })` | | `getPublicLogsByTagsFromContract(contract, tags, page?, referenceBlock?)` | `getPublicLogsByTags({ contractAddress, tags, ... })` | **New query and response shapes:** ``` // Query type TagQuery = T | { tag: T; afterLog?: LogCursor }; type LogsQueryBase = { fromBlock?: BlockNumber; // inclusive toBlock?: BlockNumber; // exclusive txHash?: TxHash; // mutually exclusive with fromBlock/toBlock referenceBlock?: BlockHash; // reorg-safety anchor; throws if missing includeEffects?: boolean; // attach noteHashes + all nullifiers }; type PrivateLogsQuery = LogsQueryBase & { tags: TagQuery[] }; type PublicLogsQuery = LogsQueryBase & { contractAddress: AztecAddress; tags: TagQuery[]; }; // Response (per log) type LogResult = { logData: Fr[]; blockNumber: BlockNumber; blockHash: BlockHash; blockTimestamp: UInt64; txHash: TxHash; txIndexWithinBlock: number; logIndexWithinTx: number; noteHashes?: Fr[]; // present only when includeEffects is set nullifiers?: Fr[]; // all nullifiers of the tx, not just the first }; ``` **Public queries now require a contract address.** Tag-only / contract-less public queries are no longer supported (the public log index is keyed on `(contract, tag)`). **Per-tag `afterLog` cursors replace the global `page` argument.** Each tag advances independently — pass `{ tag, afterLog: LogCursor.fromLog(lastLog) }` to resume that tag, and omit it for tags that are already exhausted. **Aztec.js wallet — `PublicEventFilter.contractAddress` is now required, and `afterLog` is a `LogCursor`:** ``` - type PublicEventFilter = EventFilterBase & { contractAddress?: AztecAddress }; + type PublicEventFilter = EventFilterBase & { contractAddress: AztecAddress }; type EventFilterBase = { txHash?: TxHash; fromBlock?: BlockNumber; toBlock?: BlockNumber; - afterLog?: LogId; + afterLog?: LogCursor; }; ``` `LogId`, `LogFilter`, `TxScopedL2Log`, `ExtendedPublicLog`, `ExtendedContractClassLog`, `GetPublicLogsResponse`, and `GetContractClassLogsResponse` are no longer exported from `@aztec/aztec.js`. Build cursors with `LogCursor.fromLog(log)` and decode public-event payloads from `result.logData.slice(1)` (the tag is field 0). **CLI — `aztec get-logs` now requires `--contract-address` and `--tag`:** ``` - aztec get-logs [--tx-hash ] [--from-block ] [--to-block ] [--after-log ] + aztec get-logs --contract-address
--tag \ + [--tx-hash ] [--from-block ] [--to-block ] [--after-log ] ``` `--after-log` now takes a `LogCursor` of the form `--` (formerly a `LogId`). **Mutual exclusion**: setting both `txHash` and `fromBlock`/`toBlock` is rejected (a `txHash` already pins a block). `txHash` + `afterLog` is allowed and paginates within the tx's logs for a tag. **Impact**: Any consumer of `getPublicLogs(LogFilter)`, `getContractClassLogs`, the old tag-based methods, `PublicEventFilter` without a `contractAddress`, or `EventFilterBase.afterLog: LogId` must be updated. The CLI rejects calls missing `--contract-address` or `--tag`. ### \[Aztec.js] `AccountManager.create` takes an options bag[​](#aztecjs-accountmanagercreate-takes-an-options-bag "Direct link to aztecjs-accountmanagercreate-takes-an-options-bag") `AccountManager.create` no longer takes `salt` as a positional argument. The trailing `salt?: Salt` parameter has been folded into a new `AccountManagerCreateOptions` bag alongside `immutablesHash` and `deployer`: ``` - AccountManager.create(wallet, secret, accountContract, salt) + AccountManager.create(wallet, secret, accountContract, { salt }) ``` `immutablesHash` lets callers commit a non-zero immutables hash on the resulting `ContractInstance` (folded into the salted initialization hash, so it affects the derived address). `deployer` overrides the deployer address recorded on the instance (defaults to `AztecAddress.ZERO`). The same `immutablesHash` field is now also threaded through `DeployMethod` / `DeployAccountMethod` so the address derived at deploy time matches the one on `accountManager.getInstance()`. ### \[Aztec.nr] Defining a custom `sync_state` function now requires `AztecConfig`[​](#aztecnr-defining-a-custom-sync_state-function-now-requires-aztecconfig "Direct link to aztecnr-defining-a-custom-sync_state-function-now-requires-aztecconfig") Contracts that previously overrode the default `sync_state` by defining their own function with that name will now get a compile error. Use `AztecConfig::custom_sync_state()` instead. The custom hook receives the same parameters as `do_sync_state` and is responsible for calling it if default behavior is also desired. You can perform work before and/or after the default `do_sync_state` call, or skip it entirely. ``` + unconstrained fn my_custom_sync( + contract_address: AztecAddress, + compute_note_hash: ComputeNoteHash, + compute_note_nullifier: ComputeNoteNullifier, + process_custom_message: Option, + offchain_inbox_sync: Option, + scope: AztecAddress, + ) { + // optional: work before default sync + do_sync_state(contract_address, compute_note_hash, compute_note_nullifier, process_custom_message, offchain_inbox_sync, scope); + // optional: work after default sync + } - #[aztec] + #[aztec(::aztec::macros::AztecConfig::new().custom_sync_state(crate::my_custom_sync))] contract MyContract { - use aztec::macros::functions::external; - - #[external("utility")] - unconstrained fn sync_state(scope: AztecAddress) { - // custom sync logic - } } ``` **Impact**: Only contracts that manually defined a `sync_state` function are affected. Contracts using the default macro-generated `sync_state` require no changes. ### \[Aztec.nr] `push_nullifier` renamed to `push_nullifier_unsafe`[​](#aztecnr-push_nullifier-renamed-to-push_nullifier_unsafe "Direct link to aztecnr-push_nullifier-renamed-to-push_nullifier_unsafe") `PrivateContext::push_nullifier` and `PublicContext::push_nullifier` have been renamed to `push_nullifier_unsafe` to make it clear that they are low-level functions that require careful domain separation. This is consistent with the `_unsafe` suffix already used by `emit_private_log_unsafe`, `emit_raw_note_log_unsafe`, and `emit_public_log_unsafe`. ``` - context.push_nullifier(nullifier); + context.push_nullifier_unsafe(nullifier); ``` Prefer higher-level abstractions like `SingleUseClaim` or `destroy_note` which handle domain separation automatically. ### \[Aztec.nr] `LogRetrievalRequest` now includes `source`, `from_block`, and `to_block` fields[​](#aztecnr-logretrievalrequest-now-includes-source-from_block-and-to_block-fields "Direct link to aztecnr-logretrievalrequest-now-includes-source-from_block-and-to_block-fields") `LogRetrievalRequest` has been extended with three new fields to support filtering logs by source and block range. The `get_logs_by_tag` oracle now also returns all matching logs per tag instead of only the first match. A `LogRetrievalRequest::new(contract_address, tag)` constructor is provided that defaults to querying both public and private logs with no block range filter: ``` LogRetrievalRequest::new(contract_address, my_tag) ``` If you need to customize source or block range, construct the struct manually with the new fields: ``` LogRetrievalRequest { tag: my_tag, + source: LogSource.PUBLIC_AND_PRIVATE, + from_block: Option::none(), + to_block: Option::none(), } ``` `source` controls which RPCs are queried: `LogSource.PRIVATE`, `LogSource.PUBLIC`, or `LogSource.PUBLIC_AND_PRIVATE`. `from_block` and `to_block` define a half-open `[from, to)` block range filter. Both are `Option` and default to `Option::none()` (no filtering). ### \[Protocol] Public-key hashes replace points in `PublicKeys`[​](#protocol-public-key-hashes-replace-points-in-publickeys "Direct link to protocol-public-key-hashes-replace-points-in-publickeys") Ships together with immutables hash changes (shown below). Per [AZIP-8](https://github.com/AztecProtocol/governance/blob/main/AZIPs/azip-8.md), `PublicKeys` no longer carries its master public keys as elliptic curve points. All of them except `ivpk_m` (`npk_m`, `ovpk_m`, `tpk_m`, and the new `mspk_m` and `fbpk_m`) are now exposed only as their poseidon2 hash digests; only `ivpk_m` (the master incoming viewing key) remains a point because address derivation needs it as a curve point. **This is a hard fork:** every contract address and account address derived from a non-default `PublicKeys` changes. **Contract author migration.** Read the master nullifier hash directly off `PublicKeys` instead of computing it from a point: ``` - let owner_npk_m = get_public_keys(owner).npk_m; - let secret = context.request_nhk_app(owner_npk_m.hash()); + let owner_npk_m_hash = get_public_keys(owner).npk_m_hash; + let secret = context.request_nhk_app(owner_npk_m_hash); ``` The same field-rename applies to `.ovpk_m` and `.tpk_m`: these are now `.ovpk_m_hash` and `.tpk_m_hash` respectively. Code that needed those keys as points will not compile; the points are no longer accessible to contract code. **Custom account contracts.** Wallets that ship their own Noir account contracts must recompile. Macro-generated calldata extraction and the `request_nsk_app` / `request_ovsk_app` paths use the hash form natively. **TS / wallet author migration.** The `PublicKeys` constructor signature changes from four `Point`s to `(npkMHash: Fr, ivpkM: Point, ovpkMHash: Fr, tpkMHash: Fr, mspkMHash: Fr, fbpkMHash: Fr)` (the new `mspk_m` message-signing and `fbpk_m` fallback keys are also exposed as hashes). `KeyValidationRequest` carries `pkMHash: Fr` instead of `pkM: Point`. `KeyStore.getMasterSecretKey` now takes a `pkMHash: Fr` rather than a `Point`. Callers using the auto-generated TS binding pick this up automatically; callers that hand-roll the arg buffer must update. **Wallet UI.** Any panel that displayed `masterNullifierPublicKey`, `masterOutgoingViewingPublicKey`, or `masterTaggingPublicKey` as Grumpkin points will no longer compile against the new `PublicKeys` class. The points themselves are no longer in `ContractInstancePublished` and cannot be recovered from the onchain record. Switch to displaying the hashes (`npkMHash`, `ovpkMHash`, `tpkMHash`) or drop the display. **PXE storage migration.** `DatabaseVersionManager` deletes pre-v6 databases on first open: users will see registered accounts, contacts, address aliases, and synced notes wiped. Wallets should surface a "your local state was reset, please re-register accounts and re-sync" path. There is no forward migration because the address derived from a given secret changes (the new `public_keys_hash` is over single-key digests, not raw points). Previous addresses are not recoverable from the same secret; assets and notes attached to them are inaccessible at the protocol level. **Indexer / event-decoder migration.** The `ContractInstancePublished` private log payload is now 15 fields: ``` [ MAGIC, address, version, salt, class_id, init_hash, immutables_hash, npk_m_hash, ivpk_m.x, ivpk_m.y, ovpk_m_hash, tpk_m_hash, mspk_m_hash, fbpk_m_hash, deployer ] ``` `version` is `2`. v1 events should be rejected. **Security note (PXE side).** The kernel circuit no longer checks that `npk_m`, `ovpk_m`, `tpk_m` are on-curve or non-infinity (those points are no longer in the witness). The PXE / key store relies on `deriveKeys`'s by-construction guarantee that derived points are on-curve and non-infinity. Account-creation flows that bypass `deriveKeys` (e.g. importing pre-derived public keys from an external source) must validate this themselves, or risk producing unspendable notes. ### \[Contracts] `ContractInstance` gains `immutablesHash`, address derivation changes[​](#contracts-contractinstance-gains-immutableshash-address-derivation-changes "Direct link to contracts-contractinstance-gains-immutableshash-address-derivation-changes") `ContractInstance` now has a new `immutablesHash: Fr` field that commits to a contract's immutable storage values. The field is folded into the salted initialization hash, so contract addresses are impacted: ``` salted_initialization_hash = poseidon2(DOM_SEP__SALTED_INITIALIZATION_HASH, [salt, initialization_hash, deployer, immutables_hash]) ``` **You may need to act if:** * You hardcode contract addresses computed from instance fields outside the SDK. Recompute them under the new derivation. * You parse the `ContractInstancePublished` private log directly. The event payload has an extra field, with `immutables_hash` inserted between `initialization_hash` and the public-keys block: ``` [tag, address, version, salt, classId, initialization_hash, immutables_hash, ...publicKeys(7), deployer] ``` * You call `ContractInstanceRegistry.publish_for_public_execution` directly. The function now takes 6 arguments instead of 5, with `immutables_hash` inserted between `initialization_hash` and `public_keys`: ``` - publish_for_public_execution(salt, contract_class_id, initialization_hash, public_keys, universal_deploy) + publish_for_public_execution(salt, contract_class_id, initialization_hash, immutables_hash, public_keys, universal_deploy) ``` * You call the `GetContractInstance` AVM opcode directly or use the per-member helpers in `aztec-nr`. A new enum value `ContractInstanceMember::IMMUTABLES_HASH = 3` selects `immutables_hash`. Use the wrapper helper from `aztec::oracle::get_contract_instance`: ``` use aztec::oracle::get_contract_instance::get_contract_instance_immutables_hash_avm; let immutables_hash: Option = get_contract_instance_immutables_hash_avm(address); ``` The `aztec.js` `publishInstance` helper handles this automatically. ### \[Aztec.nr] `emit_private_log_unsafe` / `emit_raw_note_log_unsafe` now take `BoundedVec`[​](#aztecnr-emit_private_log_unsafe--emit_raw_note_log_unsafe-now-take-boundedvec "Direct link to aztecnr-emit_private_log_unsafe--emit_raw_note_log_unsafe-now-take-boundedvec") The old array-based `emit_private_log_unsafe(tag, log: [Field; N], length)` and `emit_raw_note_log_unsafe(tag, log: [Field; N], length, note_hash_counter)` have been removed. The temporary `_vec_unsafe` variants introduced in a prior release have been renamed to take their place. ``` - context.emit_private_log_unsafe(tag, log_array, length); + context.emit_private_log_unsafe(tag, bounded_vec_log); - context.emit_raw_note_log_unsafe(tag, log_array, length, note_hash_counter); + context.emit_raw_note_log_unsafe(tag, bounded_vec_log, note_hash_counter); ``` If you were already using `emit_private_log_vec_unsafe` / `emit_raw_note_log_vec_unsafe`, simply drop the `_vec` from the function name: ``` - context.emit_private_log_vec_unsafe(tag, log); + context.emit_private_log_unsafe(tag, log); - context.emit_raw_note_log_vec_unsafe(tag, log, note_hash_counter); + context.emit_raw_note_log_unsafe(tag, log, note_hash_counter); ``` If you were manually padding an array and passing a shorter length, build a `BoundedVec` from just the meaningful fields instead: ``` - let padded = payload.concat([0; PRIVATE_LOG_CIPHERTEXT_LEN - 2]); - context.emit_private_log_unsafe(tag, padded, 2); + context.emit_private_log_unsafe(tag, BoundedVec::from_array(payload)); ``` If you were passing the full array, wrap it with `BoundedVec::from_array`: ``` - context.emit_private_log_unsafe(tag, ciphertext, ciphertext.len()); + context.emit_private_log_unsafe(tag, BoundedVec::from_array(ciphertext)); ``` ### \[bb.js / accounts / aztec.nr] Schnorr signatures switched to Poseidon2[​](#bbjs--accounts--aztecnr-schnorr-signatures-switched-to-poseidon2 "Direct link to \[bb.js / accounts / aztec.nr] Schnorr signatures switched to Poseidon2") The Schnorr challenge hash function changed from `blake2s(pedersen(R.x, pubkey.x, pubkey.y) ‖ message)` to `Poseidon2(DST, R.x, pubkey.x, pubkey.y, message)`, where `DST = poseidon2_hash_bytes("schnorr_grumpkin_poseidon2")` is a domain separation tag binding signatures to this scheme. The change applies end-to-end across the native signer (`bb`), `@aztec/bb.js`, the noir verifier library (`noir-lang/schnorr` v0.2.0 → v0.4.0), and both standard Schnorr account contracts. The auth witness on-wire shape also changes from `[u8; 64]` (the serialized `(s, e)` bytes) to `[Field; 4]` (`[s.lo, s.hi, e.lo, e.hi]`, each scalar split into two 128-bit limbs). **Impact:** A previously-deployed Schnorr account cannot be controlled by the new TypeScript code. Both the signature scheme and the auth witness format change, so signatures produced by the new code will fail in-circuit verification against the old account contract, and old-style 64-byte auth witnesses will not decode in the new contract. Users with existing Schnorr accounts on testnet must deploy a fresh account contract and migrate funds. ECDSA accounts (`ecdsa_k`, `ecdsa_r`) are unaffected. **If you maintain a custom Schnorr account contract**, bump the `schnorr` dependency in `Nargo.toml`: ``` - schnorr = { tag = "v0.2.0", git = "https://github.com/noir-lang/schnorr" } + schnorr = { tag = "v0.4.0", git = "https://github.com/noir-lang/schnorr" } ``` and update `is_valid_impl` to consume the auth witness as four `Field` limbs and pass `outer_hash` directly: ``` - let signature: [u8; 64] = unsafe { get_auth_witness_as_bytes(outer_hash) }; - schnorr::verify_signature(pub_key, signature, outer_hash.to_be_bytes::<32>()) + let limbs: [Field; 4] = unsafe { get_auth_witness(outer_hash) }; + let signature = ( + std::embedded_curve_ops::EmbeddedCurveScalar::new(limbs[0], limbs[1]), + std::embedded_curve_ops::EmbeddedCurveScalar::new(limbs[2], limbs[3]), + ); + schnorr::verify_signature(pub_key, signature, outer_hash) ``` The `Schnorr` TypeScript API in `@aztec/foundation/crypto/schnorr` keeps the same surface (`constructSignature(msg: Uint8Array, ...)`, `verifySignature(msg, ...)`), but the `msg` parameter is now required to be exactly 32 bytes — a serialized field element (e.g. `Fr.toBuffer()` or `messageHash.toBuffer()`). Passing arbitrary-length byte strings will fail at the bb.js boundary. ### \[Aztec.nr] `attempt_note_discovery` is no longer exposed; use `process_private_note_msg`[​](#aztecnr-attempt_note_discovery-is-no-longer-exposed-use-process_private_note_msg "Direct link to aztecnr-attempt_note_discovery-is-no-longer-exposed-use-process_private_note_msg") `attempt_note_discovery` is now crate-private. Custom message handlers (implementations of `CustomMessageHandler`) that previously called it directly should call `process_private_note_msg` instead, which runs the standard private note message decoding and discovery pipeline. `process_private_note_msg` takes the raw `msg_metadata` and `msg_content` rather than already-decoded note fields, so it handles decoding (and silently discards undecodable messages) on your behalf: ``` - attempt_note_discovery( - contract_address, - tx_hash, - unique_note_hashes_in_tx, - first_nullifier_in_tx, - compute_note_hash, - compute_note_nullifier, - owner, - storage_slot, - randomness, - note_type_id, - packed_note, - ); + process_private_note_msg( + contract_address, + tx_hash, + unique_note_hashes_in_tx, + first_nullifier_in_tx, + compute_note_hash, + compute_note_nullifier, + msg_metadata, + msg_content, + ); ``` **Impact**: Custom message handlers that reused the standard note message processing pipeline must switch to `process_private_note_msg`. Contracts using only built-in private note handling are unaffected. ### \[Aztec.nr] TXE `call_public_incognito` no longer takes a `from` parameter[​](#aztecnr-txe-call_public_incognito-no-longer-takes-a-from-parameter "Direct link to aztecnr-txe-call_public_incognito-no-longer-takes-a-from-parameter") `TestEnvironment::call_public_incognito` previously accepted a `from` address that was silently ignored (the function always uses a null `msg_sender`). The `from` parameter has been removed. ``` - env.call_public_incognito(sender, SampleContract::at(addr).some_function()); + env.call_public_incognito(SampleContract::at(addr).some_function()); ``` If you need to call a public function *with* a sender, use `call_public` instead. ### \[Aztec.nr] TXE `view_public_incognito` is deprecated[​](#aztecnr-txe-view_public_incognito-is-deprecated "Direct link to aztecnr-txe-view_public_incognito-is-deprecated") `TestEnvironment::view_public_incognito` is now deprecated in favor of `view_public`, which has the same behavior (null `msg_sender`, static call). ``` - env.view_public_incognito(SampleContract::at(addr).some_view()); + env.view_public(SampleContract::at(addr).some_view()); ``` ### \[Aztec.js] `DeployMethod` address-affecting parameters move to construction time[​](#aztecjs-deploymethod-address-affecting-parameters-move-to-construction-time "Direct link to aztecjs-deploymethod-address-affecting-parameters-move-to-construction-time") Salt, deployer, and public keys are now passed when the `DeployMethod` is constructed, not on every call to `send` / `simulate` / `request` / `getInstance`. This locks the contract address once it is determined and prevents the silent salt-cache poisoning bug where the address could change between calls. `contractAddressSalt`, `deployer`, and `universalDeploy` have been removed from `DeployOptions`, `RequestDeployOptions`, and `SimulateDeployOptions`. They now live on a new `DeployInstantiationOptions` argument passed at construction. `deployer` and `universalDeploy` are mutually exclusive; passing both throws. `Contract.deployWithPublicKeys` and the generated `MyContract.deployWithPublicKeys(...)` factories have been removed; pass `publicKeys` via the `instantiation` argument of `deploy(...)` instead. The buggy synchronous `address` and `partialAddress` getters have been removed and replaced with `getAddress()` and `getPartialAddress()` (both `async`). The compact form keeps working: `MyContract.deploy(wallet, ...args).send({ from: alice })` deploys with `deployer = alice` and `salt = random()`, exactly as before. The deployer is locked the first time `send` / `simulate` / `profile` is called (from `options.from`, with `NO_FROM` or undefined → universal) and cannot change after that: * Subsequent `send` / `simulate` / `profile` calls with a `from` that would imply a different deployer throw, instead of silently producing a different address. * A lock to universal (`AztecAddress.ZERO`) is the only one compatible with any sender, since the universal address does not depend on `from`. * A lock to a concrete address only accepts that exact `from` on subsequent calls. **Migration:** Universal deployment with a fixed salt: ``` - const deploy = MyContract.deploy(wallet, ...args); - await deploy.send({ - from: alice, - contractAddressSalt: salt, - universalDeploy: true, - }); + const deploy = MyContract.deploy(wallet, ...args, { salt, universalDeploy: true }); + await deploy.send({ from: alice }); ``` Non-universal deploy where `from` doubles as the deployer: ``` - const deploy = MyContract.deploy(wallet, ...args); - await deploy.send({ from: alice, contractAddressSalt: salt }); + const deploy = MyContract.deploy(wallet, ...args, { salt }); + await deploy.send({ from: alice }); ``` If you need to read the address before sending, lock the deployer at construction: ``` const deploy = MyContract.deploy(wallet, ...args, { salt, deployer: alice }); const address = await deploy.getAddress(); // resolves; deployer was locked at construction await deploy.send({ from: alice }); // deploys at the address `getAddress` returned ``` Universal deploys can be sent by any account, since the universal address does not depend on `from`: ``` const deploy = MyContract.deploy(wallet, ...args, { universalDeploy: true }); await deploy.send({ from: bob }); // OK, universal accepts any sender ``` A lock to a concrete deployer rejects sending from a different account, instead of silently deploying at a different address: ``` const deploy = MyContract.deploy(wallet, ...args, { deployer: alice }); await deploy.send({ from: bob }); // throws: deployer is locked to alice ``` `deployWithPublicKeys` is gone; pass `publicKeys` in the instantiation options instead: ``` - const deploy = MyContract.deployWithPublicKeys(publicKeys, wallet, ...args); + const deploy = MyContract.deploy(wallet, ...args, { publicKeys }); ``` `ContractDeployer.deploy(...)` now takes the constructor args first and the instantiation options as its second argument (pass `{}` for the instantiation to use defaults and rely on lazy locking from `from`): ``` - const cd = new ContractDeployer(artifact, wallet); - await cd.deploy(...ctorArgs).send({ from: alice, contractAddressSalt: salt }); + const cd = new ContractDeployer(artifact, wallet); + await cd.deploy(ctorArgs, { salt }).send({ from: alice }); ``` The synchronous `address` / `partialAddress` getters are gone: ``` - const address = deploy.address; // sync, possibly undefined - const partial = await deploy.partialAddress; // sync getter wrapping async value + const address = await deploy.getAddress(); // requires the deployer to be locked + const partial = await deploy.getPartialAddress(); // requires the deployer to be locked ``` `getInstance()` no longer takes options; use the construction-time instantiation instead: ``` - const instance = await deploy.getInstance({ contractAddressSalt: salt }); + const deploy = MyContract.deploy(wallet, ...args, { salt, deployer: alice }); + const instance = await deploy.getInstance(); ``` ### \[aztec-up] Bundled binaries are no longer exposed under bare names on `PATH`[​](#aztec-up-bundled-binaries-are-no-longer-exposed-under-bare-names-on-path "Direct link to aztec-up-bundled-binaries-are-no-longer-exposed-under-bare-names-on-path") The Aztec installer previously placed bundled binaries directly into `$HOME/.aztec/current/bin` under bare names (`forge`, `nargo`, `bb`, `pxe`, ...). Anything with the same name in your own `PATH` was silently shadowed in unrelated projects. Every bundled binary is now exposed only under an `aztec-` prefixed name in `$HOME/.aztec/current/bin`. Bare names are not on `PATH` at all and resolve to your own install (if any). | Was on `PATH` | Now | | ------------------ | ------------------------ | | `forge` | `aztec-forge` | | `cast` | `aztec-cast` | | `anvil` | `aztec-anvil` | | `chisel` | `aztec-chisel` | | `nargo` | `aztec-nargo` | | `noir-profiler` | `aztec-noir-profiler` | | `bb` | `aztec-bb` | | `bb-cli` | `aztec-bb-cli` | | `pxe` | `aztec-pxe` | | `txe` | `aztec-txe` | | `validator-client` | `aztec-validator-client` | | `blob-client` | `aztec-blob-client` | `aztec`, `aztec-wallet`, and `aztec-up` keep their existing names. If you relied on a bundled bare-name binary for general use: * For Aztec contract work, prefer `aztec compile` and `aztec test`. * For other Noir / Foundry commands, invoke the `aztec-*` symlink directly (e.g. `aztec-nargo fmt`, `aztec-forge build`). * Or install Foundry / nargo separately via `foundryup` / `noirup`. If you set `Noir: Nargo Path` in the VS Code Noir extension to `$HOME/.aztec/current/bin/nargo`, change it to `$HOME/.aztec/current/bin/aztec-nargo` (the symlink is a drop-in for `nargo`). See the [Noir VSCode Extension guide](/developers/testnet/docs/aztec-nr/installation.md) for details. The installer also no longer adds `$HOME/.aztec/current/node_modules/.bin` to your shell `PATH`, so the \~40 transitive npm bins it used to leak (`jest`, `tsc`, `tsserver`, ...) are gone and no longer shadow your own installs. If you installed an earlier version, your shell profile (`~/.bashrc` / `~/.zshrc`) may still contain the old `PATH` line — re-run the installer once to replace it, open a fresh shell, and confirm `$HOME/.aztec/current/node_modules/.bin` no longer appears in `echo $PATH`. ### \[Stdlib] `SimulationOverrides.contracts` entries no longer carry an artifact[​](#stdlib-simulationoverridescontracts-entries-no-longer-carry-an-artifact "Direct link to stdlib-simulationoverridescontracts-entries-no-longer-carry-an-artifact") `ContractOverrides` entries are now `{ instance }` only. To override a contract's artifact, pre-register the target class via `pxe.registerContractClass(artifact)` and set the override instance's `currentContractClassId` to that class id: ``` - const instance = await getContractInstanceFromInstantiationParams(stubArtifact, { salt: Fr.random() }); + const instance = await pxe.getContractInstance(addr); + await pxe.registerContractClass(stubArtifact); + const stubClassId = (await getContractClassFromArtifact(stubArtifact)).id; - overrides = { contracts: { [addr.toString()]: { instance, artifact: stubArtifact } } }; + overrides = { contracts: { [addr.toString()]: { instance: { ...instance, currentContractClassId: stubClassId } } } }; ``` ### \[Aztec.js] `simulate` accepts `overrides` for testing "what if storage value was X?"[​](#aztecjs-simulate-accepts-overrides-for-testing-what-if-storage-value-was-x "Direct link to aztecjs-simulate-accepts-overrides-for-testing-what-if-storage-value-was-x") `Contract.methods.foo(...).simulate(...)` now accepts an `overrides` option that injects values into the simulator's (ephemeral) world-state fork and contract DB before the call runs. The supported field is `publicStorage`, which writes a `(contract, slot, value)` into the public-data tree as if a previous tx had set it. Overrides are thrown away after simulation completes. ``` const result = await contract.methods.read_balance(account).simulate({ overrides: { publicStorage: [ { contract: contract.address, slot: BALANCE_SLOT, value: new Fr(1_000_000n), }, ], }, }); ``` The same option flows through `wallet.simulateTx` and eventually to `simulatePublicCalls` RPC on `AztecNode`. Direct callers of the `SimulationOverrides` constructor must switch from a positional `contracts` argument to an options bag: ``` - new SimulationOverrides(contracts); + new SimulationOverrides({ contracts }); ``` `overrides.contracts` swaps contract instances in the simulator's contract DB — useful for simulating a contract being on a different class than the one it was deployed with. To simulate a complete onchain upgrade flow, use the `fastForwardContractUpdate` helper which returns a `SimulationOverrides` covering both registry storage rewrites and the upgraded instance entry: ``` import { fastForwardContractUpdate } from "@aztec/aztec.js"; const overrides = await fastForwardContractUpdate({ instanceAddress: contract.address, newClassId: upgradedClass.id, node, }); const result = await contract.methods.upgraded_method().simulate({ overrides }); ``` ### \[PXE] `proveTx` takes an options bag[​](#pxe-provetx-takes-an-options-bag "Direct link to pxe-provetx-takes-an-options-bag") `PXE.proveTx` used to accept `scopes` as a positional argument; it now takes an options bag consistent with `simulateTx` and `profileTx`, and adds an optional `senderForTags` field. Update direct callers: ``` - pxe.proveTx(txRequest, scopes); + pxe.proveTx(txRequest, { scopes }); ``` The new `senderForTags` field sets the address recipients use to find private messages (notes, events, logs) emitted by this tx. Most wallets don't need to set it; the wallet SDK derives it from the tx's `from` address: ``` // Most callers: just migrate scopes pxe.proveTx(txRequest, { scopes }); // When from === NO_FROM (e.g. self-paid account deploy), supply the tag sender explicitly: pxe.proveTx(txRequest, { scopes, senderForTags: deployedAddress }); ``` ### \[Aztec Node] Unified `getBlock` / `getCheckpoint` RPC API[​](#aztec-node-unified-getblock--getcheckpoint-rpc-api "Direct link to aztec-node-unified-getblock--getcheckpoint-rpc-api") The Aztec Node JSON-RPC surface for fetching blocks and checkpoints has been consolidated. The unified `getBlock` and `getCheckpoint` methods return uniform `BlockResponse` / `CheckpointResponse` shapes. The extra fields a caller cares about (tx bodies, L1 publish info, committee attestations, nested blocks) are now controlled by an `options` argument rather than by picking the right method. `getBlocks` and `getCheckpoints` retain their names but now return the new response shapes. **Removed methods:** | Removed | Replacement | | ---------------------------------- | -------------------------------------------- | | `getBlockByHash(hash)` | `getBlock(hash)` or `getBlock({ hash })` | | `getBlockByArchive(archive)` | `getBlock({ archive })` | | `getBlockHeaderByArchive(archive)` | `getBlock({ archive }).then(r => r?.header)` | | `getProvenBlockNumber()` | `getBlockNumber('proven')` | | `getCheckpointedBlockNumber()` | `getBlockNumber('checkpointed')` | **Deprecated but still present** (scheduled for removal once internal consumers of the archiver shape are rewired): `getL2Tips` (use `getChainTips`), `getBlockHeader` (use `getBlock(param).then(r => r?.header)`), `getCheckpointedBlocks` (use `getBlocks(from, limit, { includeL1PublishInfo: true, includeAttestations: true })`). Do not adopt these in new code. (`getCheckpointsDataForEpoch` was previously listed here; see the dedicated checkpoint-API entry below for its removal.) **New response shapes:** `BlockResponse` always carries `header`, `archive`, `hash`, `number`, `checkpointNumber`, and `indexWithinCheckpoint`. `body`, `l1` (an `L1PublishInfo` discriminated union), and `attestations` are present only when the matching include option is set. `CheckpointResponse` mirrors this for checkpoints, with `blocks` gated on `includeBlocks`, and always carries `feeAssetPriceModifier` as a base field. The response types are generic over the options object, so passing a literal `{ includeTransactions: true }` narrows the return type and `response.body` becomes non-optional. **Nested blocks on `getCheckpoint`:** only `includeTransactions` is forwarded to the blocks embedded by `includeBlocks: true`. `includeL1PublishInfo` and `includeAttestations` on a checkpoint request attach L1 / attestation data to the checkpoint itself, not to its nested blocks. **Return type changes for `getBlocks` / `getCheckpoints`:** the return type is now `BlockResponse[]` / `CheckpointResponse[]` instead of `L2Block[]` / `PublishedCheckpoint[]`. Callers that previously consumed fields of `L2Block` (e.g. `.body`) must now opt in via `{ includeTransactions: true }`; callers that consumed `PublishedCheckpoint.checkpoint.blocks` must opt in via `{ includeBlocks: true }`. **Migration for wallet/SDK consumers (`@aztec/aztec.js`, `@aztec/wallet-sdk`):** ``` - const block = await node.getBlockByHash(hash); + const block = await node.getBlock(hash, { includeTransactions: true }); - const archiveBlock = await node.getBlockByArchive(archive); + const archiveBlock = await node.getBlock({ archive }, { includeTransactions: true }); - const provenNumber = await node.getProvenBlockNumber(); + const provenNumber = await node.getBlockNumber('proven'); - const checkpointedNumber = await node.getCheckpointedBlockNumber(); + const checkpointedNumber = await node.getBlockNumber('checkpointed'); - const tips = await node.getL2Tips(); + const tips = await node.getChainTips(); ``` `getBlockHeader`, `getCheckpointedBlocks`, `getCheckpointsDataForEpoch`, and `getL2Tips` continue to work in this release but are deprecated; migrate to the replacements above. **Chain-tip selectors:** `getBlockNumber` and `getCheckpointNumber` now accept an optional `ChainTip` argument (`'proposed' | 'checkpointed' | 'proven' | 'finalized'`). The `'proposed'` semantics are described in the dedicated checkpoint-API entry below — they were tightened in this release to mean "the proposed-tip checkpoint" rather than "the latest L1-confirmed checkpoint." **Block parameter variants:** `BlockParameter` now also accepts a block hash, an archive root, and chain-tip names. The existing `number | 'latest'` forms continue to work — `'latest'` is an alias for `'proposed'`. **Impact**: Source changes are required anywhere the removed methods are called. Type changes are required anywhere `L2Block` / `BlockHeader` / `CheckpointedL2Block` were consumed from the RPC — those call sites now receive `BlockResponse` / `CheckpointResponse` and must request the fields they need via `options`. Production nodes will reject JSON-RPC calls to the removed method names. ### \[Aztec Node] Checkpoint RPC: `'proposed'` is now strictly proposed; `'latest'` removed; `getCheckpointsData` takes a query[​](#aztec-node-checkpoint-rpc-proposed-is-now-strictly-proposed-latest-removed-getcheckpointsdata-takes-a-query "Direct link to aztec-node-checkpoint-rpc-proposed-is-now-strictly-proposed-latest-removed-getcheckpointsdata-takes-a-query") Follow-up to the unified-RPC change above. Tightens the checkpoint-side API surface: removes the old positional / per-shape entrypoints, drops the wire-level alias that conflated proposed and confirmed checkpoints, and replaces the deprecated epoch-only `getCheckpointsDataForEpoch` method with a unified query-shaped `getCheckpointsData` that mirrors the block-side API. **`getCheckpointsDataForEpoch(epoch)` removed.** The previously-deprecated method is gone. Use `getCheckpointsData({ epoch })` instead. The new `getCheckpointsData` also accepts a contiguous range: ``` - const cps = await node.getCheckpointsDataForEpoch(epoch); + const cps = await node.getCheckpointsData({ epoch }); // New: contiguous range + const cps = await node.getCheckpointsData({ from: 1, limit: 5 }); ``` **`'latest'` removed from `CheckpointParameter`.** The `'latest'` literal previously accepted by `getCheckpoint('latest', options)` is no longer valid. Use `'checkpointed'` to address the latest confirmed checkpoint, or `'proposed'` for the proposed-tip semantics described below. (Block-side `'latest'` in `BlockParameter` is unaffected.) ``` - await node.getCheckpoint('latest'); + await node.getCheckpoint('checkpointed'); ``` **`'proposed'` semantics changed.** Previously `'proposed'` on the checkpoint side aliased to "latest L1-confirmed checkpoint" — a documented foot-gun. After this release: * `getCheckpoint('proposed')` resolves to the proposed-tip checkpoint number and looks it up confirmed-first, then falls back to the proposed-checkpoint store. When a proposed entry exists at that number it is returned; when none exists, the proposed-tip falls back to the confirmed tip and the call returns the latest confirmed checkpoint. Returns `undefined` only when neither store has the resolved number. * `getCheckpointNumber('proposed')` returns the proposed-tip checkpoint number, falling back to the latest confirmed checkpoint number when no proposed entry exists. Return type stays `Promise`. If you want the latest L1-confirmed checkpoint regardless of proposed state, switch the call to `'checkpointed'`: ``` - const cp = await node.getCheckpoint('proposed'); - const n = await node.getCheckpointNumber('proposed'); + const cp = await node.getCheckpoint('checkpointed'); + const n = await node.getCheckpointNumber('checkpointed'); ``` **By-number / by-slot lookups gain a confirmed→proposed fallback.** `getCheckpoint({ number: N })` and `getCheckpoint({ slot: S })` now check the confirmed store first, then fall back to the proposed store. Tag-based lookups (`'checkpointed'`, `'proven'`, `'finalized'`) do not fall back — those tags name confirmed-only positions. **Throws on a proposed match + L1/attestations.** Proposed checkpoints have no L1 publish info or committee attestations (those data points only exist after L1 confirmation). The throw fires only when the lookup actually lands on a proposed entry — i.e. the confirmed store missed and the proposed store hit. When the proposed-tip falls back to the confirmed tip (no proposed entry exists), `'proposed' + includeAttestations` returns the latest confirmed checkpoint with attestations rather than throwing: ``` // Throws BadRequestError when a proposed entry exists at the resolved number: await node.getCheckpoint("proposed", { includeAttestations: true }); await node.getCheckpoint("proposed", { includeL1PublishInfo: true }); // And when a by-number / by-slot lookup falls back to a proposed entry: await node.getCheckpoint({ number: N }, { includeAttestations: true }); // → throws if N is matched only in the proposed store ``` If your code asks for `includeAttestations` / `includeL1PublishInfo` and might land on a proposed entry, gate the call on `getCheckpoint(param)` first, then re-issue with the include flags only after confirming the result is from the confirmed store (e.g. by checking that the tag-based equivalent returns the same checkpoint number). **Impact**: Wallet, indexer, and tooling code that called `node.getCheckpoint('proposed')` or `node.getCheckpoint('latest')` will need to update their tag. Any code relying on the old "proposed = latest confirmed" alias should switch to `'checkpointed'`. Code that combined `'proposed'` (or by-number/by-slot fallbacks) with `includeAttestations` / `includeL1PublishInfo` will now throw at runtime; gate those flags as described above. ### \[Aztec Node] `feeAssetPriceModifier` now correctly populated on confirmed checkpoints[​](#aztec-node-feeassetpricemodifier-now-correctly-populated-on-confirmed-checkpoints "Direct link to aztec-node-feeassetpricemodifier-now-correctly-populated-on-confirmed-checkpoints") Confirmed checkpoints previously reported `feeAssetPriceModifier = 0n` regardless of the value observed on L1, because the archiver dropped the field on checkpoint confirmation. The field is now persisted and returned correctly on `CheckpointResponse`. Any wallet or indexer logic that special-cased `0n` as a sentinel for "no modifier" will need to be updated; it is now a valid value in its own right. ### \[Protocol] Domain separators introduced for merkle-node, block-headers, and blob hashes[​](#protocol-domain-separators-introduced-for-merkle-node-block-headers-and-blob-hashes "Direct link to \[Protocol] Domain separators introduced for merkle-node, block-headers, and blob hashes") Several protocol hashes that previously used bare `poseidon2_hash` are now domain-separated via `poseidon2_hash_with_separator`. This is a security hardening change — it prevents a value produced by one hash context from being reinterpreted in another (e.g. a sibling path from one tree being transported to another). **New domain separators:** * `DOM_SEP__MERKLE_HASH` — sibling-pair hash for append-only trees (note-hash, L1→L2, archive, VK tree, and the balanced/unbalanced tree hash helpers in `@aztec/foundation/trees`). * `DOM_SEP__NULLIFIER_MERKLE`, `DOM_SEP__PUBLIC_DATA_MERKLE`, `DOM_SEP__WRITTEN_SLOTS_MERKLE`, `DOM_SEP__RETRIEVED_BYTECODES_MERKLE` — per-tree sibling-pair hash for each indexed tree. Each tree uses its own separator so sibling paths are non-transportable across trees. * `DOM_SEP__BLOCK_HEADERS_HASH` — used when accumulating block headers into `blockHeadersHash`. * `DOM_SEP__BLOB_HASHED_Y_LIMBS`, `DOM_SEP__BLOB_CHALLENGE_Z`, `DOM_SEP__BLOB_Z_ACC`, `DOM_SEP__BLOB_GAMMA_ACC`, `DOM_SEP__BLOB_GAMMA_FINAL` — blob accumulator and challenge derivations. **⚠️ Hard-coded test constants will no longer match.** Every value derived from any of the hashes above is new in this release. This includes (non-exhaustively): * **All merkle tree roots** — note-hash, nullifier, public-data, L1→L2 message, archive, VK, and the AVM-internal written-slots and retrieved-bytecodes (class-ids) trees. * **Genesis constants** — `GENESIS_BLOCK_HEADER_HASH`, `GENESIS_ARCHIVE_ROOT`, `AVM_WRITTEN_PUBLIC_DATA_SLOTS_TREE_INITIAL_ROOT`, `AVM_RETRIEVED_BYTECODES_TREE_INITIAL_ROOT`. * **Every block hash and archive root** — they commit to tree roots and the new block-headers-hash. * **Protocol contract addresses** — their derivation depends on the private-function tree root. * **Blob commitments / challenges** — any test that pins `z`, `gamma`, or the accumulator outputs. Regenerate these values from a fresh build of this release — do not copy them from previous release fixtures. **If you re-implement any of these hashes off-circuit** (e.g. a wallet that computes nullifier low-leaf membership, or an indexer that derives block hashes / tree roots), update the call sites: ``` // Merkle sibling-pair hash (append-only trees) - poseidon2Hash([left, right]) + poseidon2HashWithSeparator([left, right], DomainSeparator.MERKLE_HASH) // Indexed-tree sibling-pair hash — pick the matching tree separator - poseidon2Hash([left, right]) + poseidon2HashWithSeparator([left, right], DomainSeparator.NULLIFIER_MERKLE) + // or PUBLIC_DATA_MERKLE, WRITTEN_SLOTS_MERKLE, RETRIEVED_BYTECODES_MERKLE ``` `poseidon2HashWithSeparator` is exported from `@aztec/foundation/crypto/poseidon`; the `DomainSeparator` enum and the matching `DOM_SEP__*` constants are defined in `@aztec/constants`. The new entries listed above are additions — existing separator names are unchanged. For TypeScript consumers, `@aztec/stdlib/hash` exports ready-made helpers that wrap the right separator: `computeMerkleHash` (append-only), `computeNullifierMerkleHash`, and `computePublicDataMerkleHash`. Prefer these over calling `poseidon2HashWithSeparator` directly so the separator choice stays colocated with the tree. ### \[aztec-nr] Nullifier membership witness oracle returns split types[​](#aztec-nr-nullifier-membership-witness-oracle-returns-split-types "Direct link to \[aztec-nr] Nullifier membership witness oracle returns split types") `get_nullifier_membership_witness` and `get_low_nullifier_membership_witness` now return `(NullifierLeafPreimage, MembershipWitness)` instead of the bundled `NullifierMembershipWitness` struct (which has been removed). If you were using these oracle functions directly (e.g. in `schnorr_account_contract`'s `lookup_validity`), update your code to destructure the tuple: ``` - let witness = get_low_nullifier_membership_witness(block_header, siloed_nullifier); - let nullifier_value = witness.leaf_preimage.nullifier; - let index = witness.index; - let path = witness.path; + let (leaf_preimage, witness) = get_low_nullifier_membership_witness(block_header, siloed_nullifier); + let nullifier_value = leaf_preimage.nullifier; + let index = witness.leaf_index; + let path = witness.sibling_path; ``` Note the field renames: `index` is now `leaf_index`, and `path` is now `sibling_path` (matching the protocol circuit's `MembershipWitness` type). This has been done because this is the format expected by the functionality in protocol circuits and given that this is sensitive security-wise it made sense to reuse that functionality in Aztec.nr. ### \[L1 Contracts] Empire slasher removed, slasher config simplified[​](#l1-contracts-empire-slasher-removed-slasher-config-simplified "Direct link to \[L1 Contracts] Empire slasher removed, slasher config simplified") The empire slashing model has been removed. Only the tally-based slashing model remains, and it has been renamed from `TallySlashingProposer` to `SlashingProposer`. **L1 contract changes:** * `SlasherFlavor` enum removed from `ISlasher.sol` * `RollupConfigInput.slasherFlavor` (enum) replaced with `slasherEnabled` (bool) * `TallySlashingProposer` contract renamed to `SlashingProposer` * `TallySlasherDeploymentExtLib` library renamed to `SlasherDeploymentExtLib` * `SlashFactory` periphery contract removed * `SLASHING_PROPOSER_TYPE` constant removed from `SlashingProposer` * All `TallySlashingProposer__` error prefixes renamed to `SlashingProposer__` **Environment variable changes:** ``` - AZTEC_SLASHER_FLAVOR=tally # was: "tally" | "empire" | "none" + AZTEC_SLASHER_ENABLED=true # now a boolean ``` **Removed environment variables:** `SLASH_MIN_PENALTY_PERCENTAGE`, `SLASH_MAX_PENALTY_PERCENTAGE` **Removed from deploy outputs:** `slashFactoryAddress` **Node admin API:** `getSlashPayloads()` method removed. **TypeScript config changes:** ``` - slasherFlavor: 'tally' | 'none' + slasherEnabled: boolean ``` `slashMinPenaltyPercentage` and `slashMaxPenaltyPercentage` removed from `SlasherConfig`. ### \[Aztec Node] `getTxByHash`, `getTxsByHash` and `getPendingTxs` no longer return tx proofs by default[​](#aztec-node-gettxbyhash-gettxsbyhash-and-getpendingtxs-no-longer-return-tx-proofs-by-default "Direct link to aztec-node-gettxbyhash-gettxsbyhash-and-getpendingtxs-no-longer-return-tx-proofs-by-default") `AztecNode.getTxByHash`, `AztecNode.getTxsByHash` and `AztecNode.getPendingTxs` (also exposed on the P2P API) now take an optional `GetTxByHashOptions` argument with an `includeProof` flag. The proof is stripped from returned txs unless `includeProof: true` is passed, cutting roughly 35-52KB per tx over the wire. **Migration:** ``` - const tx = await node.getTxByHash(txHash); + const tx = await node.getTxByHash(txHash, { includeProof: true }); - const txs = await node.getPendingTxs(limit, after); + const txs = await node.getPendingTxs(limit, after, { includeProof: true }); ``` **Impact**: Callers that read the proof off returned txs (eg to re-broadcast or validate them) must now pass `{ includeProof: true }` explicitly; by default the returned txs carry an empty proof. ### \[aztec.js] `DeployMethod.send()` always returns `{ contract, receipt, instance }`[​](#aztecjs-deploymethodsend-always-returns--contract-receipt-instance- "Direct link to aztecjs-deploymethodsend-always-returns--contract-receipt-instance-") The `returnReceipt` option in deploy wait options has been removed. `DeployMethod.send()` now always returns an object with `contract`, `receipt`, and `instance` at the top level, provided the user waits for the transaction to be included. The `DeployTxReceipt` and `DeployWaitOptions` types have been removed. **Migration:** ``` - const { - receipt: { contract, instance }, - } = await MyContract.deploy(wallet, ...args).send({ - from: address, - wait: { returnReceipt: true }, - }); + const { contract, instance } = await MyContract.deploy(wallet, ...args).send({ + from: address, + }); ``` ### \[CLI] `aztec init` now scaffolds a Counter example template[​](#cli-aztec-init-now-scaffolds-a-counter-example-template "Direct link to cli-aztec-init-now-scaffolds-a-counter-example-template") `aztec init` previously created a blank contract crate. It now scaffolds a runnable **Counter** example contract with a constructor, `increment`, and `get_counter` functions, plus a test suite, so new developers have a working starting point ([#22751](https://github.com/AztecProtocol/aztec-packages/pull/22751)). * `aztec init` — scaffolds the Counter example (new default). * `aztec new ` — still scaffolds a blank contract, either as a new standalone project or as a new crate added to an existing workspace. **Impact**: any scripts, CI jobs, or onboarding docs that ran `aztec init` expecting an empty contract starting point now get the Counter example. Use `aztec new ` for the blank scaffold. The existing Counter tutorial under [`docs/tutorials/contract_tutorials`](/developers/testnet/docs/tutorials/contract_tutorials/counter_contract.md) is unaffected because it uses `aztec new`. ## 4.3.0[​](#430 "Direct link to 4.3.0") ### `aztec new` and `aztec init` now create a 2-crate workspace[​](#aztec-new-and-aztec-init-now-create-a-2-crate-workspace "Direct link to 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 `contract` crate (type = "contract") for your smart contract code * A `test` crate (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 `--contract` and `--lib` flags have been removed from `aztec new` and `aztec init`. These commands now always create a contract workspace. * Contract code is now at `contract/src/main.nr` instead of `src/main.nr`. * The `Nargo.toml` in the project root is now a workspace file. Contract dependencies go in `contract/Nargo.toml`. * Tests should be written in the separate `test` crate (`test/src/lib.nr`) and import the contract by package name (e.g., `use my_contract::MyContract;`) instead of using `crate::`. ### `aztec new` crate directories are now named after the contract[​](#aztec-new-crate-directories-are-now-named-after-the-contract "Direct link to aztec-new-crate-directories-are-now-named-after-the-contract") `aztec new` and `aztec init` now name the generated crate directories after the contract instead of using generic `contract/` and `test/` names. For example, `aztec new counter` now creates: ``` counter/ ├── Nargo.toml # [workspace] members = ["counter_contract", "counter_test"] ├── counter_contract/ │ ├── src/main.nr │ └── Nargo.toml # type = "contract" └── counter_test/ ├── src/lib.nr └── Nargo.toml # type = "lib" ``` This enables adding multiple contracts to a single workspace. Running `aztec new ` inside an existing workspace (a directory with a `Nargo.toml` containing `[workspace]`) now adds a new `_contract` and `_test` crate pair to the workspace instead of creating a new directory. **What changed:** * Crate directories are now `_contract/` and `_test/` instead of `contract/` and `test/`. * Contract code is now at `_contract/src/main.nr` instead of `contract/src/main.nr`. * Contract dependencies go in `_contract/Nargo.toml` instead of `contract/Nargo.toml`. * Tests import the contract by its new crate name (e.g., `use counter_contract::Main;` instead of `use counter::Main;`). ### \[CLI] `--name` flag removed from `aztec new` and `aztec init`[​](#cli---name-flag-removed-from-aztec-new-and-aztec-init "Direct link to cli---name-flag-removed-from-aztec-new-and-aztec-init") The `--name` flag has been removed from both `aztec new` and `aztec init`. For `aztec new`, the positional argument now serves as both the contract name and the directory name. For `aztec init`, the directory name is always used as the contract name. **Migration:** ``` - aztec new my_project --name counter + aztec new counter ``` ``` - aztec init --name counter + aztec init ``` **Impact**: If you were using `--name` to set a contract name different from the directory name, rename your directory or use `aztec new` with the desired contract name directly. ## 4.2.0[​](#420 "Direct link to 4.2.0") ### \[Aztec.js] `GasSettings.default()` renamed to `GasSettings.fallback()`[​](#aztecjs-gassettingsdefault-renamed-to-gassettingsfallback "Direct link to aztecjs-gassettingsdefault-renamed-to-gassettingsfallback") `GasSettings.default()` has been renamed to `GasSettings.fallback()` to clarify that these gas limits are not protocol defaults — the protocol has no concept of "default" gas settings. `fallback()` is a convenience for cases where gas estimation is not being used, but callers should prefer estimating gas via simulation for accurate limits. The old `DEFAULT_GAS_LIMIT` and `DEFAULT_TEARDOWN_GAS_LIMIT` constants have been removed. Gas limits are now derived from protocol-level maximums (`MAX_PROCESSABLE_L2_GAS`, `MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT`) rather than arbitrary fixed values. A new `GasSettings.forEstimation()` method provides intentionally high gas limits for use during simulation. These limits exceed protocol maximums so the simulation doesn't hit gas caps — you must pass `skipTxValidation: true` when simulating with them, then use the results to set accurate gas limits on the actual transaction. `EmbeddedWallet` does this by default. **Migration:** ``` - import { DEFAULT_GAS_LIMIT, DEFAULT_TEARDOWN_GAS_LIMIT } from '@aztec/constants'; - const settings = GasSettings.default({ maxFeesPerGas }); + const settings = GasSettings.fallback({ maxFeesPerGas }); ``` **Impact**: Any code referencing `GasSettings.default()`, `DEFAULT_GAS_LIMIT`, or `DEFAULT_TEARDOWN_GAS_LIMIT` will fail to compile. ### \[PXE] `simulateTx`, `executeUtility`, `profileTx`, and `proveTx` no longer accept `scopes: 'ALL_SCOPES'`[​](#pxe-simulatetx-executeutility-profiletx-and-provetx-no-longer-accept-scopes-all_scopes "Direct link to pxe-simulatetx-executeutility-profiletx-and-provetx-no-longer-accept-scopes-all_scopes") The `AccessScopes` type (`'ALL_SCOPES' | AztecAddress[]`) has been removed. The `scopes` field in `SimulateTxOpts`, `ExecuteUtilityOpts`, and `ProfileTxOpts` now requires an explicit `AztecAddress[]`. Callers that previously passed `'ALL_SCOPES'` must now specify which addresses will be in scope for the call. **Migration:** ``` + const accounts = await pxe.getRegisteredAccounts(); + const scopes = accounts.map(a => a.address); // simulateTx - await pxe.simulateTx(txRequest, { simulatePublic: true, scopes: 'ALL_SCOPES' }); + await pxe.simulateTx(txRequest, { simulatePublic: true, scopes }); // executeUtility - await pxe.executeUtility(call, { scopes: 'ALL_SCOPES' }); + await pxe.executeUtility(call, { scopes }); // profileTx - await pxe.profileTx(txRequest, { profileMode: 'full', scopes: 'ALL_SCOPES' }); + await pxe.profileTx(txRequest, { profileMode: 'full', scopes }); // proveTx - await pxe.proveTx(txRequest, 'ALL_SCOPES'); + await pxe.proveTx(txRequest, scopes); ``` **Impact**: Any code passing `'ALL_SCOPES'` to `simulateTx`, `executeUtility`, `profileTx`, or `proveTx` will fail to compile. Replace with an explicit array of account addresses. ### \[PXE] Capsule operations are now scope-enforced at the PXE level[​](#pxe-capsule-operations-are-now-scope-enforced-at-the-pxe-level "Direct link to \[PXE] Capsule operations are now scope-enforced at the PXE level") The PXE now enforces that capsule operations can only access scopes that were authorized for the current execution. If a contract attempts to access a capsule scope that is not in its allowed scopes list, the PXE will throw an error: ``` Scope 0x1234... is not in the allowed scopes list: [0xabcd...]. ``` The zero address (`AztecAddress::zero()`) is always allowed regardless of the scopes list, preserving backwards compatibility for contracts using the global scope. **Impact**: Contracts that access capsules scoped to addresses not included in the transaction's authorized scopes will now fail at runtime. Ensure the correct scopes are passed when executing transactions. ### \[aztec.js] `EmbeddedWalletOptions` now uses a unified `pxe` field[​](#aztecjs-embeddedwalletoptions-now-uses-a-unified-pxe-field "Direct link to aztecjs-embeddedwalletoptions-now-uses-a-unified-pxe-field") The `pxeConfig` and `pxeOptions` fields on `EmbeddedWalletOptions` have been deprecated in favor of a single `pxe` field that accepts both PXE configuration and dependency overrides (custom prover, store, simulator): ``` const wallet = await EmbeddedWallet.create(nodeUrl, { - pxeConfig: { proverEnabled: true }, - pxeOptions: { proverOrOptions: myCustomProver }, + pxe: { + proverEnabled: true, + proverOrOptions: myCustomProver, + }, }); ``` The old fields still work but will be removed in a future release. ### \[Aztec.nr] Ephemeral arrays replace capsule arrays in PXE oracle interfaces[​](#aztecnr-ephemeral-arrays-replace-capsule-arrays-in-pxe-oracle-interfaces "Direct link to \[Aztec.nr] Ephemeral arrays replace capsule arrays in PXE oracle interfaces") Oracle interfaces between Aztec.nr and PXE now use a new `EphemeralArray` type (`aztec::ephemeral::EphemeralArray`) instead of `CapsuleArray`. Ephemeral arrays live in memory and are scoped by contract call frame, so they no longer need to be addressed by `(contract_address, scope)`. Several public message-discovery and validation functions lost their `recipient`, `scope`, and `contract_address` parameters as a result. Most contracts are not affected, as the macro-generated `sync_state` and `process_message` functions handle these APIs automatically. Only contracts that call these functions directly need to update. **Migration:** ``` attempt_note_discovery( contract_address, tx_hash, unique_note_hashes_in_tx, first_nullifier_in_tx, - recipient, compute_note_hash, compute_note_nullifier, owner, storage_slot, randomness, note_type_id, packed_note, ); - enqueue_note_for_validation(contract_address, owner, storage_slot, randomness, note_nonce, packed_note, note_hash, nullifier, tx_hash, scope); + enqueue_note_for_validation(contract_address, owner, storage_slot, randomness, note_nonce, packed_note, note_hash, nullifier, tx_hash); - enqueue_event_for_validation(contract_address, event_type_id, randomness, serialized_event, event_commitment, tx_hash, scope); + enqueue_event_for_validation(contract_address, event_type_id, randomness, serialized_event, event_commitment, tx_hash); - validate_and_store_enqueued_notes_and_events(contract_address, scope); + validate_and_store_enqueued_notes_and_events(scope); ``` The `sync_inbox` function and the `OffchainInboxSync` type now return `EphemeralArray` instead of `CapsuleArray`. Custom message handlers that bind the returned array to an explicit type must update the type annotation. **Impact**: Contracts that call the above functions directly (rather than relying on macro-generated code) will fail to compile until the trailing `recipient`, `scope`, and `contract_address` parameters are removed. ## 4.2.0-aztecnr-rc.2[​](#420-aztecnr-rc2 "Direct link to 4.2.0-aztecnr-rc.2") ### \[Aztec.js] Removed `SingleKeyAccountContract`[​](#aztecjs-removed-singlekeyaccountcontract "Direct link to aztecjs-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. ### Custom token FPCs removed from default public setup allowlist[​](#custom-token-fpcs-removed-from-default-public-setup-allowlist "Direct link to Custom token FPCs removed from default public setup allowlist") Token contract functions (like `transfer_in_public` and `_increase_public_balance`) have been removed from the default public setup allowlist. FPCs that accept custom tokens (like the reference `FPC` contract) will not work on public networks, because their setup-phase calls to these functions will be rejected. Token class IDs change with each aztec-nr release, making it impractical to maintain them in the allowlist. FPCs that use only Fee Juice still work on all networks, since FeeJuice is a protocol contract with a fixed address in the allowlist. Custom FPCs should only call protocol contract functions (AuthRegistry, FeeJuice) during setup. `PublicFeePaymentMethod` and `PrivateFeePaymentMethod` in aztec.js are affected, since they use the reference `FPC` contract which calls Token functions during setup. Switch to `FeeJuicePaymentMethodWithClaim` (after [bridging Fee Juice from L1](/developers/testnet/docs/aztec-js/how_to_pay_fees.md#bridge-fee-juice-from-l1)) or write an FPC that uses Fee Juice natively. **Migration:** ``` - import { PublicFeePaymentMethod } from '@aztec/aztec.js/fee'; - const paymentMethod = new PublicFeePaymentMethod(fpcAddress, senderAddress, wallet, gasSettings); + import { FeeJuicePaymentMethodWithClaim } from '@aztec/aztec.js/fee'; + const paymentMethod = new FeeJuicePaymentMethodWithClaim(senderAddress, claim); ``` Similarly, the `fpc-public` and `fpc-private` CLI wallet payment methods use the reference Token-based FPC and will not work on public networks. Use `fee_juice` for direct Fee Juice payment, or `fpc-sponsored` on devnet and local network. ### \[Aztec.nr] Domain-separated tags on log emission[​](#aztecnr-domain-separated-tags-on-log-emission "Direct link to \[Aztec.nr] Domain-separated tags on log emission") All logs emitted through the Aztec.nr framework now include a domain-separated tag at `fields[0]`. Each log category uses its own domain separator via `compute_log_tag(raw_tag, dom_sep)`: * **Events** (`DOM_SEP__EVENT_LOG_TAG`): the event type ID is the raw tag. * **Message delivery** (`DOM_SEP__UNCONSTRAINED_MSG_LOG_TAG`): the discovery tag is the raw tag. * **Partial note completion logs** (`DOM_SEP__NOTE_COMPLETION_LOG_TAG`): the partial note's `commitment` field is the raw tag. The low-level emit methods now take `tag` as an explicit first parameter and have been renamed with an `_unsafe` suffix. Previously the tag was included as `log[0]` — it has now been extracted into its own parameter, and `log` no longer contains it: ``` - context.emit_private_log(log, length); + context.emit_private_log_unsafe(tag, log, length); - context.emit_raw_note_log(log, length, note_hash_counter); + context.emit_raw_note_log_unsafe(tag, log, length, note_hash_counter); - context.emit_public_log(log); + context.emit_public_log_unsafe(tag, log); ``` Prefer the higher-level APIs (`emit` for events, `MessageDelivery` for messages) which handle tagging automatically. ### \[Aztec.nr] Public events no longer include the event type selector at the end of the payload[​](#aztecnr-public-events-no-longer-include-the-event-type-selector-at-the-end-of-the-payload "Direct link to \[Aztec.nr] Public events no longer include the event type selector at the end of the payload") `emit_event_in_public` previously appended the event type selector as the last field. It now prepends a domain-separated tag at `fields[0]` instead. The payload after the tag contains only the serialized event fields. If you were reading public event directly from node logs (i.e. via `node.getPublicLogs` and not via `wallet.getPublicEvents`), update your parsing: ``` - // Old: fields = [serialized_event..., event_type_selector] - const selector = EventSelector.fromField(fields[fields.length - 1]); - const event = decodeFromAbi([abiType], fields); + // New: fields = [domain_separated_tag, serialized_event...] + const eventFields = log.getEmittedFieldsWithoutTag(); + const event = decodeFromAbi([abiType], eventFields); ``` ### \[Aztec.nr] Capsule operations are now addressed by scope[​](#aztecnr-capsule-operations-are-now-addressed-by-scope "Direct link to \[Aztec.nr] Capsule operations are now addressed by scope") All capsule operations (`store`, `load`, `delete`, `copy`) and `CapsuleArray` now require a `scope: AztecAddress` parameter. This scopes capsule storage by address, providing isolation between different accounts within the same PXE. Contracts that use `CapsuleArray` directly also need to update. **Migration:** ``` - let array: CapsuleArray = CapsuleArray::at(contract_address, slot); + let array: CapsuleArray = CapsuleArray::at(contract_address, slot, scope); ``` The low-level capsule functions are similarly affected: ``` - capsules::store(contract_address, slot, value); + capsules::store(contract_address, slot, value, scope); - capsules::load(contract_address, slot); + capsules::load(contract_address, slot, scope); - capsules::delete(contract_address, slot); + capsules::delete(contract_address, slot, scope); - capsules::copy(contract_address, src_slot, dst_slot, num_entries); + capsules::copy(contract_address, src_slot, dst_slot, num_entries, scope); ``` If you need to stick the old, scope-less behavior, and you are really sure that that's what you need to use, you can use `scope = AztecAddress::zero()`. ### \[Aztec.nr] `process_message` utility function removed[​](#aztecnr-process_message-utility-function-removed "Direct link to aztecnr-process_message-utility-function-removed") The auto-generated `process_message` utility function has been removed. If you need to deliver offchain messages (messages not broadcast via onchain logs), use the `offchain_receive` utility function instead. This function is automatically injected by the `#[aztec]` macro and accepts messages into a persistent inbox scoped by recipient. These messages are then picked up and processed during `sync_state`. **Impact**: Contracts that explicitly called `process_message` must switch to delivering messages via `offchain_receive` and letting `sync_state` handle processing. ### \[Aztec.nr] `CustomMessageHandler` type signature changed[​](#aztecnr-custommessagehandler-type-signature-changed "Direct link to aztecnr-custommessagehandler-type-signature-changed") The `CustomMessageHandler` function type now receives an additional `scope: AztecAddress` parameter: ``` type CustomMessageHandler = unconstrained fn( AztecAddress, // contract_address u64, // msg_type_id u64, // msg_metadata BoundedVec, // msg_content MessageContext, // message_context + AztecAddress, // scope ); ``` **Impact**: Contracts that implement a custom message handler must update the function signature. ### \[aztec.js] `isContractInitialized` is now `initializationStatus` tri-state enum[​](#aztecjs-iscontractinitialized-is-now-initializationstatus-tri-state-enum "Direct link to aztecjs-iscontractinitialized-is-now-initializationstatus-tri-state-enum") `ContractMetadata.isContractInitialized` has been renamed to `ContractMetadata.initializationStatus` and changed from `boolean | undefined` to a `ContractInitializationStatus` enum with values `INITIALIZED`, `UNINITIALIZED`, and `UNKNOWN`. * `INITIALIZED`: the contract has been initialized (initialization nullifier found) * `UNINITIALIZED`: the contract instance is registered but has not been initialized * `UNKNOWN`: the instance is not registered and no public initialization nullifier was found When the instance is not registered, the wallet now attempts to check the public initialization nullifier (computed from address alone) before returning `UNKNOWN`. Previously this case returned `undefined`. **Migration:** ``` + import { ContractInitializationStatus } from '@aztec/aztec.js/wallet'; const metadata = await wallet.getContractMetadata(address); - if (metadata.isContractInitialized) { + if (metadata.initializationStatus === ContractInitializationStatus.INITIALIZED) { // contract is initialized } ``` ### \[Aztec.js] Use `NO_FROM` instead of `AztecAddress.ZERO` to bypass account contract entrypoint[​](#aztecjs-use-no_from-instead-of-aztecaddresszero-to-bypass-account-contract-entrypoint "Direct link to aztecjs-use-no_from-instead-of-aztecaddresszero-to-bypass-account-contract-entrypoint") When sending transactions that should not be mediated by an account contract (e.g., account contract self-deployments), use the explicit `NO_FROM` sentinel instead of `AztecAddress.ZERO`. `NO_FROM` signals that the transaction should be executed directly via the `DefaultEntrypoint`. This replaces the brittle convention of passing `AztecAddress.ZERO` as the `from` field. **Migration:** ``` - import { AztecAddress } from '@aztec/aztec.js'; + import { NO_FROM } from '@aztec/aztec.js/account'; await contract.methods.my_method().send({ - from: AztecAddress.ZERO, + from: NO_FROM, }); ``` Note that `DefaultEntrypoint` only accepts a single call. If you need to execute multiple calls without account contract mediation (e.g., deploying an account contract and paying a fee in the same transaction), wrap them through `DefaultMultiCallEntrypoint` on the app side before sending: ``` import { NO_FROM } from "@aztec/aztec.js/account"; import { DefaultMultiCallEntrypoint } from "@aztec/entrypoints/multicall"; import { mergeExecutionPayloads } from "@aztec/stdlib/tx"; // Merge multiple execution payloads into one const merged = mergeExecutionPayloads([deployPayload, feePayload]); // Wrap through multicall so it becomes a single call for DefaultEntrypoint const multicall = new DefaultMultiCallEntrypoint(); const chainInfo = await wallet.getChainInfo(); const wrappedPayload = await multicall.wrapExecutionPayload(merged, chainInfo); // Send without account contract mediation await wallet.sendTx(wrappedPayload, { from: NO_FROM }); ``` Using other contracts for wrapping (for example, supporting more calls) is also supported, as long as the contract is registered in the wallet. This opens the door to different flows that do not use account entrypoints as the first call in the chain, including app sponsored FPCs. **Impact**: Any code that passes `AztecAddress.ZERO` as the `from` option in `.send()`, `.simulate()`, or deploy options must switch to `NO_FROM`. Wallets use `DefaultEntrypoint` directly for `NO_FROM` transactions, instead of the `DefaultMultiCallEntrypoint` that was used internally before when specifying `AztecAddress.ZERO`. ### \[Aztec.js] `ExecuteUtilityOptions.scope` renamed to `scopes` and type changed to `AztecAddress[]`[​](#aztecjs-executeutilityoptionsscope-renamed-to-scopes-and-type-changed-to-aztecaddress "Direct link to aztecjs-executeutilityoptionsscope-renamed-to-scopes-and-type-changed-to-aztecaddress") The `scope` field in `ExecuteUtilityOptions` has been renamed to `scopes` and changed from a single `AztecAddress` to `AztecAddress[]`. This aligns the wallet's `executeUtility` API with the PXE API and `sendTx` in `Wallet`, which both accept an array of scopes. **Migration:** ``` wallet.executeUtility(call, { - scope: myAddress, + scopes: [myAddress], }); ``` **Impact**: Any code that calls `wallet.executeUtility` directly must update the options object. Wallets must update to adapt to the new interface ### \[Aztec.nr] `attempt_note_discovery` now takes two separate functions instead of one[​](#aztecnr-attempt_note_discovery-now-takes-two-separate-functions-instead-of-one "Direct link to aztecnr-attempt_note_discovery-now-takes-two-separate-functions-instead-of-one") The `attempt_note_discovery` function (and related discovery functions like `do_sync_state`, `process_message_ciphertext`) now takes separate `compute_note_hash` and `compute_note_nullifier` arguments instead of a single combined `compute_note_hash_and_nullifier`. The corresponding type aliases are now `ComputeNoteHash` and `ComputeNoteNullifier` (instead of `ComputeNoteHashAndNullifier`). This split improves performance during nonce discovery: the note hash only needs to be computed once, while the old combined function recomputed it for every candidate nonce. Most contracts are not affected, as the macro-generated `sync_state` and `process_message` functions handle this automatically. Only contracts that call `attempt_note_discovery` directly need to update. **Migration:** ``` attempt_note_discovery( contract_address, tx_hash, unique_note_hashes_in_tx, first_nullifier_in_tx, recipient, - _compute_note_hash_and_nullifier, + _compute_note_hash, + _compute_note_nullifier, owner, storage_slot, randomness, note_type_id, packed_note, ); ``` **Impact**: Contracts that call `attempt_note_discovery` or related discovery functions directly with a custom `_compute_note_hash_and_nullifier` argument. The old combined function is still generated (deprecated) but is no longer used by the framework. Additionally, if you had a custom `_compute_note_hash_and_nullifier` function then compilation will now fail as you'll need to also produce the corresponding `_compute_note_hash` and `_compute_note_nullifier` functions. ### Private initialization nullifier now includes `init_hash`[​](#private-initialization-nullifier-now-includes-init_hash "Direct link to private-initialization-nullifier-now-includes-init_hash") The private initialization nullifier is no longer derived from just the contract address. It is now computed as a Poseidon2 hash of `[address, init_hash]` using a dedicated domain separator. This prevents observers from determining whether a fully private contract has been initialized by simply knowing its address. Note that `Wallet.getContractMetadata` now returns `initializationStatus: ContractInitializationStatus.UNKNOWN` when the wallet does not have the contract instance registered, since `init_hash` is needed to compute the nullifier and initialization status cannot be determined. Previously, this check worked for any address. Callers should check the enum value before branching on the initialization state. If you use `assert_contract_was_initialized_by` or `assert_contract_was_not_initialized_by` from `aztec::history::deployment`, these now require an additional `init_hash: Field` parameter: ``` + let instance = get_contract_instance(contract_address); assert_contract_was_initialized_by( block_header, contract_address, + instance.initialization_hash, ); ``` ### Two separate init nullifiers for private and public[​](#two-separate-init-nullifiers-for-private-and-public "Direct link to Two separate init nullifiers for private and public") Contract initialization now emits two separate nullifiers instead of one: a **private init nullifier** and a **public init nullifier**. Each nullifier gates its respective execution domain: * Private external functions check the private init nullifier. * Public external functions check the public init nullifier. **How initializers work:** * **Private initializers** emit the private init nullifier. If the contract has any external public functions, the protocol auto-enqueues a public call to emit the public init nullifier. * **Public initializers** emit both nullifiers directly. * Contracts with no public functions only emit the private init nullifier. **`only_self` functions no longer have init checks.** They behave as if marked `noinitcheck`. **External functions called during private initialization must be `#[only_self]`.** Init nullifiers are emitted at the end of the initializer, so any external functions called on the initializing contract (e.g. via `enqueue_self` or `call_self`) during initialization will fail the init check unless they skip it. **Breaking change for deployment:** If your contract has external public functions and a private initializer, the class must be registered onchain before initialization. You can no longer pass `skipClassPublication: true`, because the auto-enqueued public call requires the class to be available. ``` const deployed = await MyContract.deploy(wallet, ...args).send({ - skipClassPublication: true, }).deployed(); ``` ### \[Aztec.nr] Made `compute_note_hash_for_nullification` unconstrained[​](#aztecnr-made-compute_note_hash_for_nullification-unconstrained "Direct link to aztecnr-made-compute_note_hash_for_nullification-unconstrained") This function shouldn't have been constrained in the first place, as constrained computation of `HintedNote` nullifiers is dangerous (constrained computation of nullifiers can be performed only on the `ConfirmedNote` type). If you were calling this from a constrained function, consider using `compute_confirmed_note_hash_for_nullification` instead. Unconstrained usage is safe. ### \[Aztec.nr] Changes to standard note hash computation[​](#aztecnr-changes-to-standard-note-hash-computation "Direct link to \[Aztec.nr] Changes to standard note hash computation") Note hashes used to be computed with the storage slot being the last value of the preimage, it is now the first. This is to make it easier to ensure all note hashes have proper domain separation. This change requires no input from your side unless you were testing or relying on hardcoded note hashes. ## 4.1.3[​](#413 "Direct link to 4.1.3") ### \[Aztec.js] `TxReceipt` now includes `epochNumber`[​](#aztecjs-txreceipt-now-includes-epochnumber "Direct link to aztecjs-txreceipt-now-includes-epochnumber") `TxReceipt` now includes an `epochNumber` field that indicates which epoch the transaction was included in. ### \[Aztec.js] Use `getL2ToL1MembershipWitness` for L2-to-L1 message witnesses[​](#aztecjs-use-getl2tol1membershipwitness-for-l2-to-l1-message-witnesses "Direct link to aztecjs-use-getl2tol1membershipwitness-for-l2-to-l1-message-witnesses") The node now computes L2-to-L1 membership witnesses directly, resolving the epoch and Outbox root internally from the transaction hash. Use `getL2ToL1MembershipWitness` instead of fetching raw epoch messages and computing the witness client-side. **Migration:** ``` - const messages = await aztecNode.getL2ToL1Messages(epochNumber); - // compute the witness client-side from the epoch messages + const witness = await aztecNode.getL2ToL1MembershipWitness(txHash, messageHash); + const epoch = witness.epochNumber; ``` The return type `L2ToL1MembershipWitness` includes `epochNumber`. An optional `messageIndexInTx` parameter can be passed as the third argument to disambiguate when a transaction emits multiple identical L2-to-L1 messages. **Impact**: Call sites that compute L2-to-L1 membership witnesses should use the node method and extract `epochNumber` from the result. ### \[Aztec.js] `getPublicEvents` now returns an object instead of an array[​](#aztecjs-getpublicevents-now-returns-an-object-instead-of-an-array "Direct link to aztecjs-getpublicevents-now-returns-an-object-instead-of-an-array") `getPublicEvents` now returns a `GetPublicEventsResult` object with `events` and `maxLogsHit` fields instead of a plain array. This enables pagination through large result sets using the new `afterLog` filter option. ``` - const events = await getPublicEvents(node, MyContract.events.MyEvent, filter); + const { events } = await getPublicEvents(node, MyContract.events.MyEvent, filter); ``` The `maxLogsHit` flag indicates whether the log limit was reached, meaning more results may be available. You can use `afterLog` in the filter to fetch the next page. ### \[Aztec.nr] Removed `get_random_bytes`[​](#aztecnr-removed-get_random_bytes "Direct link to aztecnr-removed-get_random_bytes") The `get_random_bytes` unconstrained function has been removed from `aztec::utils::random`. If you were using it, you can replace it with direct calls to the `random` oracle from `aztec::oracle::random` and convert to bytes yourself. ## 4.1.0-rc.2[​](#410-rc2 "Direct link to 4.1.0-rc.2") ### \[Aztec.js] `simulate()`, `send()`, and deploy return types changed to always return objects[​](#aztecjs-simulate-send-and-deploy-return-types-changed-to-always-return-objects "Direct link to aztecjs-simulate-send-and-deploy-return-types-changed-to-always-return-objects") All SDK interaction methods now return structured objects that include offchain output alongside the primary result. This affects `.simulate()`, `.send()`, deploy `.send()`, and `Wallet.sendTx()`. **Impact**: Every call site that uses `.simulate()`, `.send()`, or deploy must destructure the result. This is a mechanical transformation. Custom wallet implementations must update `sendTx()` to return the new object shapes, using `extractOffchainOutput` to decode offchain messages from raw effects. The offchain output includes two fields: * `offchainEffects` — raw offchain effects emitted during execution, other than `offchainMessages` * `offchainMessages` — decoded messages intended for specific recipients We are making this change now so in the future we can add more fields to the responses of this APIs without breaking backwards compatibility, so this won't ever happen again. **`simulate()` — always returns `{ result, offchainEffects, offchainMessages }` object:** ``` - const value = await contract.methods.foo(args).simulate({ from: sender }); + const { result: value } = await contract.methods.foo(args).simulate({ from: sender }); ``` When using `includeMetadata` or `fee.estimateGas`, `stats` and `estimatedGas` are also available as optional fields on the same object: ``` - const { stats, estimatedGas } = await contract.methods.foo(args).simulate({ + const sim = await contract.methods.foo(args).simulate({ from: sender, includeMetadata: true, }); + const stats = sim.stats!; + const estimatedGas = sim.estimatedGas!; ``` `SimulationReturn` is no longer a generic conditional type — it's a single flat type with optional `stats` and `estimatedGas` fields. **`send()` — returns `{ receipt, offchainEffects, offchainMessages }` object:** ``` - const receipt = await contract.methods.foo(args).send({ from: sender }); + const { receipt } = await contract.methods.foo(args).send({ from: sender }); ``` When using `NO_WAIT`, returns `{ txHash, offchainEffects, offchainMessages }` instead of a bare `TxHash`: ``` - const txHash = await contract.methods.foo(args).send({ from: sender, wait: NO_WAIT }); + const { txHash } = await contract.methods.foo(args).send({ from: sender, wait: NO_WAIT }); ``` Offchain messages emitted by the transaction are available on the result: ``` const { receipt, offchainMessages } = await contract.methods .foo(args) .send({ from: sender }); for (const msg of offchainMessages) { console.log( `Message for ${msg.recipient} from contract ${msg.contractAddress}:`, msg.payload, ); } ``` **Deploy — returns `{ contract, receipt, offchainEffects, offchainMessages }` object:** ``` - const myContract = await MyContract.deploy(wallet, ...args).send({ from: sender }); + const { contract: myContract } = await MyContract.deploy(wallet, ...args).send({ from: sender }); ``` The deploy receipt is also available via `receipt` if needed (e.g. for `receipt.txHash` or `receipt.transactionFee`). **Custom wallet implementations — `sendTx()` must return objects:** If you implement the `Wallet` interface (or extend `BaseWallet`), the `sendTx()` method must now return objects that include offchain output. Use `extractOffchainOutput` to split raw effects into decoded messages and remaining effects: ``` + import { extractOffchainOutput } from '@aztec/aztec.js/contracts'; async sendTx(executionPayload, opts) { const provenTx = await this.pxe.proveTx(...); + const offchainOutput = extractOffchainOutput(provenTx.getOffchainEffects()); const tx = await provenTx.toTx(); const txHash = tx.getTxHash(); await this.aztecNode.sendTx(tx); if (opts.wait === NO_WAIT) { - return txHash; + return { txHash, ...offchainOutput }; } const receipt = await waitForTx(this.aztecNode, txHash, opts.wait); - return receipt; + return { receipt, ...offchainOutput }; } ``` ### Scope enforcement for private state access (TXE and PXE)[​](#scope-enforcement-for-private-state-access-txe-and-pxe "Direct link to 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)[​](#noir-developers-txe "Direct link to 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)[​](#aztecjs-developers-pxewallet "Direct link to 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`?** 1. **Deploying contracts whose constructor initializes private storage** (e.g., account contracts, or any contract using `SinglePrivateImmutable`/`SinglePrivateMutable` in the constructor). The contract's own address must be in scope so its nullifier key is accessible during initialization. 2. **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`[​](#simulateutility-renamed-to-executeutility "Direct link to 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[​](#400-devnet2-patch0 "Direct link to 4.0.0-devnet.2-patch.0") ### \[Protocol] `include_by_timestamp` renamed to `expiration_timestamp`[​](#protocol-include_by_timestamp-renamed-to-expiration_timestamp "Direct link to 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[​](#cli-dockerless-cli-installation "Direct link to \[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 ``` **New installation:** ``` VERSION= bash -i <(curl -sL https://install.aztec.network/) ``` For example, to install version `5.0.0-rc.1`: ``` VERSION=5.0.0-rc.1 bash -i <(curl -sL https://install.aztec.network/5.0.0-rc.1) ``` **Key changes:** * Docker is no longer required to run the Aztec CLI tools * The `VERSION` environment 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 ` | Install a specific version and switch to it | | `aztec-up use ` | 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`[​](#aztectest-wallet-replaced-by-aztecwallets "Direct link to aztectest-wallet-replaced-by-aztecwallets") 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`[​](#aztecnr-debug_log-module-renamed-to-logging "Direct link to aztecnr-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[​](#aztecnode-sentinel-validator-status-values-renamed "Direct link to \[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[​](#aztecjs-getdecodedpublicevents-renamed-to-getpublicevents-with-new-signature "Direct link to aztecjs-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`[​](#aztecnr-nophasecheck-renamed-as-allow_phase_change "Direct link to aztecnr-nophasecheck-renamed-as-allow_phase_change") ### \[AztecNode] Removed sibling path RPC methods[​](#aztecnode-removed-sibling-path-rpc-methods "Direct link to \[AztecNode] Removed sibling path RPC methods") The following methods have been removed from the `AztecNode` interface: * `getNullifierSiblingPath` * `getNoteHashSiblingPath` * `getArchiveSiblingPath` * `getPublicDataSiblingPath` 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)[​](#protocol-nullifier-secret-key-renamed-to-nullifier-hiding-key-nsk--nhk "Direct link to \[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`[​](#aztecnodeaztecnr-getarchivemembershipwitness-renamed-to-getblockhashmembershipwitness "Direct link to aztecnodeaztecnr-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`[​](#aztecnr-protocol_types-renamed-to-protocol "Direct link to aztecnr-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[​](#protocol-contract-interface-separate-from-protocol-contracts "Direct link to 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[​](#aztec-nr-history-module-refactored-to-use-standalone-functions "Direct link to \[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[​](#aztecjs-transaction-sending-api-redesign "Direct link to \[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[​](#deployment-changes "Direct link to 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[​](#breaking-changes-to-wallet-interface "Direct link to 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 + sendTx( + payload: ExecutionPayload, + options: SendOptions + ): Promise> ``` #### Manual waiting with `waitForTx`[​](#manual-waiting-with-waitfortx "Direct link to 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[​](#aztec-nr-removal-of-intermediate-modules "Direct link to \[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[​](#l1-contracts-fee-asset-pricing-direction-inverted "Direct link to \[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.ethPerFeeAsset` * `RollupConfigInput` now requires `initialEthPerFeeAsset` parameter at deployment * Default value: `1e7` (0.00001 ETH per AZTEC) * Valid range: `100` (1e-10 ETH/AZTEC) to `1e11` (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[​](#l1-contracts-fee-asset-price-modifier-now-in-basis-points "Direct link to \[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: `-100` to `+100` BPS (±1% max change per checkpoint) * A value of `+100` increases the price by 1%, `-100` decreases by 1% * Validated by `MAX_FEE_ASSET_PRICE_MODIFIER_BPS = 100` ### \[Aztec.js] Wallet batching now supports all methods[​](#aztecjs-wallet-batching-now-supports-all-methods "Direct link to \[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[​](#aztecjs-refactored-getcontractmetadata-and-getcontractclassmetadata-in-wallet "Direct link to aztecjs-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 `registerContract` are needed ### \[Aztec.js] Removed `UnsafeContract` and protocol contract helper functions[​](#aztecjs-removed-unsafecontract-and-protocol-contract-helper-functions "Direct link to aztecjs-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[​](#aztecnr-renamed-router-contract "Direct link to \[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[​](#aztec-node-getblockbyhash-and-getblockheaderbyhash-removed "Direct link to 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[​](#aztecnr-oracle-functions-now-take-blockheader-instead-of-block-number "Direct link to aztecnr-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[​](#toolchain-nodejs-upgraded-to-v24 "Direct link to \[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[​](#l1-contracts-renamed-base-fee-to-min-fee "Direct link to \[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` → `getManaMinFeeAt` * `getManaBaseFeeComponentsAt` → `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[​](#aztecjs-renamed-base-fee-to-min-fee "Direct link to \[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[​](#aztecnr-renamed-fee-context-methods "Direct link to \[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[​](#aztecnr-cleaning-up-message-sender-functions "Direct link to \[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 = self.msg_sender(); + let maybe_message_sender: Option = 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[​](#aztecnr-renamed-message-delivery-options "Direct link to \[Aztec.nr] Renamed message delivery options") The following terms have been renamed: * `MessageDelivery::UNCONSTRAINED_OFFCHAIN` -> `MessageDelivery::OFFCHAIN` * `MessageDelivery::UNCONSTRAINED_ONCHAIN` -> `MessageDelivery::ONCHAIN_UNCONSTRAINED` * `MessageDelivery::CONSTRAINED_ONCHAIN` -> `MessageDelivery::ONCHAIN_CONSTRAINED` We believe these names will better convey the meaning of the concepts. ### \[Aztec Node] changes to `getLogsByTags` endpoint[​](#aztec-node-changes-to-getlogsbytags-endpoint "Direct link to aztec-node-changes-to-getlogsbytags-endpoint") `getLogsByTags` endpoint has been optimized for our new log sync algorithm and these are the changes: * The `logsPerTag` pagination 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 `TxScopedL2Log` has been revised to meet the requirements of our new log sync algorithm. * The endpoint has been separated into two versions: `getPrivateLogsByTags` and `getPublicLogsByTagsFromContract`. This change was made because it was never desirable in PXE to mix public and private logs. The public version requires both a `Tag` and a contract address as input. In contrast to the private version—which uses `SiloedTag` (a tag that hashes the raw tag with the emitting contract's address)—the public version uses the raw `Tag` type, 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[​](#avm-gas-cost-multipliers-for-public-execution-to-reach-simulationproving-parity "Direct link to \[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`[​](#pxe-deprecated-getnotes "Direct link to 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[​](#300-devnet20251212 "Direct link to 3.0.0-devnet.20251212") ### \[Aztec node, archiver] Deprecated `getPrivateLogs`[​](#aztec-node-archiver-deprecated-getprivatelogs "Direct link to 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[​](#aztecnr-private-event-emission-api-changes "Direct link to \[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[​](#aztecnr-history-proof-functions-no-longer-require-storage_slot-parameter "Direct link to aztecnr-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` - removed `storage_slot: Field` parameter * `BlockHeader::prove_note_validity` - removed `storage_slot: Field` parameter * `BlockHeader::prove_note_is_nullified` - removed `storage_slot: Field` parameter * `BlockHeader::prove_note_not_nullified` - removed `storage_slot: Field` parameter **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[​](#aztecnr-note-fields-are-now-public "Direct link to \[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` - `value` is now public, `new()` and `get_value()` removed * `AddressNote` - `address` is now public, `new()` and `get_address()` removed * `FieldNote` - `value` is now public, `new()` and `value()` 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`[​](#aztecnr-emit-renamed-to-deliver "Direct link to aztecnr-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`[​](#aztecnr-valuenote-renamed-to-fieldnote-and-value-note-crate-renamed-to-field-note "Direct link to aztecnr-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.toml` dependencies: `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` → `PrivateSet` ### \[Aztec.nr] New `balance-set` library for managing token balances[​](#aztecnr-new-balance-set-library-for-managing-token-balances "Direct link to aztecnr-new-balance-set-library-for-managing-token-balances") A new `balance-set` library has been created that provides `BalanceSet` for managing u128 token balances with `UintNote`. This consolidates balance management functionality that was previously duplicated across contracts. **Features:** * `add(amount: u128)` - Add to balance * `sub(amount: u128)` - Subtract from balance (with change note) * `try_sub(amount: u128, max_notes: u32)` - Attempt to subtract with configurable note limit * `balance_of()` - Get total balance (unconstrained) **Usage:** ``` use balance_set::BalanceSet; #[storage] struct Storage { balances: Owned, 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(); ``` ### \[Aztec.nr] `EasyPrivateUint` deprecated and removed[​](#aztecnr-easyprivateuint-deprecated-and-removed "Direct link to aztecnr-easyprivateuint-deprecated-and-removed") The `EasyPrivateUint` type and `easy-private-state` crate have been deprecated and removed. Use `BalanceSet` from the `balance-set` crate instead. **Migration:** * Remove `easy_private_state` dependency from `Nargo.toml` * Add `balance_set = { path = "../../../../aztec-nr/balance-set" }` to `Nargo.toml` * Update storage: `EasyPrivateUint` → `Owned, Context>` * Update method calls: * `add(amount, owner)` → `at(owner).add(amount).deliver(owner, MessageDelivery.CONSTRAINED_ONCHAIN)` * `sub(amount, owner)` → `at(owner).sub(amount).deliver(owner, MessageDelivery.CONSTRAINED_ONCHAIN)` * `get_value(owner)` → `at(owner).balance_of()` (returns `u128` instead of `Field`) ### \[Aztec.nr] `balance_utils` removed from `value-note` (now `field-note`)[​](#aztecnr-balance_utils-removed-from-value-note-now-field-note "Direct link to aztecnr-balance_utils-removed-from-value-note-now-field-note") The `balance_utils` module has been removed from the `field-note` crate (formerly `value-note`). If you need similar functionality, implement it locally in your contract or use `BalanceSet` for u128 balances. ### \[Aztec.nr] `filter_notes_min_sum` removed from `value-note` (now `field-note`)[​](#aztecnr-filter_notes_min_sum-removed-from-value-note-now-field-note "Direct link to aztecnr-filter_notes_min_sum-removed-from-value-note-now-field-note") The `filter_notes_min_sum` function has been removed from the `field-note` crate (formerly in `value-note`). If you need this functionality, copy it to your contract locally. This function was only used in specific test contracts and doesn't belong in the general-purpose note library. ### \[Aztec.nr] `derive_ecdh_shared_secret_using_aztec_address` removed[​](#aztecnr-derive_ecdh_shared_secret_using_aztec_address-removed "Direct link to aztecnr-derive_ecdh_shared_secret_using_aztec_address-removed") This function made it annoying to deal with invalid addresses in circuits. If you were using it, replace it with `derive_ecdh_shared_secret` instead: ``` -let shared_secret = derive_ecdh_shared_secret_using_aztec_address(secret, address).unwrap(); +let shared_secret = derive_ecdh_shared_secret(secret, address.to_address_point().unwrap().inner); ``` ### \[Aztec.nr] Note owner is now enshrined[​](#aztecnr-note-owner-is-now-enshrined "Direct link to \[Aztec.nr] Note owner is now enshrined") It turns out that in all cases a note always has a logical owner. For this reason we have decided to enshrine the concept of a note owner and you should drop the field from your note: ``` #[derive(Deserialize, Eq, Packable, Serialize)] #[note] pub struct ValueNote { value: Field, - owner: AztecAddress, } ``` The owner being enshrined means that our API explicitly expects it on the input. The `NoteHash` trait got modified as follows: ``` pub trait NoteHash { fn compute_note_hash( self, + owner: AztecAddress, storage_slot: Field, randomness: Field, ) -> Field; fn compute_nullifier( self, context: &mut PrivateContext, + owner: AztecAddress, note_hash_for_nullification: Field, ) -> Field; unconstrained fn compute_nullifier_unconstrained( self, + owner: AztecAddress, note_hash_for_nullification: Field, ) -> Field; } ``` Our low-level note utilities now also accept owner as a parameter: ``` pub fn create_note( context: &mut PrivateContext, + owner: AztecAddress, storage_slot: Field, note: Note, ) -> NoteEmission where Note: NoteType + NoteHash + Packable, { ... } ``` Signature of some functions like `destroy_note_unsafe` is unchanged: ``` pub fn destroy_note_unsafe( context: &mut PrivateContext, hinted_note: HintedNote, note_hash_read: NoteHashRead, ) where Note: NoteHash, { ... } ``` because `HintedNote` now contains owner. `PrivateImmutable`, `PrivateMutable` and `PrivateSet` got modified to directly contain the owner instead of implicitly "containing it" by including it in the storage slot via a `Map`. These state variables now implement a newly introduced `OwnedStateVariable` trait (see docs of `OwnedStateVariable` for explanation of what it is). These changes make the state variables incompatible with `Map` and now instead these should be wrapped in new `Owned` state variable: ``` #[storage] struct Storage { - private_nfts: Map, Context>, + private_nfts: Owned, Context>, } ``` Note that even though the types of your state variables are changing from `Map` to `Owned`, usage remains unchanged: ``` let nft_notes = self.storage.private_nfts.at(from).pop_notes(NoteGetterOptions::new().select(NFTNote::properties().token_id, Comparator.EQ, token_id).set_limit(1)); ``` With this change the underlying notes will inherit the storage slot of the `Owned` state variable. This is unlike `Map` where the nested state variable got the storage slot computed as `hash([map_storage_slot, key])`. if you had `PrivateImmutable` or `PrivateMutable` defined out of a `Map`, e.g.: ``` #[storage] struct Storage { signing_public_key: PrivateImmutable, } ``` you were most likely dealing with some kind of admin flow where only the admin can modify the state variable. Now, unfortunately, there is a bit of a regression and you will need to wrap the state variable in `Owned` and call `at` on the state var: ``` + use aztec::state_vars::Owned; #[storage] struct Storage { - signing_public_key: PrivateImmutable, + signing_public_key: Owned, Context>, } #[external("private")] fn my_external_function() { - self.storage.signing_public_key.initialize(pub_key_note) + self.storage.signing_public_key.at(self.address).initialize(pub_key_note) .emit(self.address, MessageDelivery.CONSTRAINED_ONCHAIN); } ``` We are likely to come up with a concept of admin state variables in the future. None of the reference notes now contain the owner so if you manually construct `AddressNote`, `UintNote` or `ValueNote` you need to update the call to `new` method: ``` - let note = UintNote::new(156, owner); + let note = UintNote::new(156); ``` ### \[Aztec.nr] Note randomness is now handled internally[​](#aztecnr-note-randomness-is-now-handled-internally "Direct link to \[Aztec.nr] Note randomness is now handled internally") In order to prevent pre-image attacks, it is necessary to inject randomness to notes. Aztec.nr users were previously expected to add said randomness to their custom note types. From now on, Aztec.nr takes care of handling randomness as built-in note metadata, making it impossible to miss for library users. This change breaks backwards compatibility as we'll discuss below. #### Changes to Aztec.nr note types[​](#changes-to-aztecnr-note-types "Direct link to Changes to Aztec.nr note types") If you're using any of the following note types, please be aware that `randomness` no longer is an explicit attribute in them. * ValueNote * UintNote * NFTNote * AddressNote #### Migrating your custom note types: refer to UintNote as an example of how to migrate[​](#migrating-your-custom-note-types-refer-to-uintnote-as-an-example-of-how-to-migrate "Direct link to Migrating your custom note types: refer to UintNote as an example of how to migrate") We show the changes to `UintNote` below since it serves as a good example of the adjustments you will need to make to your own custom note types, including those that need to support partial notes. ##### Remove `randomness` from note struct[​](#remove-randomness-from-note-struct "Direct link to remove-randomness-from-note-struct") ``` pub struct UintNote { /// The owner of the note, i.e. the account whose nullifier secret key is required to compute the nullifier. owner: AztecAddress, - /// Random value, protects against note hash preimage attacks. - randomness: Field, /// The number stored in the note. value: u128, } impl UintNote { pub fn new(value: u128, owner: AztecAddress) -> Self { - let randomness = unsafe { random() }; - Self { value, owner, randomness } + Self { value, owner } } ``` ##### Add `randomness` to `compute_note_hash` implementation[​](#add-randomness-to-compute_note_hash-implementation "Direct link to add-randomness-to-compute_note_hash-implementation") The `NoteHash` trait now requires `compute_note_hash` to receive a `randomness` field. This impacts ``` pub trait NoteHash { /// ... - fn compute_note_hash(self, storage_slot: Field) -> Field; + fn compute_note_hash(self, storage_slot: Field, randomness: Field) -> Field; ``` Then in trait implementations: ``` impl NoteHash for UintNote { - fn compute_note_hash(self, storage_slot: Field) -> Field { + fn compute_note_hash(self, storage_slot: Field, randomness: Field) -> Field { /// ... - let private_content = - UintPartialNotePrivateContent { owner: self.owner, randomness: self.randomness }; - let partial_note = PartialUintNote { - commitment: private_content.compute_partial_commitment(storage_slot), - }; + let private_content = + UintPartialNotePrivateContent { owner: self.owner }; + let partial_note = PartialUintNote { + commitment: private_content.compute_partial_commitment(storage_slot, randomness), + }; ``` It's worth noting that this change also affects how partial notes are structured and handled. ``` pub fn partial( owner: AztecAddress, storage_slot: Field, randomness: Field, context: &mut PrivateContext, recipient: AztecAddress, completer: AztecAddress, ) -> PartialUintNote { - let commitment = UintPartialNotePrivateContent { owner, randomness } - .compute_partial_commitment(storage_slot); + let commitment = UintPartialNotePrivateContent { owner } + .compute_partial_commitment(storage_slot, randomness); let private_log_content = - UintPartialNotePrivateLogContent { owner, randomness, public_log_tag: commitment }; + UintPartialNotePrivateLogContent { owner, public_log_tag: commitment }; let encrypted_log = note::compute_partial_note_private_content_log( private_log_content, storage_slot, + randomness, recipient, ); /// ... } struct UintPartialNotePrivateContent { owner: AztecAddress, - randomness: Field, } impl UintPartialNotePrivateContent { - fn compute_partial_commitment(self, storage_slot: Field) -> Field { + fn compute_partial_commitment(self, storage_slot: Field, randomness: Field) -> Field { poseidon2_hash_with_separator( - self.pack().concat([storage_slot]), + self.pack().concat([storage_slot, randomness]), DOM_SEP__NOTE_HASH, ) } } struct UintPartialNotePrivateLogContent { public_log_tag: Field, owner: AztecAddress, - randomness: Field, } ``` ##### Note size[​](#note-size "Direct link to Note size") As a result of this change, the maximum packed length of the content of a note is 11 fields, down from 12. This is a direct consequence of moving the randomness field from the note content structure to the note's metadata. #### HintedNote now includes randomness field[​](#hintednote-now-includes-randomness-field "Direct link to HintedNote now includes randomness field") ``` pub struct HintedNote { pub note: Note, pub contract_address: AztecAddress, + pub randomness: Field, pub metadata: NoteMetadata, } ``` ### \[L1 Contracts] `Block` is now `Checkpoint`[​](#l1-contracts-block-is-now-checkpoint "Direct link to l1-contracts-block-is-now-checkpoint") A `checkpoint` is now the primary unit handled by the L1 contracts. A checkpoint may contain one or more L2 blocks. The protocol circuits already support producing multiple blocks per checkpoint. Updating the L1 contracts to operate on checkpoints allow L2 blockchain to advance faster. Below are the API and event renames reflecting this change: ``` - event L2BlockProposed + event CheckpointProposed ``` ``` - event BlockInvalidated + event CheckpointInvalidated ``` ``` - function getEpochForBlock(uint256 _blockNumber) external view returns (Epoch); + function getEpochForCheckpoint(uint256 _checkpointNumber) external view returns (Epoch); ``` ``` - function getProvenBlockNumber() external view returns (uint256); + function getProvenCheckpointNumber() external view returns (uint256); ``` ``` - function getPendingBlockNumber() external view returns (uint256); + function getPendingCheckpointNumber() external view returns (uint256); ``` ``` - function getBlock(uint256 _blockNumber) external view returns (BlockLog memory); + function getCheckpoint(uint256 _checkpointNumber) external view returns (CheckpointLog memory); ``` ``` - function getBlockReward() external view returns (uint256); + function getCheckpointReward() external view returns (uint256); ``` Additionally, any function or struct that previously referenced an L2 block number now uses a checkpoint number instead: ``` - function status(uint256 _blockNumber) external view returns ( + function status(uint256 _checkpointNumber) external view returns ( - uint256 provenBlockNumber, + uint256 provenCheckpointNumber, bytes32 provenArchive, - uint256 pendingBlockNumber, + uint256 pendingCheckpointNumber, bytes32 pendingArchive, bytes32 archiveOfMyBlock, Epoch provenEpochNumber ); ``` Note: current node softwares still produce exactly one L2 block per checkpoint, so for now checkpoint numbers and L2 block numbers remain equal. This may change once multi-block checkpoints are enabled. ### \[L1 Contracts] L2-to-L1 messages are now grouped by epoch.[​](#l1-contracts-l2-to-l1-messages-are-now-grouped-by-epoch "Direct link to \[L1 Contracts] L2-to-L1 messages are now grouped by epoch.") L2-to-L1 messages are aggregated and organized per epoch rather than per block, but callers should now ask the node to compute the membership witness directly from the emitting transaction hash. The node resolves the epoch and Outbox root internally. **Note**: This is only an API change. The protocol behavior remains the same - messages can still only be consumed once an epoch is proven as before. #### What changed[​](#what-changed "Direct link to What changed") Previously, callers fetched epoch messages and computed the witness client-side. Now, call `getL2ToL1MembershipWitness` on the node: ``` const witness = await node.getL2ToL1MembershipWitness( l2TxReceipt.txHash, l2ToL1Message, ); ``` ### \[Aztec.js] Wallet interface changes[​](#aztecjs-wallet-interface-changes "Direct link to \[Aztec.js] Wallet interface changes") #### `simulateTx` is now batchable[​](#simulatetx-is-now-batchable "Direct link to simulatetx-is-now-batchable") The `simulateTx` method on the `Wallet` interface is now batchable, meaning it can be called as part of a batch operation using `wallet.batch()`. This allows you to batch simulations together with other wallet operations like `registerContract`, `sendTx`, and `registerSender`. ``` - // Could not batch simulations - const simulationResult = await wallet.simulateTx(executionPayload, options); + // Can now batch simulations with other operations + const results = await wallet.batch([ + { name: 'registerContract', args: [instance, artifact] }, + { name: 'simulateTx', args: [executionPayload, options] }, + { name: 'sendTx', args: [anotherPayload, sendOptions] }, + ]); ``` #### `ExecutionPayload` moved to `@aztec/stdlib/tx`[​](#executionpayload-moved-to-aztecstdlibtx "Direct link to executionpayload-moved-to-aztecstdlibtx") The `ExecutionPayload` type has been moved from `@aztec/aztec.js` to `@aztec/stdlib/tx`. Update your imports accordingly. ``` - import { ExecutionPayload } from '@aztec/aztec.js'; + import { ExecutionPayload } from '@aztec/stdlib/tx'; + // Or import from the re-export in aztec.js/tx: + import { ExecutionPayload } from '@aztec/aztec.js/tx'; ``` #### `ExecutionPayload` now includes `feePayer` property[​](#executionpayload-now-includes-feepayer-property "Direct link to executionpayload-now-includes-feepayer-property") The `ExecutionPayload` class now includes an optional `feePayer` property that specifies which address is paying for the fee in the execution payload (if any) ``` const payload = new ExecutionPayload( calls, authWitnesses, capsules, extraHashedArgs, + feePayer // optional AztecAddress ); ``` This was previously provided as part of the `SendOptions` (and others) in the wallet interface, which could cause problems if a payload was assembled with a payment method and the parameter was later omitted. This means `SendOptions` now loses `embeddedPaymentMethodFeePayer` ``` -wallet.simulateTx(executionPayload, { from: address, embeddedFeePaymentMethodFeePayer: feePayer }); +wallet.simulateTx(executionPayload, { from: address }); ``` #### `simulateUtility` signature and return type changed[​](#simulateutility-signature-and-return-type-changed "Direct link to simulateutility-signature-and-return-type-changed") The `simulateUtility` method signature has changed to accept a `FunctionCall` object instead of separate `functionName`, `args`, and `to` parameters. Additionally, the return type has changed from `AbiDecoded` to `Fr[]`. ``` - const result: AbiDecoded = await wallet.simulateUtility(functionName, args, to, authWitnesses); + const result: UtilitySimulationResult = await wallet.simulateUtility(functionCall, authWitnesses?); + // result.result is now Fr[] instead of AbiDecoded ``` The new signature takes: * `functionCall`: A `FunctionCall` object containing `name`, `args`, `to`, `selector`, `type`, `isStatic`, `hideMsgSender`, and `returnTypes` * `authWitnesses` (optional): An array of `AuthWitness` objects The first argument is exactly the same as what goes into `ExecutionPayload.calls`. As such, the data is already encoded. The return value is now `UtilitySimulationResult` with `result: Fr[]` instead of returning an `AbiDecoded` value directly. You'll need to decode the `Fr[]` array yourself if you need typed results. #### `Contract.at()` is now synchronous and no longer calls `registerContract`[​](#contractat-is-now-synchronous-and-no-longer-calls-registercontract "Direct link to contractat-is-now-synchronous-and-no-longer-calls-registercontract") The `Contract.at()` method (and generated contract `.at()` methods) is now synchronous and no longer automatically registers the contract with the wallet. This reduces unnecessary artifact storage and RPC calls. ``` - const contract = await TokenContract.at(address, wallet); + const contract = TokenContract.at(address, wallet); ``` **Important:** You now need to explicitly call `registerContract` if you want the wallet to store the contract instance and artifact. This is only necessary when: * An app first registers a contract * An app tries to update a contract's artifact If you need to register the contract, do so explicitly: ``` // Get the instance from deployment const { contract, instance } = await TokenContract.deploy(wallet, ...args) .send({ from: address }) .wait(); // wallet already has it registered, since the deploy method does it by default // to avoid it, set skipContractRegistration: true in the send options. // Register it with another wallet await otherWallet.registerContract(instance, TokenContract.artifact); // Now you can use the contract const otherContract = TokenContract.at(instance.address, otherWallet); ``` Publicly deployed contract instances can be retrieved via `node.getContract(address)`. Otherwise and if deployment parameters are known, an instance can be computed via the `getContractInstanceFromInstantiationParams` from `@aztec/aztec.js/contracts` #### `registerContract` signature simplified[​](#registercontract-signature-simplified "Direct link to registercontract-signature-simplified") The `registerContract` method now takes a `ContractInstanceWithAddress` instead of a `Contract` object, and the `artifact` parameter is now optional. If the artifact is not provided, the wallet will attempt to look it up from its contract class storage. ``` - await wallet.registerContract(contract); + await wallet.registerContract(instance, artifact?); ``` The method now only accepts: * `instance`: A `ContractInstanceWithAddress` object * `artifact` (optional): A `ContractArtifact` object * `secretKey` (optional): A secret key for privacy keys registration #### Return value of `getNotes` no longer contains a recipient and it contains some other additional info[​](#return-value-of-getnotes-no-longer-contains-a-recipient-and-it-contains-some-other-additional-info "Direct link to return-value-of-getnotes-no-longer-contains-a-recipient-and-it-contains-some-other-additional-info") Return value of `getNotes` used to be defined as `Promise` and is now defined as `Promise`. `NoteDao` is mostly a super-set of `UniqueNote` but it doesn't contain a `recipient`. Having the recipient in the return value has been redundant as the same outcome can be achieved by populating the `scopes` array in `NoteFilter` with the `recipient` value. #### Changes to `getPrivateEvents`[​](#changes-to-getprivateevents "Direct link to changes-to-getprivateevents") The signature of `getPrivateEvents` has changed for two reasons: 1. To align it with how other query methods that include filtering by block range work (for example, `AztecNode#getPublicLogs`) 2. To enrich the returned private events with metadata. ``` getPrivateEvents( - contractAddress: AztecAddress, - eventMetadata: EventMetadataDefinition, - from: number, - numBlocks: number, - recipients: AztecAddress[], - ): Promise; + eventFilter: PrivateEventFilter, + ): Promise[]>; ``` `PrivateEvent` bundles together an ABI decoded event of type `T`, with `metadata` of type `InTx`: ``` export type InBlock = { l2BlockNumber: BlockNumber; l2BlockHash: L2BlockHash; }; export type InTx = InBlock & { txHash: TxHash; }; export type PrivateEvent = { event: T; metadata: InTx; }; ``` You will need to update any calls to `Wallet#getPrivateEvents` accordingly. See below for before/after comparison which conserves semantics. Pay special attention to the fact that the old method expects a `numBlocks` parameter that instructs it to return `numBlocks` blocks after `fromBlock`, whereas the new version expects an (exclusive) `toBlock` block number. Also note we're replacing *recipient* terminology with *scope*. While underlying data types are equivalent (they are Aztec addresses), they have different semantics. Messages have a recipient who will be able to receive and process them. As a result of processing messages for a given recipient address, PXE might discover events. Those events are then said to be *in scope* for that address. ``` - const events = await context.client.getPrivateEvents(contractAddress, eventMetadata, 42, 10, [recipient]); - doSomethingWithAnEvent(events[0]); + const events = await context.client.getPrivateEvents(eventMetadata, { + contractAddress, + fromBlock: BlockNumber(42), + toBlock: BlockNumber(42 + 10), + scopes: [scope], + }); + doSomethingWithAnEvent(events[0].event); ``` Please refer to the wallet interface js-docs for further details. ### \[CLI] Command refactor[​](#cli-command-refactor "Direct link to \[CLI] Command refactor") The sandbox command has been renamed and remapped to "local network". We believe this conveys better what is actually being spun up when running it. **REMOVED/RENAMED**: * `aztec start --sandbox`: now `aztec start --local-network` ### \[Aztec.nr] - Contract API redesign[​](#aztecnr---contract-api-redesign "Direct link to \[Aztec.nr] - Contract API redesign") In this release we decided to largely redesign our contract API. Most of the changes here are not a breaking change (only renaming of original `#[internal]` to `#[only_self]` and `storage` now being available on the newly introduced `self` struct are a breaking change). #### 1. Renaming of original #\[internal] as #\[only\_self][​](#1-renaming-of-original-internal-as-only_self "Direct link to 1. Renaming of original #\[internal] as #\[only_self]") We want for internal to mean the same as in Solidity where internal function can be called only from the same contract and is also inlined (EVM JUMP opcode and not EVM CALL). The original implementation of our `#[internal]` macro also results in the function being callable only from the same contract but it results in a different call (hence it doesn't map to EVM JUMP). This is very confusing for people that know Solidity hence we are doing the rename. A true `#[internal]` will be introduced in the future. To migrate your contracts simply rename all the occurrences of `#[internal]` with `#[only_self]` and update the imports: ``` - use aztec::macros::functions::internal; + use aztec::macros::functions::only_self; ``` ``` #[external("public")] - #[internal] + #[only_self] fn _deduct_public_balance(owner: AztecAddress, amount: u64) { ... } ``` #### 2. Introducing of new #\[internal][​](#2-introducing-of-new-internal "Direct link to 2. Introducing of new #\[internal]") Same as in Solidity internal functions are functions that are callable from inside the contract. Unlike #\[only\_self] functions, internal functions are inlined (e.g. akin to EVM's JUMP and not EVM's CALL). Internal function can be called using the following API which leverages the new `self` struct (see change 3 below for details): ``` self.internal.my_internal_function(...) ``` Private internal functions can only be called from other private external or internal functions. Public internal functions can only be called from other public external or internal functions. #### 3. Introducing `self` in contracts and a new call interface[​](#3-introducing-self-in-contracts-and-a-new-call-interface "Direct link to 3-introducing-self-in-contracts-and-a-new-call-interface") Aztec contracts now automatically inject a `self` parameter into every contract function, providing a unified interface for accessing the contract's address, storage, calling of function and an execution context. ##### What is `self`?[​](#what-is-self "Direct link to what-is-self") `self` is an instance of `ContractSelf` that provides: * `self.address` - The contract's own address * `self.storage` - Access to your contract's storage * `self.context` - The execution context (private, public, or utility) * `self.msg_sender()` - Get the address of the caller * `self.emit(...)` - Emit events * `self.call(...)` - Call an external function * `self.view(...)` - Call an external function statically * `self.enqueue(...)` - Enqueue a call to an external function * `self.enqueue_view(...)` - Enqueue a call to an external function * `self.enqueue_incognito(...)` - Enqueue a call to an external function but hides the `msg_sender` * `self.enqueue_view_incognito(...)` - Enqueue a static call to an external function but hides the `msg_sender` * `self.set_as_teardown(...)` - Enqueue a call to an external public function and sets the call as teardown * `self.set_as_teardown_incognito(...)` - Enqueue a call to an external public function and sets the call as teardown and hides the `msg_sender` * `self.internal.my_internal_fn(...)` - Call an internal function `self` also provides you with convenience API to call and enqueue calls to external functions from within the same contract (this is just a convenience API as `self.call(MyContract::at(self.address).my_external_fn(...))` would also work): * `self.call_self.my_external_fn(...)` - Call external function from within the same contract * `self.enqueue_self.my_public_external_fn(...)` * `self.call_self_static.my_static_external_fn(...)` * `self.enqueue_self_static.my_static_external_public_fn(...)` ##### How it works[​](#how-it-works "Direct link to How it works") The `#[external(...)]` macro automatically injects `self` into your function. When you write: ``` #[external("private")] fn transfer(amount: u128, recipient: AztecAddress) { let sender = self.msg_sender().unwrap(); self.storage.balances.at(sender).sub(amount); self.storage.balances.at(recipient).add(amount); } ``` The macro transforms it to initialize `self` with the context and storage before your code executes. ##### Migration guide[​](#migration-guide "Direct link to Migration guide") **Before:** Access context and storage as separate parameters ``` #[external("private")] fn old_transfer(amount: u128, recipient: AztecAddress) { let storage = Storage::init(context); let sender = context.msg_sender().unwrap(); storage.balances.at(sender).sub(amount); } ``` **After:** Use `self` to access everything ``` #[external("private")] fn new_transfer(amount: u128, recipient: AztecAddress) { let sender = self.msg_sender().unwrap(); self.storage.balances.at(sender).sub(amount); } ``` ##### Key changes[​](#key-changes "Direct link to Key changes") 1. **Storage and context access:** Storage and context are no longer injected into the function as standalone variables and instead you need to access them via `self`: ``` - let balance = storage.balances.at(owner).read(); + let balance = self.storage.balances.at(owner).read(); ``` ``` - context.push_nullifier(nullifier); + self.context.push_nullifier(nullifier); ``` Note that `context` is expected to be use only when needing to access a low-level API (like directly emitting a nullifier). 2. **Getting caller address:** Use `self.msg_sender()` instead of `context.msg_sender()` ``` - let caller = context.msg_sender().unwrap(); + let caller = self.msg_sender().unwrap(); ``` 3. **Getting contract address:** Use `self.address` instead of `context.this_address()` ``` - let this_contract = context.this_address(); + let this_contract = self.address; ``` 4. **Emitting events:** In private functions: ``` - emit_event_in_private(event, context, recipient, delivery_mode); + self.emit(event, recipient, delivery_mode); ``` In public functions: ``` - emit_event_in_public(event, context); + self.emit(event); ``` 5. **Calling functions:** In private functions: ``` - Token::at(stable_coin).mint_to_public(to, amount).call(&mut context); + self.call(Token::at(stable_coin).mint_to_public(to, amount)); ``` ##### Example: Full contract migration[​](#example-full-contract-migration "Direct link to Example: Full contract migration") **Before:** ``` #[external("private")] fn withdraw(amount: u128, recipient: AztecAddress) { let storage = Storage::init(context); let sender = context.msg_sender().unwrap(); let token = storage.donation_token.get_note().get_address(); // ... withdrawal logic emit_event_in_private(Withdraw { withdrawer, amount }, context, withdrawer, MessageDelivery.UNCONSTRAINED_ONCHAIN); } ``` **After:** ``` #[external("private")] fn withdraw(amount: u128, recipient: AztecAddress) { let sender = self.msg_sender().unwrap(); let token = self.storage.donation_token.get_note().get_address(); // ... withdrawal logic self.emit(Withdraw { withdrawer, amount }, withdrawer, MessageDelivery.UNCONSTRAINED_ONCHAIN); } ``` #### No-longer allowing calling of non-view function statically via the old higher-level API[​](#no-longer-allowing-calling-of-non-view-function-statically-via-the-old-higher-level-api "Direct link to No-longer allowing calling of non-view function statically via the old higher-level API") We used to allow calling of non-view function statically as follows: ``` MyContract::at(address).my_non_view_function(...).view(context); MyContract::at(address).my_non_view_function(...).enqueue_view(context); ``` This is no-longer allowed and if you will want to call a function statically you will need to mark the function with `#[view]`. ### Phase checks[​](#phase-checks "Direct link to Phase checks") Now private external functions check by default that no phase change from non revertible to revertible happens during the execution of the function or any of its nested calls. If you're developing a function that handles phase change (you call `context.end_setup()` or call a function that you expect will change phase) you need to opt out of the phase check using the `#[nophasecheck]` macro. Also, now it's possible to know if you're in the revertible phase of the transaction at any point using `self.context.in_revertible_phase()`. ### \[`aztec` command] Moving functionality of `aztec-nargo` to `aztec` command[​](#aztec-command-moving-functionality-of-aztec-nargo-to-aztec-command "Direct link to aztec-command-moving-functionality-of-aztec-nargo-to-aztec-command") `aztec-nargo` has been deprecated and all workflows should now migrate to the `aztec` command that fully replaces `aztec-nargo`: * **For contract initialization:** ``` aztec init ``` (Behaves like `nargo init`, but defaults to a contract project.) * **For testing:** ``` aztec test ``` (Starts the Aztec TXE and runs your tests.) * **For compiling contracts:** ``` aztec compile ``` (Transpiles your contracts and generates verification keys.) ## 3.0.0-devnet.4[​](#300-devnet4 "Direct link to 3.0.0-devnet.4") ## \[aztec.js] Removal of barrel export[​](#aztecjs-removal-of-barrel-export "Direct link to \[aztec.js] Removal of barrel export") `aztec.js` is now divided into granular exports, which improves loading performance in node.js and also makes the job of web bundlers easier: ``` -import { AztecAddress, Fr, getContractInstanceFromInstantiationParams, type Wallet } from '@aztec/aztec.js'; +import { AztecAddress } from '@aztec/aztec.js/addresses'; +import { getContractInstanceFromInstantiationParams } from '@aztec/aztec.js/contracts'; +import { Fr } from '@aztec/aztec.js/fields'; +import type { Wallet } from '@aztec/aztec.js/wallet'; ``` Additionally, some general utilities reexported from `foundation` have been removed: ``` -export { toBigIntBE } from '@aztec/foundation/bigint-buffer'; -export { sha256, Grumpkin, Schnorr } from '@aztec/foundation/crypto'; -export { makeFetch } from '@aztec/foundation/json-rpc/client'; -export { retry, retryUntil } from '@aztec/foundation/retry'; -export { to2Fields, toBigInt } from '@aztec/foundation/serialize'; -export { sleep } from '@aztec/foundation/sleep'; -export { elapsed } from '@aztec/foundation/timer'; -export { type FieldsOf } from '@aztec/foundation/types'; -export { fileURLToPath } from '@aztec/foundation/url'; ``` ### `getSenders` renamed to `getAddressBook` in wallet interface[​](#getsenders-renamed-to-getaddressbook-in-wallet-interface "Direct link to getsenders-renamed-to-getaddressbook-in-wallet-interface") An app could request "contacts" from the wallet, which don't necessarily have to be senders in the wallet's PXE. This method has been renamed to reflect that fact: ``` -wallet.getSenders(); +wallet.getAddressBook(); ``` ### Removal of `proveTx` from `Wallet` interface[​](#removal-of-provetx-from-wallet-interface "Direct link to removal-of-provetx-from-wallet-interface") Exposing this method on the interface opened the door for certain types of attacks, were an app could route proven transactions through malicious nodes (that stored them for later decryption, or collected user IPs for example). It also made transactions difficult to track for the wallet, since they could be sent without their knowledge at any time. This change also affects `ContractFunctionInteraction` and `DeployMethod`, which no longer expose a `prove()` method. ### `msg_sender` is now an `Option` type.[​](#msg_sender-is-now-an-optionaztecaddress-type "Direct link to msg_sender-is-now-an-optionaztecaddress-type") Because Aztec has native account abstraction, the very first function call of a tx has no `msg_sender`. (Recall, the first function call of an Aztec transaction is always a *private* function call). Previously (before this change) we'd been silently setting this first `msg_sender` to be `AztecAddress::from_field(-1);`, and enforcing this value in the protocol's kernel circuits. Now we're passing explicitness to smart contract developers by wrapping `msg_sender` in an `Option` type. We'll explain the syntax shortly. We've also added a new protocol feature. Previously (before this change) whenever a public function call was enqueued by a private function (a so-called private->public call), the called public function (and hence the whole world) would be able to see `msg_sender`. For some use cases, visibility of `msg_sender` is important, to ensure the caller executed certain checks in private-land. For `#[only_self]` public functions, visibility of `msg_sender` is unavoidable (the caller of an `#[only_self]` function must be the same contract address by definition). But for *some* use cases, a visible `msg_sender` is an unnecessary privacy leakage. We therefore have added a feature where `msg_sender` can be optionally set to `Option::none()` for enqueued public function calls (aka private->public calls). We've been colloquially referring to this as "setting msg\_sender to null". #### Aztec.nr diffs[​](#aztecnr-diffs "Direct link to Aztec.nr diffs") > Note: we'll be doing another pass at this aztec.nr syntax in the near future. Given the above, the syntax for accessing `msg_sender` in Aztec.nr is slightly different: For most public and private functions, to adjust to this change, you can make this change to your code: ``` - let sender: AztecAddress = context.msg_sender(); + let sender: AztecAddress = context.msg_sender().unwrap(); ``` Recall that `Option::unwrap()` will throw if the Option is "none". Indeed, most smart contract functions will require access to a proper contract address (instead of a "null" value), in order to do bookkeeping (allocation of state variables against user addresses), and so in such cases throwing is sensible behaviour. If you want to output a useful error message when unwrapping fails, you can use `Option::expect`: ``` - let sender: AztecAddress = context.msg_sender(); + let sender: AztecAddress = context.msg_sender().expect(f"Sender must not be none!"); ``` For a minority of functions, a "null" msg\_sender will be acceptable: * A private entrypoint function. * A public function which doesn't seek to do bookkeeping against `msg_sender`. Some apps might even want to *assert* that the `msg_sender` is "null" to force their users into strong privacy practices: ``` let sender: Option = context.msg_sender(); assert(sender.is_none()); ``` ##### Enqueueing public function calls[​](#enqueueing-public-function-calls "Direct link to Enqueueing public function calls") ###### Auto-generated contract interfaces[​](#auto-generated-contract-interfaces "Direct link to Auto-generated contract interfaces") When you use the `#[aztec]` macro, it will generate a noir contract interface for your contract, behind the scenes. This provides pretty syntax when you come to call functions of that contract. E.g.: ``` Token::at(context.this_address())._increase_public_balance(to, amount).enqueue(&mut context); ``` In keeping with this new feature of being able to enqueue public function calls with a hidden `msg_sender`, there are some new methods that can be chained instead of `.enqueue(...)`: * `enqueue_incognito` -- akin to `enqueue`, but `msg_sender` is set "null". * `enqueue_view_incognito` -- akin to `enqueue_view`, but `msg_sender` is "null". * `set_as_teardown_incognito` -- akin to `set_as_teardown`, but `msg_sender` is "null". > The name "incognito" has been chosen to imply "msg\_sender will not be visible to observers". These new functions enable the *calling* contract to specify that it wants its address to not be visible to the called public function. This is worth re-iterating: it is the *caller's* choice. A smart contract developer who uses these functions must be sure that the target public function will accept a "null" `msg_sender`. It would not be good (for example) if the called public function did `context.msg_sender().unwrap()`, because then a public function that is called via `enqueue_incognito` would *always fail*! Hopefully smart contract developers will write sufficient tests to catch such problems during development! ###### Making lower-level public function calls from the private context[​](#making-lower-level-public-function-calls-from-the-private-context "Direct link to Making lower-level public function calls from the private context") This is discouraged vs using the auto-generated contract interfaces described directly above. If you do use any of these low-level methods of the `PrivateContext` in your contract: * `call_public_function` * `static_call_public_function` * `call_public_function_no_args` * `static_call_public_function_no_args` * `call_public_function_with_calldata_hash` * `set_public_teardown_function` * `set_public_teardown_function_with_calldata_hash` ... there is a new `hide_msg_sender: bool` parameter that you will need to specify. #### Aztec.js diffs[​](#aztecjs-diffs "Direct link to Aztec.js diffs") > Note: we'll be doing another pass at this aztec.js syntax in the near future. When lining up a new tx, the `FunctionCall` struct has been extended to include a `hide_msg_sender: bool` field. * `is_public & hide_msg_sender` -- will make a public call with `msg_sender` set to "null". * `is_public & !hide_msg_sender` -- will make a public call with a visible `msg_sender`, as was the case before this new feature. * `!is_public & hide_msg_sender` -- Incompatible flags. * `!is_public & !hide_msg_sender` -- will make a private call with a visible `msg_sender` (noting that since it's a private function call, the `msg_sender` will only be visible to the called private function, but not to the rest of the world). ## \[cli-wallet][​](#cli-wallet "Direct link to \[cli-wallet]") The `deploy-account` command now requires the address (or alias) of the account to deploy as an argument, not a parameter ``` +aztec-wallet deploy-account main -aztec-wallet deploy-account -f main ``` This release includes a major architectural change to the system. The PXE JSON RPC Server has been removed, and PXE is now available only as a library to be used by wallets. ## \[Aztec node][​](#aztec-node "Direct link to \[Aztec node]") Network config. The node now pulls default configuration from the public repository [AztecProtocol/networks](https://github.com/AztecProtocol/networks) after it applies the configuration it takes from the running environment and the configuration values baked into the source code. See associated [Design document](https://github.com/AztecProtocol/engineering-designs/blob/15415a62a7c8e901acb8e523625e91fc6f71dce4/docs/network-config/dd.md) ## \[Aztec.js][​](#aztecjs "Direct link to \[Aztec.js]") ### Removing Aztec cheatcodes[​](#removing-aztec-cheatcodes "Direct link to Removing Aztec cheatcodes") The Aztec cheatcodes class has been removed. Its functionality can be replaced by using the `getNotes(...)` function directly available on our `TestWallet`, along with the relevant functions available on the Aztec Node interface (note that the cheatcodes were generally just a thin wrapper around the Aztec Node interface). ### CLI Wallet commands dropped from `aztec` command[​](#cli-wallet-commands-dropped-from-aztec-command "Direct link to cli-wallet-commands-dropped-from-aztec-command") The following commands used to be exposed by both the `aztec` and the `aztec-wallet` commands: * import-test-accounts * create-account * deploy-account * deploy * send * simulate * profile * bridge-fee-juice * create-authwit * authorize-action * get-tx * cancel-tx * register-sender * register-contract These were dropped from `aztec` and now are exposed only by the `cli-wallet` command exposed by the `@aztec/cli-wallet` package. ### PXE commands dropped from `aztec` command[​](#pxe-commands-dropped-from-aztec-command "Direct link to pxe-commands-dropped-from-aztec-command") The following commands were dropped from the `aztec` command: * `add-contract`: use can be replaced with `register-contract` on our `cli-wallet` * `get-contract-data`: debug-only and not considered important enough to need a replacement * `get-accounts`: debug-only and can be replaced by loading aliases from `cli-wallet` * `get-account`: debug-only and can be replaced by loading aliases from `cli-wallet` * `get-pxe-info`: debug-only and not considered important enough to need a replacement ## \[Aztec.nr][​](#aztecnr "Direct link to \[Aztec.nr]") ### Replacing #\[private], #\[public], #\[utility] with #\[external(...)] macro[​](#replacing-private-public-utility-with-external-macro "Direct link to Replacing #\[private], #\[public], #\[utility] with #\[external(...)] macro") The original naming was not great in that it did not sufficiently communicate what the given macro did. We decided to rename `#[private]` as `#[external("private")]`, `#[public]` as `#[external("public")]`, and `#[utility]` as `#[external("utility")]` to better communicate that these functions are externally callable and to specify their execution context. In this sense, `external` now means the exact same thing as in Solidity, i.e. a function that can be called from other contracts, and that can only be invoked via a contract call (i.e. the `CALL` opcode in the EVM, and a kernel call/AVM `CALL` opcode in Aztec). You have to do the following changes in your contracts: Update import: ``` - use aztec::macros::functions::private; - use aztec::macros::functions::public; - use aztec::macros::functions::utility; + use aztec::macros::functions::external; ``` Update attributes of your functions: ``` - #[private] + #[external("private")] fn my_private_func() { ``` ``` - #[public] + #[external("public")] fn my_public_func() { ``` ``` - #[utility] + #[external("utility")] fn my_utility_func() { ``` ### Dropping remote mutable references to public context[​](#dropping-remote-mutable-references-to-public-context "Direct link to Dropping remote mutable references to public context") `PrivateContext` generally needs to be passed as a mutable reference to functions because it does actually hold state we're mutating. This is not the case for `PublicContext`, or `UtilityContext` - these are just marker objects that indicate the current execution mode and make available the correct subset of the API. For this reason we have dropped the mutable reference from the API. If you've passed the context as an argument to custom functions you will need to do the following migration (example from our token contract): ``` #[contract_library_method] fn _finalize_transfer_to_private( from_and_completer: AztecAddress, amount: u128, partial_note: PartialUintNote, - context: &mut PublicContext, - storage: Storage<&mut PublicContext>, + context: PublicContext, + storage: Storage, ) { ... } ``` ### Authwit Test Helper now takes `env`[​](#authwit-test-helper-now-takes-env "Direct link to authwit-test-helper-now-takes-env") The `add_private_authwit_from_call_interface` test helper available in `test::helpers::authwit` now takes a `TestEnvironment` parameter, mirroring `add_public_authwit_from_call_interface`. This adds some unfortunate verbosity, but there are bigger plans to improve authwit usage in Noir tests in the near future. ``` add_private_authwit_from_call_interface( + env, on_behalf_of, caller, call_interface, ); ``` ### Historical block renamed as anchor block[​](#historical-block-renamed-as-anchor-block "Direct link to Historical block renamed as anchor block") A historical block term has been used as a term that denotes the block against which a private part of a tx has been executed. This name is ambiguous and for this reason we've introduced "anchor block". This naming change resulted in quite a few changes and if you've access private context's or utility context's block header you will need to update your code: ``` - let header = context.get_block_header(); + let header = context.get_anchor_block_header(); ``` ### Removed ValueNote utils[​](#removed-valuenote-utils "Direct link to Removed ValueNote utils") The `value_note::utils` module has been removed because it was incorrect to have those in the value note package. For the increment function you can easily just insert the note: ``` - use value_note::utils; - utils::increment(storage.notes.at(owner), value, owner, sender); + let note = ValueNote::new(value, owner); + storage.notes.at(owner).insert(note).emit(&mut context, owner, MessageDelivery.CONSTRAINED_ONCHAIN); ``` ### PrivateMutable: replace / initialize\_or\_replace behaviour change[​](#privatemutable-replace--initialize_or_replace-behaviour-change "Direct link to PrivateMutable: replace / initialize_or_replace behaviour change") **Motivation:** Updating a note used to require reading it first (via `get_note`, which nullifies and recreates it) and then calling `replace` — effectively proving a note twice. Now, `replace` accepts a callback that transforms the current note directly, and `initialize_or_replace` simply uses this updated `replace` internally. This reduces circuit cost while maintaining exactly one current note. **Key points:** 1. `replace(self, new_note)` (old) → `replace(self, f)` (new), where `f` takes the current note and returns a transformed note. 2. `initialize_or_replace(self, note)` (old) → `initialize_or_replace(self, f)` (new), where `f` takes an `Option` with the current note, or `none` if uninitialized. 3. Previous note is automatically nullified before the new note is inserted. 4. `NoteEmission` still requires `.emit()` or `.discard()`. **Example Migration:** ``` - let current_note = storage.my_var.get_note(); - let new_note = f(current_note); - storage.my_var.replace(new_note); + storage.my_var.replace(|current_note| f(current_note)); ``` ``` - storage.my_var.initialize_or_replace(new_note); + storage.my_var.initialize_or_replace(|_| new_note); ``` This makes it easy and efficient to handle both initialization and current value mutation via `initialize_or_replace`, e.g. if implementing a note that simply counts how many times it has been read: ``` + storage.my_var.initialize_or_replace(|opt_current: Option| opt_current.unwrap_or(0 /* initial value */) + 1); ``` * The callback can be a closure (inline) or a named function. * Any previous assumptions that replace simply inserts a new\_note directly must be updated. ### Unified oracles into single get\_utility\_context oracle[​](#unified-oracles-into-single-get_utility_context-oracle "Direct link to Unified oracles into single get_utility_context oracle") The following oracles: 1. get\_contract\_address, 2. get\_block\_number, 3. get\_timestamp, 4. get\_chain\_id, 5. get\_version were replaced with a single `get_utility_context` oracle whose return value contains all the values returned from the removed oracles. If you have used one of these removed oracles before, update the import, e.g.: ``` - aztec::oracle::execution::get_chain_id; + aztec::oracle::execution::get_utility_context ``` and get the value out of the returned utility context: ``` - let chain_id = get_chain_id(); + let chain_id = get_utility_context().chain_id(); ``` ### Note emission API changes[​](#note-emission-api-changes "Direct link to Note emission API changes") The note emission API has been significantly reworked to provide clearer semantics around message delivery guarantees. The key changes are: 1. `encode_and_encrypt_note` has been removed in favor of calling `emit` directly with `MessageDelivery.CONSTRAINED_ONCHAIN` 2. `encode_and_encrypt_note_unconstrained` has been removed in favor of calling `emit` directly with `MessageDelivery.UNCONSTRAINED_ONCHAIN` 3. `encode_and_encrypt_note_and_emit_as_offchain_message` has been removed in favor of using `emit` with `MessageDelivery.UNCONSTRAINED_OFFCHAIN` 4. Note emission now takes a `delivery_mode` parameter with the following values: * `CONSTRAINED_ONCHAIN`: For onchain delivery with cryptographic guarantees that recipients can discover and decrypt messages. Uses constrained encryption but is slower to prove. Best for critical messages that contracts need to verify. * `UNCONSTRAINED_ONCHAIN`: For onchain delivery without encryption constraints. Faster proving but trusts the sender. Good when the sender is incentivized to perform encryption correctly (e.g. they are buying something and will only get it if the recipient sees the note). No guarantees that recipients will be able to find or decrypt messages. * `UNCONSTRAINED_OFFCHAIN`: For offchain delivery (e.g. cloud storage) without constraints. Lowest cost since no onchain storage needed. Requires custom infrastructure for delivery. No guarantees that messages will be delivered or that recipients will ever find them. 5. The `context` object no longer needs to be passed to these functions Example migration: First you need to update imports in your contract: ``` - aztec::messages::logs::note::encode_and_encrypt_note; - aztec::messages::logs::note::encode_and_encrypt_note_unconstrained; - aztec::messages::logs::note::encode_and_encrypt_note_and_emit_as_offchain_message; + aztec::messages::message_delivery::MessageDelivery; ``` Then update the emissions: ``` - storage.balances.at(from).sub(from, amount).emit(encode_and_encrypt_note(&mut context, from)); + storage.balances.at(from).sub(from, amount).emit(&mut context, from, MessageDelivery.CONSTRAINED_ONCHAIN); ``` ``` - storage.balances.at(from).add(from, change).emit(encode_and_encrypt_note_unconstrained(&mut context, from)); + storage.balances.at(from).add(from, change).emit(&mut context, from, MessageDelivery.UNCONSTRAINED_ONCHAIN); ``` ``` - storage.balances.at(owner).insert(note).emit(encode_and_encrypt_note_and_emit_as_offchain_message(&mut context, context.msg_sender()); + storage.balances.at(owner).insert(note).emit(&mut context, context.msg_sender(), MessageDelivery.UNCONSTRAINED_OFFCHAIN); ``` ## 2.0.2[​](#202 "Direct link to 2.0.2") ## \[Public functions][​](#public-functions "Direct link to \[Public functions]") The L2 gas cost of the different AVM opcodes have been updated to reflect more realistic proving costs. Developers should review the L2 gas costs of executing public functions and reevaluate any hardcoded L2 gas limits. ## \[Aztec Tools][​](#aztec-tools "Direct link to \[Aztec Tools]") ### Contract compilation now requires two steps[​](#contract-compilation-now-requires-two-steps "Direct link to Contract compilation now requires two steps") The `aztec-nargo` command is now a direct pass-through to vanilla nargo, without any special compilation flags or postprocessing. Contract compilation for Aztec now requires two explicit steps: 1. Compile your contracts with `aztec-nargo compile` 2. Run postprocessing with the new `aztec-postprocess-contract` command The postprocessing step includes: * Transpiling functions for the Aztec VM * Generating verification keys for private functions * Caching verification keys for faster subsequent compilations Update your build scripts accordingly: ``` - aztec-nargo compile + aztec-nargo compile + aztec-postprocess-contract ``` If you're using the `aztec-up` installer, the `aztec-postprocess-contract` command will be automatically installed alongside `aztec-nargo`. ## \[Aztec.js] Mandatory `from`[​](#aztecjs-mandatory-from "Direct link to aztecjs-mandatory-from") As we prepare for a bigger `Wallet` interface refactor and the upcoming `WalletSDK`, a new parameter has been added to contract interactions, which now should indicate *explicitly* the address of the entrypoint (usually the account contract) that will be used to authenticate the request. This will be checked in runtime against the current `this.wallet.getAddress()` value, to ensure consistent behavior while the rest of the API is reworked. ``` - await contract.methods.my_func(arg).send().wait(); + await contract.methods.my_func(arg).send({ from: account1Address }).wait(); ``` ## \[Aztec.nr][​](#aztecnr-1 "Direct link to \[Aztec.nr]") ### `emit_event_in_public_log` function renamed as `emit_event_in_public`[​](#emit_event_in_public_log-function-renamed-as-emit_event_in_public "Direct link to emit_event_in_public_log-function-renamed-as-emit_event_in_public") This change was done to make the naming consistent with the private counterpart (`emit_event_in_private`). ### Private event emission API changes[​](#private-event-emission-api-changes "Direct link to Private event emission API changes") The private event emission API has been significantly reworked to provide clearer semantics around message delivery guarantees. The key changes are: 1. `emit_event_in_private_log` has been renamed to `emit_event_in_private` and now takes a `delivery_mode` parameter instead of `constraints` 2. `emit_event_as_offchain_message` has been removed in favor of using `emit_event_in_private` with `MessageDelivery.UNCONSTRAINED_OFFCHAIN` 3. `PrivateLogContent` enum has been replaced with `MessageDelivery` enum with the following values: * `CONSTRAINED_ONCHAIN`: For onchain delivery with cryptographic guarantees that recipients can discover and decrypt messages. Uses constrained encryption but is slower to prove. Best for critical messages that contracts need to verify. * `UNCONSTRAINED_ONCHAIN`: For onchain delivery without encryption constraints. Faster proving but trusts the sender. Good when the sender is incentivized to perform encryption correctly (e.g. they are buying something and will only get it if the recipient sees the note). No guarantees that recipients will be able to find or decrypt messages. * `UNCONSTRAINED_OFFCHAIN`: For offchain delivery (e.g. cloud storage) without constraints. Lowest cost since no onchain storage needed. Requires custom infrastructure for delivery. No guarantees that messages will be delivered or that recipients will ever find them. ### Contract functions can no longer be `pub` or `pub(crate)`[​](#contract-functions-can-no-longer-be-pub-or-pubcrate "Direct link to contract-functions-can-no-longer-be-pub-or-pubcrate") With the latest changes to `TestEnvironment`, making contract functions have public visibility is no longer required given the new `call_public` and `simulate_utility` functions. To avoid accidental direct invocation, and to reduce confusion with the autogenerated interfaces, we're forbidding them being public. ``` - pub(crate) fn balance_of_private(account: AztecAddress) -> 128 { + fn balance_of_private(account: AztecAddress) -> 128 { ``` ### Notes require you to manually implement or derive Packable[​](#notes-require-you-to-manually-implement-or-derive-packable "Direct link to Notes require you to manually implement or derive Packable") We have decided to drop auto-derivation of `Packable` from the `#[note]` macro because we want to make the macros less magical. With this change you will be forced to either apply `#[derive(Packable)` on your notes: ``` +use aztec::protocol::traits::Packable; +#[derive(Packable)] #[note] pub struct UintNote { owner: AztecAddress, randomness: Field, value: u128, } ``` or to implement it manually yourself: ``` impl Packable for UintNote { let N: u32 = 3; fn pack(self) -> [Field; Self::N] { [self.owner.to_field(), randomness, value as Field] } fn unpack(fields: [Field; Self::N]) -> Self { let owner = AztecAddress::from_field(fields[0]); let randomness = fields[1]; let value = fields[2] as u128; UintNote { owner, randomness, value } } } ``` ### Tagging sender now managed via oracle functions[​](#tagging-sender-now-managed-via-oracle-functions "Direct link to Tagging sender now managed via oracle functions") Now, instead of manually needing to pass a tagging sender as an argument to log emission functions (e.g. `encode_and_encrypt_note`, `encode_and_encrypt_note_unconstrained`, `emit_event_in_private_log`, ...) we automatically load the sender via the `get_sender_for_tags()` oracle. This value is expected to be populated by account contracts that should call `set_sender_for_tags()` in their entry point functions. The changes you need to do in your contracts are quite straightforward. You simply need to drop the `sender` arg from the callsites of the log emission functions. E.g. note emission: ``` storage.balances.at(from).sub(from, amount).emit(encode_and_encrypt_note( &mut context, from, - tagging_sender, )); ``` E.g. private event emission: ``` emit_event_in_private_log( Transfer { from, to, amount }, &mut context, - tagging_sender, to, PrivateLogContent.NO_CONSTRAINTS, ); ``` This change affected arguments `prepare_private_balance_increase` and `mint_to_private` functions on the `Token` contract. Drop the `from` argument when calling these. Example in TypeScript test: ``` - await token.methods.mint_to_private(fundedWallet.getAddress(), alice, mintAmount).send().wait(); + await token.methods.mint_to_private(alice, mintAmount).send().wait(); ``` Example when ``` let token_out_partial_note = Token::at(token_out).prepare_private_balance_increase( sender, - tagging_sender ).call(&mut context); ``` ### SharedMutable -> DelayedPublicMutable[​](#sharedmutable---delayedpublicmutable "Direct link to SharedMutable -> DelayedPublicMutable") The `SharedMutable` state variable has been renamed to `DelayedPublicMutable`. It is a public mutable with a delay before state changes take effect. It can be read in private during the delay period. The name "shared" confuses developers who actually wish to work with so-called "shared private state". Also, we're working on a `DelayedPrivateMutable` which will have similar properties, except writes will be scheduled from private instead. With this new state variable in mind, the new name works nicely. ## \[TXE] - Testing Aztec Contracts using Noir[​](#txe---testing-aztec-contracts-using-noir "Direct link to \[TXE] - Testing Aztec Contracts using Noir") ### Full `TestEnvironment` API overhaul[​](#full-testenvironment-api-overhaul "Direct link to full-testenvironment-api-overhaul") As part of a broader effort to make Noir tests that leverage TXE easier to use and reason about, large parts of it were changed or adapted, resulting in the API now being quite different. No functionality was lost, so it should be possible to migrate any older Noir test to use the new API. #### Network State Manipulation[​](#network-state-manipulation "Direct link to Network State Manipulation") * `committed_timestamp` removed: this function did not work correctly * `private_at_timestamp`: this function was not really meaningful: private contexts are built from block numbers, not timestamps * `pending_block_number` was renamed to `next_block_number`. `pending_timestamp` was removed since it was confusing and not useful * `committed_block_number` was renamed to `last_block_number` * `advance_timestamp_to` and `advance_timestamp_by` were renamed to `set_next_block_timestamp` and `advance_next_block_timestamp_by` respectively * `advance_block_to` was renamed to `mine_block_at`, which takes a timestamp instead of a target block number * `advance_block_by` was renamed to `mine_block`, which now mines a single block #### Account Management[​](#account-management "Direct link to Account Management") * `create_account` was renamed to `create_light_account` * `create_account_contract` was renamed to `create_contract_account` #### Contract Deployment[​](#contract-deployment "Direct link to Contract Deployment") * `deploy_self` removed: merged into `deploy` * `deploy` now accepts both local and external contracts #### Contract Interactions[​](#contract-interactions "Direct link to Contract Interactions") The old way of calling contract functions is gone. Contract functions are now invoked via the `call_private`, `view_private`, `call_public`, `view_public` and `simulate_utility` `TestEnvironment` methods. These take a `CallInterface`, like their old counterparts, but now also take an explicit `from` parameter (for the `call` variants - this is left out of the `view` and `simulate` methods for simplicity). #### Raw Context Access[​](#raw-context-access "Direct link to Raw Context Access") The `private` and `public` methods are gone. Private, public and utility contexts can now be crated with the `private_context`, `public_context` and `utility_context` functions, all of which takes a callback function that is called with the corresponding context. This functions are expected to be defined in-line as lambdas, and contain the user-defined test logic. This helps delineate where contexts begin and end. Contexts automatically mine blocks on closing, when appropriate. #### Error-expecting Functions[​](#error-expecting-functions "Direct link to Error-expecting Functions") `assert_public_call_revert` and variants have been removed. Use `#[test(should_fail_with = "message")]` instead. #### Example Migration[​](#example-migration "Direct link to Example Migration") The following are two tests using the older version of `TestEnvironment`: ``` #[test] unconstrained fn initial_empty_value() { let mut env = TestEnvironment::new(); // Setup without account contracts. We are not using authwits here, so dummy accounts are enough let admin = env.create_account(1); let initializer_call_interface = Auth::interface().constructor(admin); let auth_contract = env.deploy_self("Auth").with_public_void_initializer(admin, initializer_call_interface); let auth_contract_address = auth_contract.to_address(); env.impersonate(admin); let authorized = Auth::at(auth_contract_address).get_authorized().view(&mut env.public()); assert_eq(authorized, AztecAddress::from_field(0)); } #[test] unconstrained fn non_admin_cannot_set_authorized() { let mut env = TestEnvironment::new(); // Setup without account contracts. We are not using authwits here, so dummy accounts are enough let admin = env.create_account(1); let other = env.create_account(2); let initializer_call_interface = Auth::interface().constructor(admin); let auth_contract = env.deploy_self("Auth").with_public_void_initializer(admin, initializer_call_interface); let auth_contract_address = auth_contract.to_address(); env.impersonate(other); env.assert_public_call_fails(Auth::at(auth_contract_address).set_authorized(to_authorize)); } ``` These now look like this: ``` #[test] unconstrained fn authorized_initially_unset() { let mut env = TestEnvironment::new(); let admin = env.create_light_account(); // Manual secret management gone let auth_contract_address = env.deploy("Auth").with_public_initializer(admin, Auth::interface().constructor(admin)); // deploy_self replaced let auth = Auth::at(auth_contract_address); assert_eq(env.view_public(auth.get_authorized()), AztecAddress::zero()); // .view_public() instead of .public() } #[test(should_fail_with = "caller is not admin")] unconstrained fn non_admin_cannot_set_unauthorized() { let mut env = TestEnvironment::new(); let admin = env.create_light_account(); let other = env.create_light_account(); let auth_contract_address = env.deploy("Auth").with_public_initializer(admin, Auth::interface().constructor(admin)); // deploy_self replaced let auth = Auth::at(auth_contract_address); env.call_public(other, auth.set_authorized(other)); // .call_public(), should_fail_with } ``` ## \[Aztec.js][​](#aztecjs-1 "Direct link to \[Aztec.js]") ### Cheatcodes[​](#cheatcodes "Direct link to Cheatcodes") Cheatcodes where moved out of the `@aztec/aztec.js` package to `@aztec/ethereum` and `@aztec/aztec` packages. While all of the cheatcodes can be imported from the `@aztec/aztec` package `EthCheatCodes` and `RollupCheatCodes` reside in `@aztec/ethereum` package and if you need only those importing only that package should result in a lighter build. ### Note exports dropped from artifact[​](#note-exports-dropped-from-artifact "Direct link to Note exports dropped from artifact") Notes are no longer exported in the contract artifact. Exporting notes was technical debt from when we needed to interpret notes in TypeScript. The following code will no longer work since `notes` is no longer available on the artifact: ``` const valueNoteTypeId = StatefulTestContractArtifact.notes['ValueNote'].id; ``` ## \[core protocol, Aztec.nr, Aztec.js] Max block number property changed to be seconds based[​](#core-protocol-aztecnr-aztecjs-max-block-number-property-changed-to-be-seconds-based "Direct link to \[core protocol, Aztec.nr, Aztec.js] Max block number property changed to be seconds based") ### `max_block_number` -> `include_by_timestamp`[​](#max_block_number---include_by_timestamp "Direct link to max_block_number---include_by_timestamp") The transaction expiration mechanism has been updated to use seconds rather than number of blocks. As part of this change, the transaction property `max_block_number` has been renamed to `include_by_timestamp`. This change significantly impacts the `SharedMutable` state variable in `Aztec.nr`, which now operates on a seconds instead of number of blocks. If your contract uses `SharedMutable`, you'll need to: 1. Update the `INITIAL_DELAY` numeric generic to use seconds instead of blocks 2. Modify any related logic to account for timestamp-based timing 3. Note that timestamps use `u64` values while block numbers use `u32` ### Removed `prelude`, so your `dep::aztec::prelude::...` imports will need to be amended.[​](#removed-prelude-so-your-depaztecprelude-imports-will-need-to-be-amended "Direct link to removed-prelude-so-your-depaztecprelude-imports-will-need-to-be-amended") Instead of importing common types from `dep::aztec::prelude...`, you'll now need to import them from their lower-level locations. The Noir Language Server vscode extension is now capable of autocompleting imports: just type some of the import and press 'tab' when it pops up with the correct item, and the import will be inserted at the top of the file. As a quick reference, here are the paths to the types that were previously in the `prelude`. So, for example, if you were previously using `dep::aztec::prelude::AztecAddress`, you'll need to replace it with `dep::aztec::protocol::address::AztecAddress`. Apologies for any pain this brings. The reasoning is that these types were somewhat arbitrary, and it was unclear which types were worthy enough to be included here. ``` use dep::aztec::{ context::{PrivateCallInterface, PrivateContext, PublicContext, UtilityContext, ReturnsHash}, note::{ note_getter_options::NoteGetterOptions, note_interface::{NoteHash, NoteType}, note_viewer_options::NoteViewerOptions, hinted_note::HintedNote, }, state_vars::{ map::Map, private_immutable::PrivateImmutable, private_mutable::PrivateMutable, private_set::PrivateSet, public_immutable::PublicImmutable, public_mutable::PublicMutable, shared_mutable::SharedMutable, }, }; use dep::aztec::protocol::{ abis::function_selector::FunctionSelector, address::{AztecAddress, EthAddress}, point::Point, traits::{Deserialize, Serialize}, }; ``` ### `include_by_timestamp` is now mandatory[​](#include_by_timestamp-is-now-mandatory "Direct link to include_by_timestamp-is-now-mandatory") Each transaction must now include a valid `include_by_timestamp` that satisfies the following conditions: * It must be greater than the historical block’s timestamp. * The duration between the `include_by_timestamp` and the historical block’s timestamp must not exceed the maximum allowed (currently 24 hours). * It must be greater than or equal to the timestamp of the block in which the transaction is included. The protocol circuits compute the `include_by_timestamp` for contract updates during each private function iteration. If a contract does not explicitly specify a value, the default will be the maximum allowed duration. This ensures that `include_by_timestamp` is never left unset. No client-side changes are required. However, please note that transactions now have a maximum lifespan of 24 hours and will be removed from the transaction pool once expired. ## 0.88.0[​](#0880 "Direct link to 0.88.0") ## \[Aztec.nr] Deprecation of the `authwit` library[​](#aztecnr-deprecation-of-the-authwit-library "Direct link to aztecnr-deprecation-of-the-authwit-library") It is now included in `aztec-nr`, so imports must be updated: ``` -dep::authwit::... +dep::aztec::authwit... ``` and stale dependencies removed from `Nargo.toml` ``` -authwit = { path = "../../../../aztec-nr/authwit" } ``` ## 0.87.0[​](#0870 "Direct link to 0.87.0") ## \[Aztec.js/TS libraries][​](#aztecjsts-libraries "Direct link to \[Aztec.js/TS libraries]") We've bumped our minimum supported node version to v20, as v18 is now EOL. As a consequence, the deprecated type assertion syntax has been replaced with modern import attributes whenever contract artifact JSONs are loaded: ``` -import ArtifactJson from '../artifacts/contract-Contract.json' assert { type: 'json' }; +import ArtifactJson from '../artifacts/contract-Contract.json' with { type: 'json' }; ``` ## \[Aztec.js/PXE] `simulateUtility` return type[​](#aztecjspxe-simulateutility-return-type "Direct link to aztecjspxe-simulateutility-return-type") `pxe.simulateUtility()` now returns a complex object (much like `.simulateTx()`) so extra information can be provided such as simulation timings. This information can be accessed setting the `includeMetadata` flag in `SimulateMethodOptions` to `true`, but not providing it (which is the default) will NOT change the behavior of the current code. ``` -const result = await pxe.simulateUtility(...); +const { meta, result } = await pxe.simulateUtility(...); const result = await Contract.methods.myFunction(...).simulate(); const { result, meta} = await Contract.methods.myFunction(...).simulate({ includeMetadata: true }); ``` ## \[Aztec.js] Removed mandatory simulation before proving in contract interfaces[​](#aztecjs-removed-mandatory-simulation-before-proving-in-contract-interfaces "Direct link to \[Aztec.js] Removed mandatory simulation before proving in contract interfaces") Previously, our autogenerated contract classes would perform a simulation when calling `.prove` or `.send` on them. This could potentially catch errors earlier, but took away control from the app/wallets on how to handle network interactions. Now this process has to be triggered manually, which means just proving an interaction (or proving and sending it to the network in one go via `.send`) is much faster. *WARNING:* This means users can incurr in network fees if a transaction that would otherwise be invalid is sent without sanity checks. To ensure this, it is recommended to do: ``` +await Contract.method.simulate(); await Contract.method.send().wait(); ``` ## 0.86.0[​](#0860 "Direct link to 0.86.0") ### \[PXE] Removed PXE\_L2\_STARTING\_BLOCK environment variable[​](#pxe-removed-pxe_l2_starting_block-environment-variable "Direct link to \[PXE] Removed PXE_L2_STARTING_BLOCK environment variable") PXE now fast-syncs by skipping finalized blocks and never downloads all blocks, so there is no longer a need to specify a starting block. ### \[Aztec.nr] Logs and messages renaming[​](#aztecnr-logs-and-messages-renaming "Direct link to \[Aztec.nr] Logs and messages renaming") The following renamings have taken place: * `encrypted_logs` to `messages`: this module now handles much more than just encrypted logs (including unconstrained message delivery, message encoding, etc.) * `log_assembly_strategies` to `logs` * `discovery` moved to `messages`: given that what is discovered are messages * `default_aes128` removed Most contracts barely used these modules directly. The frequently used `encode_and_encrypt` function imports remain unchanged: ``` use dep::aztec::messages::logs::note::encode_and_encrypt_note; ``` ### \[noir-contracts] Reference Noir contracts directory structure change[​](#noir-contracts-reference-noir-contracts-directory-structure-change "Direct link to \[noir-contracts] Reference Noir contracts directory structure change") `noir-projects/noir-contracts/contracts` directory became too cluttered so we grouped contracts into `account`, `app`, `docs`, `fees`, `libs`, `protocol` and `test` dirs. If you import contract from the directory make sure to update the paths accordingly. E.g. for a token contract: ``` #[dependencies] -token = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v0.83.0", directory = "noir-projects/noir-contracts/contracts/src/token_contract" } +token = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v0.83.0", directory = "noir-projects/noir-contracts/contracts/app/src/token_contract" } ``` ### \[Aztec.nr] #\[utility] contract functions[​](#aztecnr-utility-contract-functions "Direct link to \[Aztec.nr] #\[utility] contract functions") Aztec contracts have three kinds of functions: `#[private]`, `#[public]` and what was sometimes called 'top-level unconstrained': an unmarked unconstrained function in the contract module. These are now called `[#utility]` functions, and must be explicitly marked as such: ``` + #[utility] unconstrained fn balance_of_private(owner: AztecAddress) -> u128 { storage.balances.at(owner).balance_of() } ``` Utility functions are standalone unconstrained functions that cannot be called from private or public functions: they are meant to be called by *applications* to perform auxiliary tasks: query contract state (e.g. a token balance), process messages received offchain, etc. All functions in a `contract` block must now be marked as one of either `#[private]`, `#[public]`, `#[utility]`, `#[contract_library_method]`, or `#[test]`. Additionally, the `UnconstrainedContext` type has been renamed to `UtilityContext`. This led us to rename the `unkonstrained` method on `TestEnvironment` to `utility`, so any tests using it also need updating: ``` - SharedMutable::new(env.unkonstrained(), storage_slot) + SharedMutable::new(env.utility(), storage_slot) ``` ### \[AuthRegistry] function name change[​](#authregistry-function-name-change "Direct link to \[AuthRegistry] function name change") As part of the broader transition from "top-level unconstrained" to "utility" name (detailed in the note above), the `unconstrained_is_consumable` function in AuthRegistry has been renamed to `utility_is_consumable`. The function's signature and behavior remain unchanged - only the name has been updated to align with the new convention. If you're currently using this function, a simple rename in your code will suffice. ## 0.83.0[​](#0830 "Direct link to 0.83.0") ### \[aztec.js] AztecNode.getPrivateEvents API change[​](#aztecjs-aztecnodegetprivateevents-api-change "Direct link to \[aztec.js] AztecNode.getPrivateEvents API change") The `getPrivateEvents` method signature has changed to require an address of a contract that emitted the event and use recipient addresses instead of viewing public keys: ``` - const events = await wallet.getPrivateEvents(TokenContract.events.Transfer, 1, 1, [recipient.getCompleteAddress().publicKeys.masterIncomingViewingPublicKey()]); + const events = await wallet.getPrivateEvents(token.address, TokenContract.events.Transfer, 1, 1, [recipient.getAddress()]); ``` ### \[portal contracts] Versions and Non-following message boxes[​](#portal-contracts-versions-and-non-following-message-boxes "Direct link to \[portal contracts] Versions and Non-following message boxes") The version number is no longer hard-coded to be `1` across all deployments (it not depends on where it is deployed to and with what genesis and logic). This means that if your portal were hard-coding `1` it will now fail when inserting into the `inbox` or consuming from the `outbox` because of a version mismatch. Instead you can get the real version (which don't change for a deployment) by reading the `VERSION` on inbox and outbox, or using `getVersion()` on the rollup. New Deployments of the protocol do not preserve former state/across each other. This means that after a new deployment, any "portal" following the registry would try to send messages into this empty rollup to non-existent contracts. To solve, the portal should be linked to a specific deployment, e.g., a specific inbox. This can be done by storing the inbox/outbox/version at the time of deployment or initialize and not update them. Both of these issues were in the token portal and the uniswap portal, so if you used them as a template it is very likely that you will also have it. ## 0.82.0[​](#0820 "Direct link to 0.82.0") ### \[aztec.js] AztecNode.findLeavesIndexes returns indexes with block metadata[​](#aztecjs-aztecnodefindleavesindexes-returns-indexes-with-block-metadata "Direct link to \[aztec.js] AztecNode.findLeavesIndexes returns indexes with block metadata") It's common that we need block metadata of a block in which leaves were inserted when querying indexes of these tree leaves. For this reason we now return that information along with the indexes. This allows us to reduce the number of individual AztecNode queries. Along with this change, `findNullifiersIndexesWithBlock` and `findBlockNumbersForIndexes` functions were removed as all their uses can now be replaced with the newly modified `findLeavesIndexes` function. ### \[aztec.js] AztecNode.getPublicDataTreeWitness renamed as AztecNode.getPublicDataWitness[​](#aztecjs-aztecnodegetpublicdatatreewitness-renamed-as-aztecnodegetpublicdatawitness "Direct link to \[aztec.js] AztecNode.getPublicDataTreeWitness renamed as AztecNode.getPublicDataWitness") This change was done to have consistent naming across codebase. ### \[aztec.js] Wallet interface and Authwit management[​](#aztecjs-wallet-interface-and-authwit-management "Direct link to \[aztec.js] Wallet interface and Authwit management") The `Wallet` interface in `aztec.js` is undergoing transformations, trying to be friendlier to wallet builders and reducing the surface of its API. This means `Wallet` no longer extends `PXE`, and instead just implements a subset of the methods of the former. This is NOT going to be its final form, but paves the way towards better interfaces and starts to clarify what the responsibilities of the wallet are: ``` /** * The wallet interface. */ export type Wallet = AccountInterface & Pick< PXE, // Simulation | "simulateTx" | "simulateUnconstrained" | "profileTx" // Sending | "sendTx" // Contract management (will probably be collapsed in the future to avoid instance and class versions) | "getContractClassMetadata" | "getContractMetadata" | "registerContract" | "registerContractClass" // Likely to be removed | "proveTx" // Will probably be collapsed | "getNodeInfo" | "getPXEInfo" // Fee info | "getCurrentMinFees" // Still undecided, kept for the time being | "updateContract" // Sender management | "registerSender" | "getSenders" | "removeSender" // Tx status | "getTxReceipt" // Events. Kept since events are going to be reworked and changes will come when that's done | "getPrivateEvents" | "getPublicEvents" > & { createAuthWit(intent: IntentInnerHash | IntentAction): Promise; }; ``` As a side effect, a few debug only features have been removed ``` // Obtain tx effects const { txHash, debugInfo } = await contract.methods .set_constant(value) .send() -- .wait({ interval: 0.1, debug: true }); ++ .wait({ interval: 0.1 }) -- // check that 1 note hash was created -- expect(debugInfo!.noteHashes.length).toBe(1); ++ const txEffect = await aztecNode.getTxEffect(txHash); ++ const noteHashes = txEffect?.data.noteHashes; ++ // check that 1 note hash was created ++ expect(noteHashes?.length).toBe(1); // Wait for a tx to be proven -- tx.wait({ timeout: 300, interval: 10, proven: true, provenTimeout: 3000 }))); ++ const receipt = await tx.wait({ timeout: 300, interval: 10 }); ++ await waitForProven(aztecNode, receipt, { provenTimeout: 3000 }); ``` Authwit management has changed, and PXE no longer stores them. This is unnecessary because now they can be externally provided to simulations and transactions, making sure no stale authorizations are kept inside PXE's db. ``` const witness = await wallet.createAuthWit({ caller, action }); --await callerWallet.addAuthWitness(witness); --await action.send().wait(); ++await action.send({ authWitnesses: [witness] }).wait(); ``` Another side effect of this is that the interface of the `lookupValidity` method has changed, and now the authwitness has to be provided: ``` const witness = await wallet.createAuthWit({ caller, action }); --await callerWallet.addAuthWitness(witness); --await wallet.lookupValidity(wallet.getAddress(), { caller, action }); ++await wallet.lookupValidity(wallet.getAddress(), { caller, action }, witness); ``` ## 0.80.0[​](#0800 "Direct link to 0.80.0") ### \[PXE] Concurrent contract function simulation disabled[​](#pxe-concurrent-contract-function-simulation-disabled "Direct link to \[PXE] Concurrent contract function simulation disabled") PXE is no longer be able to execute contract functions concurrently (e.g. by collecting calls to `simulateTx` and then using `await Promise.all`). They will instead be put in a job queue and executed sequentially in order of arrival. ## 0.79.0[​](#0790 "Direct link to 0.79.0") ### \[aztec.js] Changes to `BatchCall` and `BaseContractInteraction`[​](#aztecjs-changes-to-batchcall-and-basecontractinteraction "Direct link to aztecjs-changes-to-batchcall-and-basecontractinteraction") The constructor arguments of `BatchCall` have been updated to improve usability. Previously, it accepted an array of `FunctionCall`, requiring users to manually set additional data such as `authwit` and `capsules`. Now, `BatchCall` takes an array of `BaseContractInteraction`, which encapsulates all necessary information. ``` class BatchCall extends BaseContractInteraction { - constructor(wallet: Wallet, protected calls: FunctionCall[]) { + constructor(wallet: Wallet, protected calls: BaseContractInteraction[]) { ... } ``` The `request` method of `BaseContractInteraction` now returns `ExecutionPayload`. This object includes all the necessary data to execute one or more functions. `BatchCall` invokes this method on all interactions to aggregate the required information. It is also used internally in simulations for fee estimation. Declaring a `BatchCall`: ``` new BatchCall(wallet, [ - await token.methods.transfer(alice, amount).request(), - await token.methods.transfer_to_private(bob, amount).request(), + token.methods.transfer(alice, amount), + token.methods.transfer_to_private(bob, amount), ]) ``` ## 0.77.0[​](#0770 "Direct link to 0.77.0") ### \[aztec-nr] `TestEnvironment::block_number()` refactored[​](#aztec-nr-testenvironmentblock_number-refactored "Direct link to aztec-nr-testenvironmentblock_number-refactored") The `block_number` function from `TestEnvironment` has been expanded upon with two extra functions, the first being `pending_block_number`, and the second being `committed_block_number`. `pending_block_number` now returns what `block_number` does. In other words, it returns the block number of the block we are currently building. `committed_block_number` returns the block number of the last committed block, i.e. the block number that gets used to execute the private part of transactions when your PXE is successfully synced to the tip of the chain. ``` + `TestEnvironment::pending_block_number()` + `TestEnvironment::committed_block_number()` ``` ### \[aztec-nr] `compute_nullifier_without_context` renamed[​](#aztec-nr-compute_nullifier_without_context-renamed "Direct link to aztec-nr-compute_nullifier_without_context-renamed") The `compute_nullifier_without_context` function from `NoteHash` (ex `NoteInterface`) is now called `compute_nullifier_unconstrained`, and instead of taking storage slot, contract address and nonce it takes a note hash for nullification (same as `compute_note_hash`). This makes writing this function simpler: ``` - unconstrained fn compute_nullifier_without_context(self, storage_slot: Field, contract_address: AztecAddress, nonce: Field) -> Field { - let note_hash_for_nullify = ...; + unconstrained fn compute_nullifier_unconstrained(self, note_hash_for_nullify: Field) -> Field { ... } ``` ### `U128` type replaced with native `u128`[​](#u128-type-replaced-with-native-u128 "Direct link to u128-type-replaced-with-native-u128") The `U128` type has been replaced with the native `u128` type. This means that you can no longer use the `U128` type in your code. Instead, you should use the `u128` type. Doing the changes is as straightforward as: ``` #[public] #[view] - fn balance_of_public(owner: AztecAddress) -> U128 { + fn balance_of_public(owner: AztecAddress) -> u128 { storage.public_balances.at(owner).read() } ``` `UintNote` has also been updated to use the native `u128` type. ### \[aztec-nr] Removed `compute_note_hash_and_optionally_a_nullifier`[​](#aztec-nr-removed-compute_note_hash_and_optionally_a_nullifier "Direct link to aztec-nr-removed-compute_note_hash_and_optionally_a_nullifier") This function is no longer mandatory for contracts, and the `#[aztec]` macro no longer injects it. ### \[PXE] Removed `addNote` and `addNullifiedNote`[​](#pxe-removed-addnote-and-addnullifiednote "Direct link to pxe-removed-addnote-and-addnullifiednote") These functions have been removed from PXE and the base `Wallet` interface. If you need to deliver a note manually because its creation is not being broadcast in an encrypted log, then create an unconstrained contract function to process it and simulate execution of it. The `aztec::discovery::private_logs::do_process_log` function can be used to perform note discovery and add to it to PXE. See an example of how to handle a `TransparentNote`: ``` unconstrained fn deliver_transparent_note( contract_address: AztecAddress, amount: Field, secret_hash: Field, tx_hash: Field, unique_note_hashes_in_tx: BoundedVec, first_nullifier_in_tx: Field, recipient: AztecAddress, ) { // do_process_log expects a standard aztec-nr encoded note, which has the following shape: // [ storage_slot, note_type_id, ...packed_note ] let note = TransparentNote::new(amount, secret_hash); let log_plaintext = BoundedVec::from_array(array_concat( [ MyContract::storage_layout().my_state_variable.slot, TransparentNote::get_note_type_id(), ], note.pack(), )); do_process_log( contract_address, log_plaintext, tx_hash, unique_note_hashes_in_tx, first_nullifier_in_tx, recipient, _compute_note_hash_and_nullifier, ); } ``` The note is then processed by calling this function: ``` const txEffects = await wallet.getTxEffect(txHash); await contract.methods .deliver_transparent_note( contract.address, new Fr(amount), secretHash, txHash.hash, toBoundedVec(txEffects!.data.noteHashes, MAX_NOTE_HASHES_PER_TX), txEffects!.data.nullifiers[0], wallet.getAddress(), ) .simulate(); ``` ### Fee is mandatory[​](#fee-is-mandatory "Direct link to Fee is mandatory") All transactions must now pay fees. Previously, the default payment method was `NoFeePaymentMethod`; It has been changed to `FeeJuicePaymentMethod`, with the wallet owner as the fee payer. For example, the following code will still work: ``` await TokenContract.at(address, wallet).methods.transfer(recipient, 100n).send().wait(); ``` However, the wallet owner must have enough fee juice to cover the transaction fee. Otherwise, the transaction will be rejected. The 3 test accounts deployed in the sandbox are pre-funded with 10 ^ 22 fee juice, allowing them to send transactions right away. In addition to the native fee juice, users can pay the transaction fees using tokens that have a corresponding FPC contract. The sandbox now includes `BananaCoin` and `BananaFPC`. Users can use a funded test account to mint banana coin for a new account. The new account can then start sending transactions and pay fees with banana coin. ``` import { getDeployedTestAccountsWallets } from "@aztec/accounts/testing"; import { getDeployedBananaCoinAddress, getDeployedBananaFPCAddress, } from "@aztec/aztec"; // Fetch the funded test accounts. const [fundedWallet] = await getDeployedTestAccountsWallets(pxe); // Create a new account. const secret = Fr.random(); const signingKey = GrumpkinScalar.random(); const alice = await getSchnorrAccount(pxe, secret, signingKey); const aliceWallet = await alice.getWallet(); const aliceAddress = alice.getAddress(); // Deploy the new account using the pre-funded test account. await alice.deploy({ deployWallet: fundedWallet }).wait(); // Mint banana coin for the new account. const bananaCoinAddress = await getDeployedBananaCoinAddress(pxe); const bananaCoin = await TokenContract.at(bananaCoinAddress, fundedWallet); const mintAmount = 10n ** 20n; await bananaCoin.methods .mint_to_private(fundedWallet.getAddress(), aliceAddress, mintAmount) .send() .wait(); // Use the new account to send a tx and pay with banana coin. const transferAmount = 100n; const bananaFPCAddress = await getDeployedBananaFPCAddress(pxe); const paymentMethod = new PrivateFeePaymentMethod( bananaFPCAddress, aliceWallet, ); const receipt = await bananaCoin .withWallet(aliceWallet) .methods.transfer(recipient, transferAmount) .send({ fee: { paymentMethod } }) .wait(); const transactionFee = receipt.transactionFee!; // Check the new account's balance. const aliceBalance = await bananaCoin.methods .balance_of_private(aliceAddress) .simulate(); expect(aliceBalance).toEqual(mintAmount - transferAmount - transactionFee); ``` ### The tree of protocol contract addresses is now an indexed tree[​](#the-tree-of-protocol-contract-addresses-is-now-an-indexed-tree "Direct link to The tree of protocol contract addresses is now an indexed tree") This is to allow for non-membership proofs for non-protocol contract addresses. As before, the canonical protocol contract addresses point to the index of the leaf of the 'real' computed protocol address. For example, the canonical `DEPLOYER_CONTRACT_ADDRESS` is a constant `= 2`. This is used in the kernels as the `contract_address`. We calculate the `computed_address` (currently `0x1665c5fbc1e58ba19c82f64c0402d29e8bbf94b1fde1a056280d081c15b0dac1`) and check that this value exists in the indexed tree at index `2`. This check already existed and ensures that the call cannot do 'special' protocol contract things unless it is a real protocol contract. The new check an indexed tree allows is non-membership of addresses of non protocol contracts. This ensures that if a call is from a protocol contract, it must use the canonical address. For example, before this check a call could be from the deployer contract and use `0x1665c5fbc1e58ba19c82f64c0402d29e8bbf94b1fde1a056280d081c15b0dac1` as the `contract_address`, but be incorrectly treated as a 'normal' call. ``` - let computed_protocol_contract_tree_root = if is_protocol_contract { - 0 - } else { - root_from_sibling_path( - computed_address.to_field(), - protocol_contract_index, - private_call_data.protocol_contract_sibling_path, - ) - }; + conditionally_assert_check_membership( + computed_address.to_field(), + is_protocol_contract, + private_call_data.protocol_contract_leaf, + private_call_data.protocol_contract_membership_witness, + protocol_contract_tree_root, + ); ``` ### \[Aztec.nr] Changes to note interfaces and note macros[​](#aztecnr-changes-to-note-interfaces-and-note-macros "Direct link to \[Aztec.nr] Changes to note interfaces and note macros") In this releases we decided to do a large refactor of notes which resulted in the following changes: 1. We removed `NoteHeader` and we've introduced a `HintedNote` struct that contains a note and the information originally stored in the `NoteHeader`. 2. We removed the `pack_content` and `unpack_content` functions from the `NoteInterface`and made notes implement the standard `Packable` trait. 3. We renamed the `NullifiableNote` trait to `NoteHash` and we've moved the `compute_note_hash` function to this trait from the `NoteInterface` trait. 4. We renamed `NoteInterface` trait as `NoteType` and `get_note_type_id` function as `get_id`. 5. The `#[note]` and `#[partial_note]` macros now generate both the `NoteType` and `NoteHash` traits. 6. `#[custom_note_interface]` macro has been renamed to `#[custom_note]` and it now implements the `NoteInterface` trait. This led us to do the following changes to the interfaces: ``` -pub trait NoteInterface { +pub trait NoteType { fn get_id() -> Field; - fn pack_content(self) -> [Field; N]; - fn unpack_content(fields: [Field; N]) -> Self; - fn get_header(self) -> NoteHeader; - fn set_header(&mut self, header: NoteHeader) -> (); - fn compute_note_hash(self) -> Field; } pub trait NoteHash { + fn compute_note_hash(self, storage_slot: Field) -> Field; fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field; - unconstrained fn compute_nullifier_without_context(self) -> Field; + unconstrained fn fn compute_nullifier_without_context(self, storage_slot: Field, contract_address: AztecAddress, note_nonce: Field) -> Field; } ``` If you are using `#[note]` or `#[partial_note(...)]` macros you will need to delete the implementations of the `NullifiableNote` (now `NoteHash`) trait as it now gets auto-generated. Your note will also need to have an `owner` (a note struct field called owner) as its used in the auto-generated nullifier functions. If you need a custom implementation of the `NoteHash` interface use the `#[custom_note]` macro. If you used `#[note_custom_interface]` macro before you will need to update your notes by using the `#[custom_note]` macro and implementing the `compute_note_hash` function. If you have no need for a custom implementation of the `compute_note_hash` function copy the default one: ``` fn compute_note_hash(self, storage_slot: Field) -> Field { let inputs = aztec::protocol::utils::arrays::array_concat(self.pack(), [storage_slot]); aztec::protocol::hash::poseidon2_hash_with_separator(inputs, aztec::protocol::constants::DOM_SEP__NOTE_HASH) } ``` If you need to keep the custom implementation of the packing functionality, manually implement the `Packable` trait: ``` + use dep::aztec::protocol::traits::Packable; +impl Packable for YourNote { + fn pack(self) -> [Field; N] { + ... + } + + fn unpack(fields: [Field; N]) -> Self { + ... + } +} ``` If you don't provide a custom implementation of the `Packable` trait, a default one will be generated. ### \[Aztec.nr] Changes to state variables[​](#aztecnr-changes-to-state-variables "Direct link to \[Aztec.nr] Changes to state variables") Since we've removed `NoteHeader` from notes we no longer need to modify the header in the notes when working with state variables. This means that we no longer need to be passing a mutable note reference which led to the following changes in the API. #### PrivateImmutable[​](#privateimmutable "Direct link to PrivateImmutable") For `PrivateImmutable` the changes are fairly straightforward. Instead of passing in a mutable reference `&mut note` just pass in `note`. ``` impl PrivateImmutable { - pub fn initialize(self, note: &mut Note) -> NoteEmission + pub fn initialize(self, note: Note) -> NoteEmission where Note: NoteInterface + NullifiableNote, { ... } } ``` #### PrivateSet[​](#privateset "Direct link to PrivateSet") For `PrivateSet` the changes are a bit more involved than the changes in `PrivateImmutable`. Instead of passing in a mutable reference `&mut note` to the `insert` function just pass in `note`. The `remove` function now takes in a `HintedNote` instead of a `Note` and the `get_notes` function now returns a vector `HintedNote`s instead of a vector `Note`s. Note getters now generally return `HintedNote`s so getting a hold of the `HintedNote` for removal should be straightforward. ``` impl PrivateSet where Note: NoteInterface + NullifiableNote + Eq, { - pub fn insert(self, note: &mut Note) -> NoteEmission { + pub fn insert(self, note: Note) -> NoteEmission { ... } - pub fn remove(self, note: Note) { + pub fn remove(self, hinted_note: HintedNote) { ... } pub fn get_notes( self, options: NoteGetterOptions, - ) -> BoundedVec { + ) -> BoundedVec, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL> { ... } } - impl PrivateSet - where - Note: NoteInterface + NullifiableNote, - { - pub fn insert_from_public(self, note: &mut Note) { - create_note_hash_from_public(self.context, self.storage_slot, note); - } - } ``` #### PrivateMutable[​](#privatemutable "Direct link to PrivateMutable") For `PrivateMutable` the changes are similar to the changes in `PrivateImmutable`. ``` impl PrivateMutable where Note: NoteInterface + NullifiableNote, { - pub fn initialize(self, note: &mut Note) -> NoteEmission { + pub fn initialize(self, note: Note) -> NoteEmission { ... } - pub fn replace(self, new_note: &mut Note) -> NoteEmission { + pub fn replace(self, new_note: Note) -> NoteEmission { ... } - pub fn initialize_or_replace(self, note: &mut Note) -> NoteEmission { + pub fn initialize_or_replace(self, note: Note) -> NoteEmission { ... } } ``` ## 0.75.0[​](#0750 "Direct link to 0.75.0") ### Changes to `TokenBridge` interface[​](#changes-to-tokenbridge-interface "Direct link to changes-to-tokenbridge-interface") `get_token` and `get_portal_address` functions got merged into a single `get_config` function that returns a struct containing both the token and portal addresses. ### \[Aztec.nr] `SharedMutable` can store size of packed length larger than 1[​](#aztecnr-sharedmutable-can-store-size-of-packed-length-larger-than-1 "Direct link to aztecnr-sharedmutable-can-store-size-of-packed-length-larger-than-1") `SharedMutable` has been modified such that now it can store type `T` which packs to a length larger than 1. This is a breaking change because now `SharedMutable` requires `T` to implement `Packable` trait instead of `ToField` and `FromField` traits. To implement the `Packable` trait for your type you can use the derive macro: ``` + use std::meta::derive; + #[derive(Packable)] pub struct YourType { ... } ``` ### \[Aztec.nr] Introduction of `WithHash`[​](#aztecnr-introduction-of-withhasht "Direct link to aztecnr-introduction-of-withhasht") `WithHash` is a struct that allows for efficient reading of value `T` from public storage in private. This is achieved by storing the value with its hash, then obtaining the values via an oracle and verifying them against the hash. This results in in a fewer tree inclusion proofs for values `T` that are packed into more than a single field. `WithHash` is leveraged by state variables like `PublicImmutable`. This is a breaking change because now we require values stored in `PublicImmutable` and `SharedMutable` to implement the `Eq` trait. To implement the `Eq` trait you can use the `#[derive(Eq)]` macro: ``` + use std::meta::derive; + #[derive(Eq)] pub struct YourType { ... } ``` ## 0.73.0[​](#0730 "Direct link to 0.73.0") ### \[Token, FPC] Moving fee-related complexity from the Token to the FPC[​](#token-fpc-moving-fee-related-complexity-from-the-token-to-the-fpc "Direct link to \[Token, FPC] Moving fee-related complexity from the Token to the FPC") There was a complexity leak of fee-related functionality in the token contract. We've came up with a way how to achieve the same objective with the general functionality of the Token contract. This lead to the removal of `setup_refund` and `complete_refund` functions from the Token contract and addition of `complete_refund` function to the FPC. ### \[Aztec.nr] Improved storage slot allocation[​](#aztecnr-improved-storage-slot-allocation "Direct link to \[Aztec.nr] Improved storage slot allocation") State variables are no longer assumed to be generic over a type that implements the `Serialize` trait: instead, they must implement the `Storage` trait with an `N` value equal to the number of slots they need to reserve. For the vast majority of state variables, this simply means binding the serialization length to this trait: ``` + impl Storage for MyStateVar where T: Serialize { }; ``` ### \[Aztec.nr] Introduction of `Packable` trait[​](#aztecnr-introduction-of-packable-trait "Direct link to aztecnr-introduction-of-packable-trait") We have introduced a `Packable` trait that allows types to be serialized and deserialized with a focus on minimizing the size of the resulting Field array. This is in contrast to the `Serialize` and `Deserialize` traits, which follows Noir's intrinsic serialization format. This is a breaking change because we now require `Packable` trait implementation for any type that is to be stored in contract storage. Example implementation of Packable trait for `U128` type from `noir::std`: ``` use crate::traits::{Packable, ToField}; let U128_PACKED_LEN: u32 = 1; impl Packable for U128 { fn pack(self) -> [Field; U128_PACKED_LEN] { [self.to_field()] } fn unpack(fields: [Field; U128_PACKED_LEN]) -> Self { U128::from_integer(fields[0]) } } ``` ### Logs for notes, partial notes, and events have been refactored.[​](#logs-for-notes-partial-notes-and-events-have-been-refactored "Direct link to Logs for notes, partial notes, and events have been refactored.") We're preparing to make log assembly more customisable. These paths have changed. ``` - use dep::aztec::encrypted_logs::encrypted_note_emission::encode_and_encrypt_note, + use dep::aztec::messages::logs::note::encode_and_encrypt_note, ``` And similar paths for `encode_and_encrypt_note_unconstrained`, and for events and partial notes. The way in which logs are assembled in this "default\_aes128" strategy is has also changed. I repeat: **Encrypted log layouts have changed**. The corresponding typescript for note discovery has also been changed, but if you've rolled your own functions for parsing and decrypting logs, those will be broken by this change. ### `NoteInferface` and `EventInterface` no-longer have a `to_be_bytes` method.[​](#noteinferface-and-eventinterface-no-longer-have-a-to_be_bytes-method "Direct link to noteinferface-and-eventinterface-no-longer-have-a-to_be_bytes-method") You can remove this method from any custom notes or events that you've implemented. ### \[Aztec.nr] Packing notes resulting in changes in `NoteInterface`[​](#aztecnr-packing-notes-resulting-in-changes-in-noteinterface "Direct link to aztecnr-packing-notes-resulting-in-changes-in-noteinterface") Note interface implementation generated by our macros now packs note content instead of serializing it With this change notes are being less costly DA-wise to emit when some of the note struct members implements the `Packable` trait (this is typically the `UintNote` which represents `value` as `U128` that gets serialized as 2 fields but packed as 1). This results in the following changes in the `NoteInterface`: ``` pub trait NoteInterface { - fn serialize_content(self) -> [Field; N]; + fn pack_content(self) -> [Field; N]; - fn deserialize_content(fields: [Field; N]) -> Self; + fn unpack_content(fields: [Field; N]) -> Self; fn get_header(self) -> NoteHeader; fn set_header(&mut self, header: NoteHeader) -> (); fn get_note_type_id() -> Field; fn compute_note_hash(self) -> Field; } ``` ### \[PXE] Cleanup of Contract and ContractClass information getters[​](#pxe-cleanup-of-contract-and-contractclass-information-getters "Direct link to \[PXE] Cleanup of Contract and ContractClass information getters") ``` - pxe.isContractInitialized - pxe.getContractInstance - pxe.isContractPubliclyDeployed + pxe.getContractMetadata ``` have been merged into getContractMetadata ``` - pxe.getContractClass - pxe.isContractClassPubliclyRegistered - pxe.getContractArtifact + pxe.getContractClassMetadata ``` These functions have been merged into `pxe.getContractMetadata` and `pxe.getContractClassMetadata`. ## 0.72.0[​](#0720 "Direct link to 0.72.0") ### Some functions in `aztec.js` and `@aztec/accounts` are now async[​](#some-functions-in-aztecjs-and-aztecaccounts-are-now-async "Direct link to some-functions-in-aztecjs-and-aztecaccounts-are-now-async") In our efforts to make libraries more browser-friendly and providing with more bundling options for `bb.js` (like a non top-level-await version), some functions are being made async, in particular those that access our cryptographic functions. ``` - AztecAddress.random(); + await AztecAddress.random(); - getSchnorrAccount(); + await getSchnorrAccount(); ``` ### Public logs replace unencrypted logs[​](#public-logs-replace-unencrypted-logs "Direct link to Public logs replace unencrypted logs") Any log emitted from public is now known as a public log, rather than an unencrypted log. This means methods relating to these logs have been renamed e.g. in the pxe, archiver, txe: ``` - getUnencryptedLogs(filter: LogFilter): Promise - getUnencryptedEvents(eventMetadata: EventMetadataDefinition, from: number, limit: number): Promise + getPublicLogs(filter: LogFilter): Promise + getPublicEvents(eventMetadata: EventMetadataDefinition, from: number, limit: number): Promise ``` The context method in aztec.nr is now: ``` - context.emit_unencrypted_log(log) + context.emit_public_log(log) ``` These logs were treated as bytes in the node and as hashes in the protocol circuits. Now, public logs are treated as fields everywhere: ``` - unencryptedLogs: UnencryptedTxL2Logs - unencrypted_logs_hashes: [ScopedLogHash; MAX_UNENCRYPTED_LOGS_PER_TX] + publicLogs: PublicLog[] + public_logs: [PublicLog; MAX_PUBLIC_LOGS_PER_TX] ``` A `PublicLog` contains the log (as an array of fields) and the app address. This PR also renamed encrypted events to private events: ``` - getEncryptedEvents(eventMetadata: EventMetadataDefinition, from: number, limit: number, vpks: Point[]): Promise + getPrivateEvents(eventMetadata: EventMetadataDefinition, from: number, limit: number, vpks: Point[]): Promise ``` ## 0.70.0[​](#0700 "Direct link to 0.70.0") ### \[Aztec.nr] Removal of `getSiblingPath` oracle[​](#aztecnr-removal-of-getsiblingpath-oracle "Direct link to aztecnr-removal-of-getsiblingpath-oracle") Use `getMembershipWitness` oracle instead that returns both the sibling path and index. ## 0.68.0[​](#0680 "Direct link to 0.68.0") ### \[archiver, node, pxe] Remove contract artifacts in node and archiver and store function names instead[​](#archiver-node-pxe-remove-contract-artifacts-in-node-and-archiver-and-store-function-names-instead "Direct link to \[archiver, node, pxe] Remove contract artifacts in node and archiver and store function names instead") Contract artifacts were only in the archiver for debugging purposes. Instead function names are now (optionally) emitted when registering contract classes Function changes in the Node interface and Contract Data source interface: ``` - addContractArtifact(address: AztecAddress, artifact: ContractArtifact): Promise; + registerContractFunctionNames(address: AztecAddress, names: Record): Promise; ``` So now the PXE registers this when calling `registerContract()` ``` await this.node.registerContractFunctionNames(instance.address, functionNames); ``` Function changes in the Archiver ``` - addContractArtifact(address: AztecAddress, artifact: ContractArtifact) - getContractArtifact(address: AztecAddress) + registerContractFunctionNames(address: AztecAddress, names: Record): Promise ``` ### \[fees, fpc] Changes in setting up FPC as fee payer on AztecJS and method names in FPC[​](#fees-fpc-changes-in-setting-up-fpc-as-fee-payer-on-aztecjs-and-method-names-in-fpc "Direct link to \[fees, fpc] Changes in setting up FPC as fee payer on AztecJS and method names in FPC") On AztecJS, setting up `PrivateFeePaymentMethod` and `PublicFeePaymentMethod` are now the same. The don't need to specify a sequencer address or which coin to pay in. The coins are set up in the FPC contract! ``` - paymentMethod: new PrivateFeePaymentMethod(bananaCoin.address,bananaFPC.address,aliceWallet,sequencerAddress), + paymentMethod: new PrivateFeePaymentMethod(bananaFPC.address, aliceWallet), - paymentMethod: new PublicFeePaymentMethod(bananaCoin.address, bananaFPC.address, aliceWallet), + paymentMethod: new PublicFeePaymentMethod(bananaFPC.address, aliceWallet), ``` Changes in `FeePaymentMethod` class in AztecJS ``` - getAsset(): AztecAddress; + getAsset(): Promise; ``` Changes in the token contract: FPC specific methods, `setup_refund()` and `complete_refund()` have minor args rename. Changes in FPC contract: Rename of args in all of FPC functions as FPC now stores the accepted token address and admin and making it clearer the amounts are corresponding to the accepted token and not fee juice. Also created a public function `pull_funds()` for admin to clawback any money in the FPC Expect more changes in FPC in the coming releases! ### Name change from `contact` to `sender` in PXE API[​](#name-change-from-contact-to-sender-in-pxe-api "Direct link to name-change-from-contact-to-sender-in-pxe-api") `contact` has been deemed confusing because the name is too similar to `contract`. For this reason we've decided to rename it: ``` - await pxe.registerContact(address); + await pxe.registerSender(address); - await pxe.getContacts(); + await pxe.getSenders(); - await pxe.removeContact(address); + await pxe.removeSender(address); ``` ## 0.67.1[​](#0671 "Direct link to 0.67.1") ### Noir contracts package no longer exposes artifacts as default export[​](#noir-contracts-package-no-longer-exposes-artifacts-as-default-export "Direct link to Noir contracts package no longer exposes artifacts as default export") To reduce loading times, the package `@aztec/noir-contracts.js` no longer exposes all artifacts as its default export. Instead, it exposes a `ContractNames` variable with the list of all contract names available. To import a given artifact, use the corresponding export, such as `@aztec/noir-contracts.js/FPC`. ### Blobs[​](#blobs "Direct link to Blobs") We now publish the majority of DA in L1 blobs rather than calldata, with only contract class logs remaining as calldata. This replaces all code that touched the `txsEffectsHash`. In the rollup circuits, instead of hashing each child circuit's `txsEffectsHash` to form a tree, we track tx effects by absorbing them into a sponge for blob data (hence the name: `spongeBlob`). This sponge is treated like the state trees in that we check each rollup circuit 'follows' the next: ``` - let txs_effects_hash = sha256_to_field(left.txs_effects_hash, right.txs_effects_hash); + assert(left.end_sponge_blob.eq(right.start_sponge_blob)); + let start_sponge_blob = left.start_sponge_blob; + let end_sponge_blob = right.end_sponge_blob; ``` This sponge is used in the block root circuit to confirm that an injected array of all `txEffects` does match those rolled up so far in the `spongeBlob`. Then, the `txEffects` array is used to construct and prove opening of the polynomial representing the blob commitment on L1 (this is done efficiently thanks to the Barycentric formula). On L1, we publish the array as a blob and verify the above proof of opening. This confirms that the tx effects in the rollup circuit match the data in the blob: ``` - bytes32 txsEffectsHash = TxsDecoder.decode(_body); + bytes32 blobHash = _validateBlob(blobInput); ``` Where `blobInput` contains the proof of opening and evaluation calculated in the block root rollup circuit. It is then stored and used as a public input to verifying the epoch proof. ## 0.67.0[​](#0670 "Direct link to 0.67.0") ### L2 Gas limit of 6M enforced for public portion of TX[​](#l2-gas-limit-of-6m-enforced-for-public-portion-of-tx "Direct link to L2 Gas limit of 6M enforced for public portion of TX") A 12M limit was previously enforced per-enqueued-public-call. The protocol now enforces a stricter limit that the entire public portion of a transaction consumes at most 6,000,000 L2 gas. ### \[aztec.nr] Renamed `Header` and associated helpers[​](#aztecnr-renamed-header-and-associated-helpers "Direct link to aztecnr-renamed-header-and-associated-helpers") The `Header` struct has been renamed to `BlockHeader`, and the `get_header()` family of functions have been similarly renamed to `get_block_header()`. ``` - let header = context.get_header_at(block_number); + let header = context.get_block_header_at(block_number); ``` ### Outgoing Events removed[​](#outgoing-events-removed "Direct link to Outgoing Events removed") Previously, every event which was emitted included: * Incoming Header (to convey the app contract address to the recipient) * Incoming Ciphertext (to convey the note contents to the recipient) * Outgoing Header (served as a backup, to convey the app contract address to the "outgoing viewer" - most likely the sender) * Outgoing Ciphertext (served as a backup, encrypting the symmetric key of the incoming ciphertext to the "outgoing viewer" - most likely the sender) The latter two have been removed from the `.emit()` functions, so now only an Incoming Header and Incoming Ciphertext will be emitted. The interface for emitting a note has therefore changed, slightly. No more ovpk's need to be derived and passed into `.emit()` functions. ``` - nfts.at(to).insert(&mut new_note).emit(encode_and_encrypt_note(&mut context, from_ovpk_m, to, from)); + nfts.at(to).insert(&mut new_note).emit(encode_and_encrypt_note(&mut context, to, from)); ``` The `getOutgoingNotes` function is removed from the PXE interface. Some aztec.nr library methods' arguments are simplified to remove an `outgoing_viewer` parameter. E.g. `ValueNote::increment`, `ValueNote::decrement`, `ValueNote::decrement_by_at_most`, `EasyPrivateUint::add`, `EasyPrivateUint::sub`. Further changes are planned, so that: * Outgoing ciphertexts (or any kind of abstract ciphertext) can be emitted by a contract, and on the other side discovered and then processed by the contract. * Headers will be removed, due to the new tagging scheme. ## 0.66[​](#066 "Direct link to 0.66") ### DEBUG env var is removed[​](#debug-env-var-is-removed "Direct link to DEBUG env var is removed") The `DEBUG` variable is no longer used. Use `LOG_LEVEL` with one of `silent`, `fatal`, `error`, `warn`, `info`, `verbose`, `debug`, or `trace`. To tweak log levels per module, add a list of module prefixes with their overridden level. For example, LOG\_LEVEL="info; verbose: aztec:sequencer, aztec:archiver; debug: aztec:kv-store" sets `info` as the default log level, `verbose` for the sequencer and archiver, and `debug` for the kv-store. Module name match is done by prefix. ### `tty` resolve fallback required for browser bundling[​](#tty-resolve-fallback-required-for-browser-bundling "Direct link to tty-resolve-fallback-required-for-browser-bundling") When bundling `aztec.js` for web, the `tty` package now needs to be specified as an empty fallback: ``` resolve: { plugins: [new ResolveTypeScriptPlugin()], alias: { './node/index.js': false }, fallback: { crypto: false, os: false, fs: false, path: false, url: false, + tty: false, worker_threads: false, buffer: require.resolve('buffer/'), util: require.resolve('util/'), stream: require.resolve('stream-browserify'), }, }, ``` ## 0.65[​](#065 "Direct link to 0.65") ### \[aztec.nr] Removed SharedImmutable[​](#aztecnr-removed-sharedimmutable "Direct link to \[aztec.nr] Removed SharedImmutable") The `SharedImmutable` state variable has been removed, since it was essentially the exact same as `PublicImmutable`, which now contains functions for reading from private: ``` - foo: SharedImmutable. + foo: PublicImmutable. ``` ### \[aztec.nr] SharedImmutable renamings[​](#aztecnr-sharedimmutable-renamings "Direct link to \[aztec.nr] SharedImmutable renamings") `SharedImmutable::read_private` and `SharedImmutable::read_public` were renamed to simply `read`, since only one of these versions is ever available depending on the current context. ``` // In private - let value = storage.my_var.read_private(); + let value = storage.my_var.read(); // In public - let value = storage.my_var.read_public(); + let value = storage.my_var.read(); ``` ### \[aztec.nr] SharedMutable renamings[​](#aztecnr-sharedmutable-renamings "Direct link to \[aztec.nr] SharedMutable renamings") `SharedMutable` getters (`get_current_value_in_public`, etc.) were renamed by dropping the `_in` suffix, since only one of these versions is ever available depending on the current context. ``` // In private - let value = storage.my_var.get_current_value_in_private(); + let value = storage.my_var.get_current_value(); // In public - let value = storage.my_var.get_current_value_in_public(); + let value = storage.my_var.get_current_value(); ``` ### \[aztec.js] Random addresses are now valid[​](#aztecjs-random-addresses-are-now-valid "Direct link to \[aztec.js] Random addresses are now valid") The `AztecAddress.random()` function now returns valid addresses, i.e. addresses that can receive encrypted messages and therefore have notes be sent to them. `AztecAddress.isValid()` was also added to check for validity of an address. ## 0.63.0[​](#0630 "Direct link to 0.63.0") ### \[PXE] Note tagging and discovery[​](#pxe-note-tagging-and-discovery "Direct link to \[PXE] Note tagging and discovery") PXE's trial decryption of notes has been replaced in favor of a tagging and discovery approach. It is much more efficient and should scale a lot better as the network size increases, since notes can now be discovered on-demand. For the time being, this means that accounts residing *on different PXE instances* should add senders to their contact list, so notes can be discovered (accounts created on the same PXE instance will be added as senders for each other by default) ``` +pxe.registerContact(senderAddress) ``` The note discovery process is triggered automatically whenever a contract invokes the `get_notes` oracle, meaning no contract changes are expected. Just in case, every contract has now a utility method `sync_notes` that can trigger the process manually if necessary. This can be useful since now the `DebugInfo` object that can be obtained when sending a tx with the `debug` flag set to true no longer contains the notes that were generated in the transaction: ``` const receipt = await inclusionsProofsContract.methods.create_note(owner, 5n).send().wait({ debug: true }); -const { visibleIncomingNotes } = receipt.debugInfo!; -expect(visibleIncomingNotes.length).toEqual(1); +await inclusionsProofsContract.methods.sync_notes().simulate(); +const incomingNotes = await wallet.getIncomingNotes({ txHash: receipt.txHash }); +expect(incomingNotes.length).toEqual(1); ``` ### \[Token contract] Partial notes related refactor[​](#token-contract-partial-notes-related-refactor "Direct link to \[Token contract] Partial notes related refactor") We've decided to replace the old "shield" flow with one leveraging partial notes. This led to a removal of `shield` and `redeem_shield` functions and an introduction of `transfer_to_private`. An advantage of the new approach is that only 1 tx is required and the API of partial notes is generally nicer. For more information on partial notes refer to docs. ### \[Token contract] Function naming changes[​](#token-contract-function-naming-changes "Direct link to \[Token contract] Function naming changes") There have been a few naming changes done for improved consistency. These are the renamings: `transfer_public` --> `transfer_in_public` `transfer_from` --> `transfer_in_private` `mint_public` --> `mint_to_public` `burn` --> `burn_private` ## 0.62.0[​](#0620 "Direct link to 0.62.0") ### \[TXE] Single execution environment[​](#txe-single-execution-environment "Direct link to \[TXE] Single execution environment") Thanks to recent advancements in Brillig TXE performs every single call as if it was a nested call, spawning a new ACVM or AVM simulator without performance loss. This ensures every single test runs in a consistent environment and allows for clearer test syntax: ``` -let my_call_interface = MyContract::at(address).my_function(args); -env.call_private(my_contract_interface) +MyContract::at(address).my_function(args).call(&mut env.private()); ``` This implies every contract has to be deployed before it can be tested (via `env.deploy` or `env.deploy_self`) and of course it has to be recompiled if its code was changed before TXE can use the modified bytecode. ### Uniqueness of L1 to L2 messages[​](#uniqueness-of-l1-to-l2-messages "Direct link to Uniqueness of L1 to L2 messages") L1 to L2 messages have been updated to guarantee their uniqueness. This means that the hash of an L1 to L2 message cannot be precomputed, and must be obtained from the `MessageSent` event emitted by the `Inbox` contract, found in the L1 transaction receipt that inserted the message: ``` event MessageSent(uint256 indexed l2BlockNumber, uint256 index, bytes32 indexed hash); ``` This event now also includes an `index`. This index was previously required to consume an L1 to L2 message in a public function, and now it is also required for doing so in a private function, since it is part of the message hash preimage. The `PrivateContext` in aztec-nr has been updated to reflect this: ``` pub fn consume_l1_to_l2_message( &mut self, content: Field, secret: Field, sender: EthAddress, + leaf_index: Field, ) { ``` This change has also modified the internal structure of the archiver database, making it incompatible with previous ones. Last, the API for obtaining an L1 to L2 message membership witness has been simplified to leverage message uniqueness: ``` getL1ToL2MessageMembershipWitness( blockNumber: L2BlockNumber, l1ToL2Message: Fr, - startIndex: bigint, ): Promise<[bigint, SiblingPath] | undefined>; ``` ### Address is now a point[​](#address-is-now-a-point "Direct link to Address is now a point") The address now serves as someone's public key to encrypt incoming notes. An address point has a corresponding address secret, which is used to decrypt the notes encrypted with the address point. ### Notes no longer store a hash of the nullifier public keys, and now store addresses[​](#notes-no-longer-store-a-hash-of-the-nullifier-public-keys-and-now-store-addresses "Direct link to Notes no longer store a hash of the nullifier public keys, and now store addresses") Because of removing key rotation, we can now store addresses as the owner of a note. Because of this and the above change, we can and have removed the process of registering a recipient, because now we do not need any keys of the recipient. example\_note.nr ``` -npk_m_hash: Field +owner: AztecAddress ``` PXE Interface ``` -registerRecipient(completeAddress: CompleteAddress) ``` ## 0.58.0[​](#0580 "Direct link to 0.58.0") ### \[l1-contracts] Inbox's MessageSent event emits global tree index[​](#l1-contracts-inboxs-messagesent-event-emits-global-tree-index "Direct link to \[l1-contracts] Inbox's MessageSent event emits global tree index") Earlier `MessageSent` event in Inbox emitted a subtree index (index of the message in the subtree of the l2Block). But the nodes and Aztec.nr expects the index in the global L1\_TO\_L2\_MESSAGES\_TREE. So to make it easier to parse this, Inbox now emits this global index. ## 0.57.0[​](#0570 "Direct link to 0.57.0") ### Changes to PXE API and \`ContractFunctionInteraction\`\`[​](#changes-to-pxe-api-and-contractfunctioninteraction "Direct link to Changes to PXE API and `ContractFunctionInteraction``") PXE APIs have been refactored to better reflect the lifecycle of a Tx (`execute private -> simulate kernels -> simulate public (estimate gas) -> prove -> send`) * `.simulateTx`: Now returns a `TxSimulationResult`, containing the output of private execution, kernel simulation and public simulation (optional). * `.proveTx`: Now accepts the result of executing the private part of a transaction, so simulation doesn't have to happen again. Thanks to this refactor, `ContractFunctionInteraction` has been updated to remove its internal cache and avoid bugs due to its mutable nature. As a result our type-safe interfaces now have to be used as follows: ``` -const action = MyContract.at(address).method(args); -await action.prove(); -await action.send().wait(); +const action = MyContract.at(address).method(args); +const provenTx = await action.prove(); +await provenTx.send().wait(); ``` It's still possible to use `.send()` as before, which will perform proving under the hood. More changes are coming to these APIs to better support gas estimation mechanisms and advanced features. ### Changes to public calling convention[​](#changes-to-public-calling-convention "Direct link to Changes to public calling convention") Contracts that include public functions (that is, marked with `#[public]`), are required to have a function `public_dispatch(selector: Field)` which acts as an entry point. This will be soon the only public function registered/deployed in contracts. The calling convention is updated so that external calls are made to this function. If you are writing your contracts using Aztec-nr, there is nothing you need to change. The `public_dispatch` function is automatically generated by the `#[aztec]` macro. ### \[Aztec.nr] Renamed `unsafe_rand` to `random`[​](#aztecnr-renamed-unsafe_rand-to-random "Direct link to aztecnr-renamed-unsafe_rand-to-random") Since this is an `unconstrained` function, callers are already supposed to include an `unsafe` block, so this function has been renamed for reduced verbosity. ``` -use aztec::oracle::unsafe_rand::unsafe_rand; +use aztec::oracle::random::random; -let random_value = unsafe { unsafe_rand() }; +let random_value = unsafe { random() }; ``` ### \[Aztec.js] Removed `L2Block.fromFields`[​](#aztecjs-removed-l2blockfromfields "Direct link to aztecjs-removed-l2blockfromfields") `L2Block.fromFields` was a syntactic sugar which is causing [issues](https://github.com/AztecProtocol/aztec-packages/issues/8340) so we've removed it. ``` -const l2Block = L2Block.fromFields({ header, archive, body }); +const l2Block = new L2Block(archive, header, body); ``` ### \[Aztec.nr] Removed `SharedMutablePrivateGetter`[​](#aztecnr-removed-sharedmutableprivategetter "Direct link to aztecnr-removed-sharedmutableprivategetter") This state variable was deleted due to it being difficult to use safely. ### \[Aztec.nr] Changes to `NullifiableNote`[​](#aztecnr-changes-to-nullifiablenote "Direct link to aztecnr-changes-to-nullifiablenote") The `compute_nullifier_without_context` function is now `unconstrained`. It had always been meant to be called in unconstrained contexts (which is why it did not receive the `context` object), but now that Noir supports trait functions being `unconstrained` this can be implemented properly. Users must add the `unconstrained` keyword to their implementations of the trait: ``` impl NullifiableNote for MyCustomNote { - fn compute_nullifier_without_context(self) -> Field { + unconstrained fn compute_nullifier_without_context(self) -> Field { ``` ### \[Aztec.nr] Make `TestEnvironment` unconstrained[​](#aztecnr-make-testenvironment-unconstrained "Direct link to aztecnr-make-testenvironment-unconstrained") All of `TestEnvironment`'s functions are now `unconstrained`, preventing accidentally calling them in a constrained circuit, among other kinds of user error. Becuase they work with mutable references, and these are not allowed to cross the constrained/unconstrained barrier, tests that use `TestEnvironment` must also become `unconstrained`. The recommended practice is to make *all* Noir tests and test helper functions be \`unconstrained: ``` #[test] -fn test_my_function() { +unconstrained fn test_my_function() { let env = TestEnvironment::new(); ``` ### \[Aztec.nr] removed `encode_and_encrypt_note` and renamed `encode_and_encrypt_note_with_keys` to `encode_and_encrypt_note`[​](#aztecnr-removed-encode_and_encrypt_note-and-renamed-encode_and_encrypt_note_with_keys-to-encode_and_encrypt_note "Direct link to aztecnr-removed-encode_and_encrypt_note-and-renamed-encode_and_encrypt_note_with_keys-to-encode_and_encrypt_note") ``` contract XYZ { - use dep::aztec::encrypted_logs::encrypted_note_emission::encode_and_encrypt_note_with_keys; + use dep::aztec::encrypted_logs::encrypted_note_emission::encode_and_encrypt_note; ... - numbers.at(owner).initialize(&mut new_number).emit(encode_and_encrypt_note_with_keys(&mut context, owner_ovpk_m, owner_ivpk_m, owner)); + numbers.at(owner).initialize(&mut new_number).emit(encode_and_encrypt_note(&mut context, owner_ovpk_m, owner_ivpk_m, owner)); } ``` ## 0.56.0[​](#0560 "Direct link to 0.56.0") ### \[Aztec.nr] Changes to contract definition[​](#aztecnr-changes-to-contract-definition "Direct link to \[Aztec.nr] Changes to contract definition") We've migrated the Aztec macros to use the newly introduce meta programming Noir feature. Due to being Noir-based, the new macros are less obscure and can be more easily modified. As part of this transition, some changes need to be applied to Aztec contracts: * The top level `contract` block needs to have the `#[aztec]` macro applied to it. * All `#[aztec(name)]` macros are renamed to `#[name]`. * The storage struct (the one that gets the `#[storage]` macro applied) but be generic over a `Context` type, and all state variables receive this type as their last generic type parameter. ``` + use dep::aztec::macros::aztec; #[aztec] contract Token { + use dep::aztec::macros::{storage::storage, events::event, functions::{initializer, private, view, public}}; - #[aztec(storage)] - struct Storage { + #[storage] + struct Storage { - admin: PublicMutable, + admin: PublicMutable, - minters: Map>, + minters: Map, Context>, } - #[aztec(public)] - #[aztec(initializer)] + #[public] + #[initializer] fn constructor(admin: AztecAddress, name: str<31>, symbol: str<31>, decimals: u8) { ... } - #[aztec(public)] - #[aztec(view)] - fn public_get_name() -> FieldCompressedString { + #[public] + #[view] fn public_get_name() -> FieldCompressedString { ... } ``` ### \[Aztec.nr] Changes to `NoteInterface`[​](#aztecnr-changes-to-noteinterface "Direct link to aztecnr-changes-to-noteinterface") The new macro model prevents partial trait auto-implementation: they either implement the entire trait or none of it. This means users can no longer implement part of `NoteInterface` and have the rest be auto-implemented. For this reason we've separated the methods which are auto-implemented and those which needs to be implemented manually into two separate traits: the auto-implemented ones stay in the `NoteInterface` trace and the manually implemented ones were moved to `NullifiableNote` (name likely to change): ``` -#[aztec(note)] +#[note] struct AddressNote { ... } -impl NoteInterface for AddressNote { +impl NullifiableNote for AddressNote { fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { ... } fn compute_nullifier_without_context(self) -> Field { ... } } ``` ### \[Aztec.nr] Changes to contract interface[​](#aztecnr-changes-to-contract-interface "Direct link to \[Aztec.nr] Changes to contract interface") The `Contract::storage()` static method has been renamed to `Contract::storage_layout()`. ``` - let fee_payer_balances_slot = derive_storage_slot_in_map(Token::storage().balances.slot, fee_payer); - let user_balances_slot = derive_storage_slot_in_map(Token::storage().balances.slot, user); + let fee_payer_balances_slot = derive_storage_slot_in_map(Token::storage_layout().balances.slot, fee_payer); + let user_balances_slot = derive_storage_slot_in_map(Token::storage_layout().balances.slot, user); ``` ### Key rotation removed[​](#key-rotation-removed "Direct link to Key rotation removed") The ability to rotate incoming, outgoing, nullifying and tagging keys has been removed - this feature was easy to misuse and not worth the complexity and gate count cost. As part of this, the Key Registry contract has also been deleted. The API for fetching public keys has been adjusted accordingly: ``` - let keys = get_current_public_keys(&mut context, account); + let keys = get_public_keys(account); ``` ### \[Aztec.nr] Rework `NoteGetterOptions::select`[​](#aztecnr-rework-notegetteroptionsselect "Direct link to aztecnr-rework-notegetteroptionsselect") The `select` function in both `NoteGetterOptions` and `NoteViewerOptions` no longer takes an `Option` of a comparator, but instead requires an explicit comparator to be passed. Additionally, the order of the parameters has been changed so that they are `(lhs, operator, rhs)`. These two changes should make invocations of the function easier to read: ``` - options.select(ValueNote::properties().value, amount, Option::none()) + options.select(ValueNote::properties().value, Comparator.EQ, amount) ``` ## 0.53.0[​](#0530 "Direct link to 0.53.0") ### \[Aztec.nr] Remove `OwnedNote` and create `UintNote`[​](#aztecnr-remove-ownednote-and-create-uintnote "Direct link to aztecnr-remove-ownednote-and-create-uintnote") `OwnedNote` allowed having a U128 `value` in the custom note while `ValueNote` restricted to just a Field. We have removed `OwnedNote` but are introducing a more genric `UintNote` within aztec.nr ``` #[aztec(note)] struct UintNote { // The integer stored by the note value: U128, // The nullifying public key hash is used with the nsk_app to ensure that the note can be privately spent. npk_m_hash: Field, // Randomness of the note to hide its contents randomness: Field, } ``` ### \[TXE] logging[​](#txe-logging "Direct link to \[TXE] logging") You can now use `debug_log()` within your contract to print logs when using the TXE Remember to set the following environment variables to activate debug logging: ``` export DEBUG="aztec:*" export LOG_LEVEL="debug" ``` ### \[Account] no assert in is\_valid\_impl[​](#account-no-assert-in-is_valid_impl "Direct link to \[Account] no assert in is_valid_impl") `is_valid_impl` method in account contract asserted if signature was true. Instead now we will return the verification to give flexibility to developers to handle it as they please. ``` - let verification = std::ecdsa_secp256k1::verify_signature(public_key.x, public_key.y, signature, hashed_message); - assert(verification == true); - true + std::ecdsa_secp256k1::verify_signature(public_key.x, public_key.y, signature, hashed_message) ``` ## 0.49.0[​](#0490 "Direct link to 0.49.0") ### Key Rotation API overhaul[​](#key-rotation-api-overhaul "Direct link to Key Rotation API overhaul") Public keys (ivpk, ovpk, npk, tpk) should no longer be fetched using the old `get_[x]pk_m` methods on the `Header` struct, but rather by calling `get_current_public_keys`, which returns a `PublicKeys` struct with all four keys at once: ``` +use dep::aztec::keys::getters::get_current_public_keys; -let header = context.header(); -let owner_ivpk_m = header.get_ivpk_m(&mut context, owner); -let owner_ovpk_m = header.get_ovpk_m(&mut context, owner); +let owner_keys = get_current_public_keys(&mut context, owner); +let owner_ivpk_m = owner_keys.ivpk_m; +let owner_ovpk_m = owner_keys.ovpk_m; ``` If using more than one key per account, this will result in very large circuit gate count reductions. Additionally, `get_historical_public_keys` was added to support reading historical keys using a historical header: ``` +use dep::aztec::keys::getters::get_historical_public_keys; let historical_header = context.header_at(some_block_number); -let owner_ivpk_m = header.get_ivpk_m(&mut context, owner); -let owner_ovpk_m = header.get_ovpk_m(&mut context, owner); +let owner_keys = get_historical_public_keys(historical_header, owner); +let owner_ivpk_m = owner_keys.ivpk_m; +let owner_ovpk_m = owner_keys.ovpk_m; ``` ## 0.48.0[​](#0480 "Direct link to 0.48.0") ### NoteInterface changes[​](#noteinterface-changes "Direct link to NoteInterface changes") `compute_note_hash_and_nullifier*` functions were renamed as `compute_nullifier*` and the `compute_nullifier` function now takes `note_hash_for_nullify` as an argument (this allowed us to reduce gate counts and the hash was typically computed before). Also `compute_note_hash_for_consumption` function was renamed as `compute_note_hash_for_nullification`. ``` impl NoteInterface for ValueNote { - fn compute_note_hash_and_nullifier(self, context: &mut PrivateContext) -> (Field, Field) { - let note_hash_for_nullify = compute_note_hash_for_consumption(self); - let secret = context.request_nsk_app(self.npk_m_hash); - let nullifier = poseidon2_hash_with_separator([ - note_hash_for_nullify, - secret, - ], - DOM_SEP__NOTE_NULLIFIER as Field, - ); - (note_hash_for_nullify, nullifier) - } - fn compute_note_hash_and_nullifier_without_context(self) -> (Field, Field) { - let note_hash_for_nullify = compute_note_hash_for_consumption(self); - let secret = get_nsk_app(self.npk_m_hash); - let nullifier = poseidon2_hash_with_separator([ - note_hash_for_nullify, - secret, - ], - DOM_SEP__NOTE_NULLIFIER as Field, - ); - (note_hash_for_nullify, nullifier) - } + fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { + let secret = context.request_nsk_app(self.npk_m_hash); + poseidon2_hash_with_separator([ + note_hash_for_nullify, + secret + ], + DOM_SEP__NOTE_NULLIFIER as Field, + ) + } + fn compute_nullifier_without_context(self) -> Field { + let note_hash_for_nullify = compute_note_hash_for_nullification(self); + let secret = get_nsk_app(self.npk_m_hash); + poseidon2_hash_with_separator([ + note_hash_for_nullify, + secret, + ], + DOM_SEP__NOTE_NULLIFIER as Field, + ) + } } ``` ### Fee Juice rename[​](#fee-juice-rename "Direct link to Fee Juice rename") The name of the canonical Gas contract has changed to Fee Juice. Update noir code: ``` -GasToken::at(contract_address) +FeeJuice::at(contract_address) ``` Additionally, `NativePaymentMethod` and `NativePaymentMethodWithClaim` have been renamed to `FeeJuicePaymentMethod` and `FeeJuicePaymentMethodWithClaim`. ### PrivateSet::pop\_notes(...)[​](#privatesetpop_notes "Direct link to PrivateSet::pop_notes(...)") The most common flow when working with notes is obtaining them from a `PrivateSet` via `get_notes(...)` and then removing them via `PrivateSet::remove(...)`. This is cumbersome and it results in unnecessary constraints due to a redundant note read request checks in the remove function. For this reason we've implemented `pop_notes(...)` which gets the notes, removes them from the set and returns them. This tight coupling of getting notes and removing them allowed us to safely remove the redundant read request check. Token contract diff: ``` -let options = NoteGetterOptions::with_filter(filter_notes_min_sum, target_amount).set_limit(max_notes); -let notes = self.map.at(owner).get_notes(options); -let mut subtracted = U128::from_integer(0); -for i in 0..options.limit { - if i < notes.len() { - let note = notes.get_unchecked(i); - self.map.at(owner).remove(note); - subtracted = subtracted + note.get_amount(); - } -} -assert(minuend >= subtrahend, "Balance too low"); +let options = NoteGetterOptions::with_filter(filter_notes_min_sum, target_amount).set_limit(max_notes); +let notes = self.map.at(owner).pop_notes(options); +let mut subtracted = U128::from_integer(0); +for i in 0..options.limit { + if i < notes.len() { + let note = notes.get_unchecked(i); + subtracted = subtracted + note.get_amount(); + } +} +assert(minuend >= subtrahend, "Balance too low"); ``` Note that `pop_notes` may not have obtained and removed any notes! The caller must place checks on the returned notes, e.g. in the example above by checking a sum of balances, or by checking the number of returned notes (`assert_eq(notes.len(), expected_num_notes)`). ## 0.47.0[​](#0470 "Direct link to 0.47.0") # \[Aztec sandbox] TXE deployment changes The way simulated deployments are done in TXE tests has changed to avoid relying on TS interfaces. It is now possible to do it by directly pointing to a Noir standalone contract or workspace: ``` -let deployer = env.deploy("path_to_contract_ts_interface"); +let deployer = env.deploy("path_to_contract_root_folder_where_nargo_toml_is", "ContractName"); ``` Extended syntax for more use cases: ``` // The contract we're testing env.deploy_self("ContractName"); // We have to provide ContractName since nargo isn't ready to support multi-contract files // A contract in a workspace env.deploy("../path/to/workspace@package_name", "ContractName"); // This format allows locating the artifact in the root workspace target folder, regardless of internal code organization ``` The deploy function returns a `Deployer`, which requires performing a subsequent call to `without_initializer()`, `with_private_initializer()` or `with_public_initializer()` just like before in order to **actually** deploy the contract. ### \[CLI] Command refactor and unification + `aztec test`[​](#cli-command-refactor-and-unification--aztec-test "Direct link to cli-command-refactor-and-unification--aztec-test") Sandbox commands have been cleaned up and simplified. Doing `aztec-up` now gets you the following top-level commands: `aztec`: All the previous commands + all the CLI ones without having to prefix them with cli. Run `aztec` for help! `aztec-nargo`: No changes **REMOVED/RENAMED**: * `aztec-sandbox` and `aztec sandbox`: now `aztec start --sandbox` * `aztec-builder`: now `aztec codegen` and `aztec update` **ADDED**: * `aztec test [options]`: runs `aztec start --txe && aztec-nargo test --oracle-resolver http://aztec:8081 --silence-warnings [options]` via docker-compose allowing users to easily run contract tests using TXE ## 0.45.0[​](#0450 "Direct link to 0.45.0") ### \[Aztec.nr] Remove unencrypted logs from private[​](#aztecnr-remove-unencrypted-logs-from-private "Direct link to \[Aztec.nr] Remove unencrypted logs from private") They leak privacy so is a footgun! ## 0.44.0[​](#0440 "Direct link to 0.44.0") ### \[Aztec.nr] Autogenerate Serialize methods for events[​](#aztecnr-autogenerate-serialize-methods-for-events "Direct link to \[Aztec.nr] Autogenerate Serialize methods for events") ``` #[aztec(event)] struct WithdrawalProcessed { who: Field, amount: Field, } -impl Serialize<2> for WithdrawalProcessed { - fn serialize(self: Self) -> [Field; 2] { - [self.who.to_field(), self.amount as Field] - } } ``` ### \[Aztec.nr] rename `encode_and_encrypt_with_keys` to `encode_and_encrypt_note_with_keys`[​](#aztecnr-rename-encode_and_encrypt_with_keys-to-encode_and_encrypt_note_with_keys "Direct link to aztecnr-rename-encode_and_encrypt_with_keys-to-encode_and_encrypt_note_with_keys") ``` contract XYZ { - use dep::aztec::encrypted_logs::encrypted_note_emission::encode_and_encrypt_with_keys; + use dep::aztec::encrypted_logs::encrypted_note_emission::encode_and_encrypt_note_with_keys; .... - numbers.at(owner).initialize(&mut new_number).emit(encode_and_encrypt_with_keys(&mut context, owner_ovpk_m, owner_ivpk_m)); + numbers.at(owner).initialize(&mut new_number).emit(encode_and_encrypt_note_with_keys(&mut context, owner_ovpk_m, owner_ivpk_m)); } ``` ### \[Aztec.nr] changes to `NoteInterface`[​](#aztecnr-changes-to-noteinterface-1 "Direct link to aztecnr-changes-to-noteinterface-1") `compute_nullifier` function was renamed to `compute_note_hash_and_nullifier` and now the function has to return not only the nullifier but also the note hash used to compute the nullifier. The same change was done to `compute_nullifier_without_context` function. These changes were done because having the note hash exposed allowed us to not having to re-compute it again in `destroy_note` function of Aztec.nr which led to significant decrease in gate counts (see the [optimization PR](https://github.com/AztecProtocol/aztec-packages/pull/7103) for more details). ``` - impl NoteInterface for ValueNote { - fn compute_nullifier(self, context: &mut PrivateContext) -> Field { - let note_hash_for_nullify = compute_note_hash_for_consumption(self); - let secret = context.request_nsk_app(self.npk_m_hash); - poseidon2_hash([ - note_hash_for_nullify, - secret, - DOM_SEP__NOTE_NULLIFIER as Field, - ]) - } - - fn compute_nullifier_without_context(self) -> Field { - let note_hash_for_nullify = compute_note_hash_for_consumption(self); - let secret = get_nsk_app(self.npk_m_hash); - poseidon2_hash([ - note_hash_for_nullify, - secret, - DOM_SEP__NOTE_NULLIFIER as Field, - ]) - } - } + impl NoteInterface for ValueNote { + fn compute_note_hash_and_nullifier(self, context: &mut PrivateContext) -> (Field, Field) { + let note_hash_for_nullify = compute_note_hash_for_consumption(self); + let secret = context.request_nsk_app(self.npk_m_hash); + let nullifier = poseidon2_hash([ + note_hash_for_nullify, + secret, + DOM_SEP__NOTE_NULLIFIER as Field, + ]); + (note_hash_for_nullify, nullifier) + } + + fn compute_note_hash_and_nullifier_without_context(self) -> (Field, Field) { + let note_hash_for_nullify = compute_note_hash_for_consumption(self); + let secret = get_nsk_app(self.npk_m_hash); + let nullifier = poseidon2_hash([ + note_hash_for_nullify, + secret, + DOM_SEP__NOTE_NULLIFIER as Field, + ]); + (note_hash_for_nullify, nullifier) + } + } ``` ### \[Aztec.nr] `note_getter` returns `BoundedVec`[​](#aztecnr-note_getter-returns-boundedvec "Direct link to aztecnr-note_getter-returns-boundedvec") The `get_notes` and `view_notes` function no longer return an array of options (i.e. `[Option, N_NOTES]`) but instead a `BoundedVec`. This better conveys the useful property the old array had of having all notes collapsed at the beginning of the array, which allows for powerful optimizations and gate count reduction when setting the `options.limit` value. A `BoundedVec` has a `max_len()`, which equals the number of elements it can hold, and a `len()`, which equals the number of elements it currently holds. Since `len()` is typically not knwon at compile time, iterating over a `BoundedVec` looks slightly different than iterating over an array of options: ``` - let option_notes = get_notes(options); - for i in 0..option_notes.len() { - if option_notes[i].is_some() { - let note = option_notes[i].unwrap_unchecked(); - } - } + let notes = get_notes(options); + for i in 0..notes.max_len() { + if i < notes.len() { + let note = notes.get_unchecked(i); + } + } ``` To further reduce gate count, you can iterate over `options.limit` instead of `max_len()`, since `options.limit` is guaranteed to be larger or equal to `len()`, and smaller or equal to `max_len()`: ``` - for i in 0..notes.max_len() { + for i in 0..options.limit { ``` ### \[Aztec.nr] static private authwit[​](#aztecnr-static-private-authwit "Direct link to \[Aztec.nr] static private authwit") The private authwit validation is now making a static call to the account contract instead of passing over control flow. This is to ensure that it cannot be used for re-entry. To make this change however, we cannot allow emitting a nullifier from the account contract, since that would break the static call. Instead, we will be changing the `spend_private_authwit` to a `verify_private_authwit` and in the `auth` library emit the nullifier. This means that the "calling" contract will now be emitting the nullifier, and not the account. For example, for a token contract, the nullifier is now emitted by the token contract. However, as this is done inside the `auth` library, the token contract doesn't need to change much. The biggest difference is related to "cancelling" an authwit. Since it is no longer in the account contract, you cannot just emit a nullifier from it anymore. Instead it must rely on the token contract providing functionality for cancelling. There are also a few general changes to how authwits are generated, namely to more easily support the data required for a validity lookup now. Previously we could lookup the `message_hash` directly at the account contract, now we instead need to use the `inner_hash` and the contract of the consumer to figure out if it have already been emitted. A minor extension have been made to the authwit creations to make it easier to sign a specific a hash with a specific caller, e.g., the `inner_hash` can be provided as `{consumer, inner_hash}` to the `createAuthWit` where it previously needed to do a couple of manual steps to compute the outer hash. The `computeOuterAuthWitHash` have been made internal and the `computeAuthWitMessageHash` can instead be used to compute the values similarly to other authwit computations. ``` const innerHash = computeInnerAuthWitHash([Fr.ZERO, functionSelector.toField(), entrypointPackedArgs.hash]); -const outerHash = computeOuterAuthWitHash( - this.dappEntrypointAddress, - new Fr(this.chainId), - new Fr(this.version), - innerHash, -); +const messageHash = computeAuthWitMessageHash( + { consumer: this.dappEntrypointAddress, innerHash }, + { chainId: new Fr(this.chainId), version: new Fr(this.version) }, +); ``` If the wallet is used to compute the authwit, it will populate the chain id and version instead of requiring it to be provided by tha actor. ``` const innerHash = computeInnerAuthWitHash([Fr.fromString('0xdead')]); -const outerHash = computeOuterAuthWitHash(wallets[1].getAddress(), chainId, version, innerHash); -const witness = await wallets[0].createAuthWit(outerHash); + const witness = await wallets[0].createAuthWit({ comsumer: accounts[1].address, inner_hash }); ``` ## 0.43.0[​](#0430 "Direct link to 0.43.0") ### \[Aztec.nr] break `token.transfer()` into `transfer` and `transferFrom`[​](#aztecnr-break-tokentransfer-into-transfer-and-transferfrom "Direct link to aztecnr-break-tokentransfer-into-transfer-and-transferfrom") Earlier we had just one function - `transfer()` which used authwits to handle the case where a contract/user wants to transfer funds on behalf of another user. To reduce circuit sizes and proof times, we are breaking up `transfer` and introducing a dedicated `transferFrom()` function like in the ERC20 standard. ### \[Aztec.nr] `options.limit` has to be constant[​](#aztecnr-optionslimit-has-to-be-constant "Direct link to aztecnr-optionslimit-has-to-be-constant") The `limit` parameter in `NoteGetterOptions` and `NoteViewerOptions` is now required to be a compile-time constant. This allows performing loops over this value, which leads to reduced circuit gate counts when setting a `limit` value. ### \[Aztec.nr] canonical public authwit registry[​](#aztecnr-canonical-public-authwit-registry "Direct link to \[Aztec.nr] canonical public authwit registry") The public authwits are moved into a shared registry (auth registry) to make it easier for sequencers to approve for their non-revertible (setup phase) whitelist. Previously, it was possible to DOS a sequencer by having a very expensive authwit validation that fails at the end, now the whitelist simply need the registry. Notable, this means that consuming a public authwit will no longer emit a nullifier in the account contract but instead update STORAGE in the public domain. This means that there is a larger difference between private and public again. However, it also means that if contracts need to approve, and use the approval in the same tx, it is transient and don't need to go to DA (saving 96 bytes). For the typescript wallets this is handled so the APIs don't change, but account contracts should get rid of their current setup with `approved_actions`. ``` - let actions = AccountActions::init(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); + let actions = AccountActions::init(&mut context, is_valid_impl); ``` For contracts we have added a `set_authorized` function in the auth library that can be used to set values in the registry. ``` - storage.approved_action.at(message_hash).write(true); + set_authorized(&mut context, message_hash, true); ``` ### \[Aztec.nr] emit encrypted logs[​](#aztecnr-emit-encrypted-logs "Direct link to \[Aztec.nr] emit encrypted logs") Emitting or broadcasting encrypted notes are no longer done as part of the note creation, but must explicitly be either emitted or discarded instead. ``` + use dep::aztec::encrypted_logs::encrypted_note_emission::{encode_and_encrypt, encode_and_encrypt_with_keys}; - storage.balances.sub(from, amount); + storage.balances.sub(from, amount).emit(encode_and_encrypt_with_keys(&mut context, from, from)); + storage.balances.sub(from, amount).emit(encode_and_encrypt_with_keys(&mut context, from_ovpk, from_ivpk)); + storage.balances.sub(from, amount).discard(); ``` ## 0.42.0[​](#0420 "Direct link to 0.42.0") ### \[Aztec.nr] Unconstrained Context[​](#aztecnr-unconstrained-context "Direct link to \[Aztec.nr] Unconstrained Context") Top-level unconstrained execution is now marked by the new `UnconstrainedContext`, which provides access to the block number and contract address being used in the simulation. Any custom state variables that provided unconstrained functions should update their specialization parameter: ``` + use dep::aztec::context::UnconstrainedContext; - impl MyStateVariable<()> { + impl MyStateVariable { ``` ### \[Aztec.nr] Filtering is now constrained[​](#aztecnr-filtering-is-now-constrained "Direct link to \[Aztec.nr] Filtering is now constrained") The `filter` argument of `NoteGetterOptions` (typically passed via the `with_filter()` function) is now applied in a constraining environment, meaning any assertions made during the filtering are guaranteed to hold. This mirrors the behavior of the `select()` function. ### \[Aztec.nr] Emitting encrypted notes and logs[​](#aztecnr-emitting-encrypted-notes-and-logs "Direct link to \[Aztec.nr] Emitting encrypted notes and logs") The `emit_encrypted_log` context function is now `encrypt_and_emit_log` or `encrypt_and_emit_note`. ``` - context.emit_encrypted_log(log1); + context.encrypt_and_emit_log(log1); + context.encrypt_and_emit_note(note1); ``` Broadcasting a note will call `encrypt_and_emit_note` in the background. To broadcast a generic event, use `encrypt_and_emit_log` with the same encryption parameters as notes require. Currently, only fields and arrays of fields are supported as events. By default, logs emitted via `encrypt_and_emit_log` will be siloed with a *masked* contract address. To force the contract address to be revealed, so everyone can check it rather than just the log recipient, provide `randomness = 0`. ## Public execution migrated to the Aztec Virtual Machine[​](#public-execution-migrated-to-the-aztec-virtual-machine "Direct link to Public execution migrated to the Aztec Virtual Machine") **What does this mean for me?** It should be mostly transparent, with a few caveats: * Not all Noir blackbox functions are supported by the AVM. Only `Sha256`, `PedersenHash`, `Poseidon2Permutation`, `Keccak256`, and `ToRadix` are supported. * For public functions, `context.nullifier_exists(...)` will now also consider pending nullifiers. * The following methods of `PublicContext` are not supported anymore: `fee_recipient`, `fee_per_da_gas`, `fee_per_l2_gas`, `call_public_function_no_args`, `static_call_public_function_no_args`, `delegate_call_public_function_no_args`, `call_public_function_with_packed_args`, `set_return_hash`, `finish`. However, in terms of functionality, the new context's interface should be equivalent (unless otherwise specified in this list). * Delegate calls are not yet supported in the AVM. * If you have types with custom serialization that you use across external contracts calls, you might need to modify its serialization to match how Noir would serialize it. This is a known problem unrelated to the AVM, but triggered more often when using it. * A few error messages might change format, so you might need to change your test assertions. **Internal details** Before this change, public bytecode was executed using the same simulator as in private: the ACIR simulator (and internally, the Brillig VM). On the Aztec.nr side, public functions accessed the context through `PublicContext`. After this change, public bytecode will be run using the AVM simulator (the simulator for our upcoming zkVM). This bytecode is generated from Noir contracts in two steps: First, `nargo compile` produces an artifact which has Brillig bytecode for public functions, just as it did before. Second: the `avm-transpiler` takes that artifact, and it transpiles Brillig bytecode to AVM bytecode. This final artifact can now be deployed and used with the new public runtime. On the Aztec.nr side, public functions keep accessing the context using `PublicContext` but the underlying implementation is switch with what formerly was the `AvmContext`. ## 0.41.0[​](#0410 "Direct link to 0.41.0") ### \[Aztec.nr] State variable rework[​](#aztecnr-state-variable-rework "Direct link to \[Aztec.nr] State variable rework") Aztec.nr state variables have been reworked so that calling private functions in public and vice versa is detected as an error during compilation instead of at runtime. This affects users in a number of ways: #### New compile time errors[​](#new-compile-time-errors "Direct link to New compile time errors") It used to be that calling a state variable method only available in public from a private function resulted in obscure runtime errors in the form of a failed `_is_some` assertion. Incorrect usage of the state variable methods now results in compile time errors. For example, given the following function: ``` #[aztec(public)] fn get_decimals() -> pub u8 { storage.decimals.read_private() } ``` The compiler will now error out with ``` Expected type SharedImmutable<_, &mut PrivateContext>, found type SharedImmutable ``` The key component is the second generic parameter: the compiler expects a `PrivateContext` (becuse `read_private` is only available during private execution), but a `PublicContext` is being used instead (because of the `#[aztec(public)]` attribute). #### Generic parameters in `Storage`[​](#generic-parameters-in-storage "Direct link to generic-parameters-in-storage") The `Storage` struct (the one marked with `#[aztec(storage)]`) should now be generic over a `Context` type, which matches the new generic parameter of all Aztec.nr libraries. This parameter is always the last generic parameter. This means that, without any additional features, we'd end up with some extra boilerplate when declaring this struct: ``` #[aztec(storage)] - struct Storage { + struct Storage { - nonce_for_burn_approval: PublicMutable, + nonce_for_burn_approval: PublicMutable, - portal_address: SharedImmutable, + portal_address: SharedImmutable, - approved_action: Map>, + approved_action: Map, Context>, } ``` Because of this, the `#[aztec(storage)]` macro has been updated to **automatically inject** this `Context` generic parameter. The storage declaration does not require any changes. #### Removal of `Context`[​](#removal-of-context "Direct link to removal-of-context") The `Context` type no longer exists. End users typically didn't use it, but if imported it needs to be deleted. ### \[Aztec.nr] View functions and interface navigation[​](#aztecnr-view-functions-and-interface-navigation "Direct link to \[Aztec.nr] View functions and interface navigation") It is now possible to explicitly state a function doesn't perform any state alterations (including storage, logs, nullifiers and/or messages from L2 to L1) with the `#[aztec(view)]` attribute, similarly to solidity's `view` function modifier. ``` #[aztec(public)] + #[aztec(view)] fn get_price(asset_id: Field) -> Asset { storage.assets.at(asset_id).read() } ``` View functions only generate a `StaticCallInterface` that doesn't include `.call` or `.enqueue` methods. Also, the denomination `static` has been completely removed from the interfaces, in favor of the more familiar `view` ``` - let price = PriceFeed::at(asset.oracle).get_price(0).static_call(&mut context).price; + let price = PriceFeed::at(asset.oracle).get_price(0).view(&mut context).price; ``` ``` #[aztec(private)] fn enqueue_public_get_value_from_child(target_contract: AztecAddress, value: Field) { - StaticChild::at(target_contract).pub_get_value(value).static_enqueue(&mut context); + StaticChild::at(target_contract).pub_get_value(value).enqueue_view(&mut context); } ``` Additionally, the Noir LSP will now honor "go to definitions" requests for contract interfaces (Ctrl+click), taking the user to the original function implementation. ### \[Aztec.js] Simulate changes[​](#aztecjs-simulate-changes "Direct link to \[Aztec.js] Simulate changes") * `.simulate()` now tracks closer the process performed by `.send().wait()`, specifically going through the account contract entrypoint instead of directly calling the intended function. * `wallet.viewTx(...)` has been renamed to `wallet.simulateUnconstrained(...)` to better clarify what it does. ### \[Aztec.nr] Keys: Token note now stores an owner master nullifying public key hash instead of an owner address[​](#aztecnr-keys-token-note-now-stores-an-owner-master-nullifying-public-key-hash-instead-of-an-owner-address "Direct link to \[Aztec.nr] Keys: Token note now stores an owner master nullifying public key hash instead of an owner address") i.e. ``` struct TokenNote { amount: U128, - owner: AztecAddress, + npk_m_hash: Field, randomness: Field, } ``` Creating a token note and adding it to storage now looks like this: ``` - let mut note = ValueNote::new(new_value, owner); - storage.a_private_value.insert(&mut note, true); + let owner_npk_m_hash = get_npk_m_hash(&mut context, owner); + let owner_ivpk_m = get_ivpk_m(&mut context, owner); + let mut note = ValueNote::new(new_value, owner_npk_m_hash); + storage.a_private_value.insert(&mut note, true, owner_ivpk_m); ``` Computing the nullifier similarly changes to use this master nullifying public key hash. ## 0.40.0[​](#0400 "Direct link to 0.40.0") ### \[Aztec.nr] Debug logging[​](#aztecnr-debug-logging "Direct link to \[Aztec.nr] Debug logging") The function `debug_log_array_with_prefix` has been removed. Use `debug_log_format` with `{}` instead. The special sequence `{}` will be replaced with the whole array. You can also use `{0}`, `{1}`, ... as usual with `debug_log_format`. ``` - debug_log_array_with_prefix("Prefix", my_array); + debug_log_format("Prefix {}", my_array); ``` ## 0.39.0[​](#0390 "Direct link to 0.39.0") ### \[Aztec.nr] Mutable delays in `SharedMutable`[​](#aztecnr-mutable-delays-in-sharedmutable "Direct link to aztecnr-mutable-delays-in-sharedmutable") The type signature for `SharedMutable` changed from `SharedMutable` to `SharedMutable`. The behavior is the same as before, except the delay can now be changed after deployment by calling `schedule_delay_change`. ### \[Aztec.nr] get\_public\_key oracle replaced with get\_ivpk\_m[​](#aztecnr-get_public_key-oracle-replaced-with-get_ivpk_m "Direct link to \[Aztec.nr] get_public_key oracle replaced with get_ivpk_m") When implementing changes according to a new key scheme we had to change oracles. What used to be called encryption public key is now master incoming viewing public key. ``` - use dep::aztec::oracles::get_public_key::get_public_key; + use dep::aztec::keys::getters::get_ivpk_m; - let encryption_pub_key = get_public_key(self.owner); + let ivpk_m = get_ivpk_m(context, self.owner); ``` ## 0.38.0[​](#0380 "Direct link to 0.38.0") ### \[Aztec.nr] Emitting encrypted logs[​](#aztecnr-emitting-encrypted-logs "Direct link to \[Aztec.nr] Emitting encrypted logs") The `emit_encrypted_log` function is now a context method. ``` - use dep::aztec::log::emit_encrypted_log; - use dep::aztec::logs::emit_encrypted_log; - emit_encrypted_log(context, log1); + context.emit_encrypted_log(log1); ``` ## 0.36.0[​](#0360 "Direct link to 0.36.0") ### `FieldNote` removed[​](#fieldnote-removed "Direct link to fieldnote-removed") `FieldNote` only existed for testing purposes, and was not a note type that should be used in any real application. Its name unfortunately led users to think that it was a note type suitable to store a `Field` value, which it wasn't. If using `FieldNote`, you most likely want to use `ValueNote` instead, which has both randomness for privacy and an owner for proper nullification. ### `SlowUpdatesTree` replaced for `SharedMutable`[​](#slowupdatestree-replaced-for-sharedmutable "Direct link to slowupdatestree-replaced-for-sharedmutable") The old `SlowUpdatesTree` contract and libraries have been removed from the codebase, use the new `SharedMutable` library instead. This will require that you add a global variable specifying a delay in blocks for updates, and replace the slow updates tree state variable with `SharedMutable` variables. ``` + global CHANGE_ROLES_DELAY_BLOCKS = 5; struct Storage { - slow_update: SharedImmutable, + roles: Map>, } ``` Reading from `SharedMutable` is much simpler, all that's required is to call `get_current_value_in_public` or `get_current_value_in_private`, depending on the domain. ``` - let caller_roles = UserFlags::new(U128::from_integer(slow.read_at_pub(context.msg_sender().to_field()).call(&mut context))); + let caller_roles = storage.roles.at(context.msg_sender()).get_current_value_in_public(); ``` Finally, you can remove all capsule usage on the client code or tests, since those are no longer required when working with `SharedMutable`. ### \[Aztec.nr & js] Portal addresses[​](#aztecnr--js-portal-addresses "Direct link to \[Aztec.nr & js] Portal addresses") Deployments have been modified. No longer are portal addresses treated as a special class, being immutably set on creation of a contract. They are no longer passed in differently compared to the other variables and instead should be implemented using usual storage by those who require it. One should use the storage that matches the usecase - likely shared storage to support private and public. This means that you will likely add the portal as a constructor argument ``` - fn constructor(token: AztecAddress) { - storage.token.write(token); - } + struct Storage { ... + portal_address: SharedImmutable, + } + fn constructor(token: AztecAddress, portal_address: EthAddress) { + storage.token.write(token); + storage.portal_address.initialize(portal_address); + } ``` And read it from storage whenever needed instead of from the context. ``` - context.this_portal_address(), + storage.portal_address.read_public(), ``` ### \[Aztec.nr] Oracles[​](#aztecnr-oracles "Direct link to \[Aztec.nr] Oracles") Oracle `get_nullifier_secret_key` was renamed to `get_app_nullifier_secret_key` and `request_nullifier_secret_key` function on PrivateContext was renamed as `request_app_nullifier_secret_key`. ``` - let secret = get_nullifier_secret_key(self.owner); + let secret = get_app_nullifier_secret_key(self.owner); ``` ``` - let secret = context.request_nullifier_secret_key(self.owner); + let secret = context.request_app_nullifier_secret_key(self.owner); ``` ### \[Aztec.nr] Contract interfaces[​](#aztecnr-contract-interfaces "Direct link to \[Aztec.nr] Contract interfaces") It is now possible to import contracts on another contracts and use their automatic interfaces to perform calls. The interfaces have the same name as the contract, and are automatically exported. Parameters are automatically serialized (using the `Serialize` trait) and return values are automatically deserialized (using the `Deserialize` trait). Serialize and Deserialize methods have to conform to the standard ACVM serialization schema for the interface to work! 1. Only fixed length types are supported 2. All numeric types become Fields 3. Strings become arrays of Fields, one per char 4. Arrays become arrays of Fields following rules 2 and 3 5. Structs become arrays of Fields, with every item defined in the same order as they are in Noir code, following rules 2, 3, 4 and 5 (recursive) ``` - context.call_public_function( - storage.gas_token_address.read_private(), - FunctionSelector::from_signature("pay_fee(Field)"), - [42] - ); - - context.call_public_function( - storage.gas_token_address.read_private(), - FunctionSelector::from_signature("pay_fee(Field)"), - [42] - ); - - let _ = context.call_private_function( - storage.subscription_token_address.read_private(), - FunctionSelector::from_signature("transfer((Field),(Field),Field,Field)"), - [ - context.msg_sender().to_field(), - storage.subscription_recipient_address.read_private().to_field(), - storage.subscription_price.read_private(), - nonce - ] - ); + use dep::gas_token::GasToken; + use dep::token::Token; + + ... + // Public call from public land + GasToken::at(storage.gas_token_address.read_private()).pay_fee(42).call(&mut context); + // Public call from private land + GasToken::at(storage.gas_token_address.read_private()).pay_fee(42).enqueue(&mut context); + // Private call from private land + Token::at(asset).transfer(context.msg_sender(), storage.subscription_recipient_address.read_private(), amount, nonce).call(&mut context); ``` It is also possible to use these automatic interfaces from the local contract, and thus enqueue public calls from private without having to rely on low level `context` calls. ### \[Aztec.nr] Rename max block number setter[​](#aztecnr-rename-max-block-number-setter "Direct link to \[Aztec.nr] Rename max block number setter") The `request_max_block_number` function has been renamed to `set_tx_max_block_number` to better reflect that it is not a getter, and that the setting is transaction-wide. ``` - context.request_max_block_number(value); + context.set_tx_max_block_number(value); ``` ### \[Aztec.nr] Get portal address[​](#aztecnr-get-portal-address "Direct link to \[Aztec.nr] Get portal address") The `get_portal_address` oracle was removed. If you need to get the portal address of SomeContract, add the following methods to it ``` #[aztec(private)] fn get_portal_address() -> EthAddress { context.this_portal_address() } #[aztec(public)] fn get_portal_address_public() -> EthAddress { context.this_portal_address() } ``` and change the call to `get_portal_address` ``` - let portal_address = get_portal_address(contract_address); + let portal_address = SomeContract::at(contract_address).get_portal_address().call(&mut context); ``` ### \[Aztec.nr] Required gas limits for public-to-public calls[​](#aztecnr-required-gas-limits-for-public-to-public-calls "Direct link to \[Aztec.nr] Required gas limits for public-to-public calls") When calling a public function from another public function using the `call_public_function` method, you must now specify how much gas you're allocating to the nested call. This will later allow you to limit the amount of gas consumed by the nested call, and handle any out of gas errors. Note that gas limits are not yet enforced. For now, it is suggested you use `dep::aztec::context::gas::GasOpts::default()` which will forward all available gas. ``` + use dep::aztec::context::gas::GasOpts; - context.call_public_function(target_contract, target_selector, args); + context.call_public_function(target_contract, target_selector, args, GasOpts::default()); ``` Note that this is not required when enqueuing a public function from a private one, since top-level enqueued public functions will always consume all gas available for the transaction, as it is not possible to handle any out-of-gas errors. ### \[Aztec.nr] Emitting unencrypted logs[​](#aztecnr-emitting-unencrypted-logs "Direct link to \[Aztec.nr] Emitting unencrypted logs") The `emit_unencrypted_logs` function is now a context method. ``` - use dep::aztec::log::emit_unencrypted_log; - use dep::aztec::log::emit_unencrypted_log_from_private; - emit_unencrypted_log(context, log1); - emit_unencrypted_log_from_private(context, log2); + context.emit_unencrypted_log(log1); + context.emit_unencrypted_log(log2); ``` ## 0.33[​](#033 "Direct link to 0.33") ### \[Aztec.nr] Storage struct annotation[​](#aztecnr-storage-struct-annotation "Direct link to \[Aztec.nr] Storage struct annotation") The storage struct now identified by the annotation `#[aztec(storage)]`, instead of having to rely on it being called `Storage`. ``` - struct Storage { - ... - } + #[aztec(storage)] + struct MyStorageStruct { + ... + } ``` ### \[Aztec.js] Storage layout and note info[​](#aztecjs-storage-layout-and-note-info "Direct link to \[Aztec.js] Storage layout and note info") Storage layout and note information are now exposed in the TS contract artifact ``` - const note = new Note([new Fr(mintAmount), secretHash]); - const pendingShieldStorageSlot = new Fr(5n); // storage slot for pending_shields - const noteTypeId = new Fr(84114971101151129711410111011678111116101n); // note type id for TransparentNote - const extendedNote = new ExtendedNote( - note, - admin.address, - token.address, - pendingShieldStorageSlot, - noteTypeId, - receipt.txHash, - ); - await pxe.addNote(extendedNote); + const note = new Note([new Fr(mintAmount), secretHash]); + const extendedNote = new ExtendedNote( + note, + admin.address, + token.address, + TokenContract.storage.pending_shields.slot, + TokenContract.notes.TransparentNote.id, + receipt.txHash, + ); + await pxe.addNote(extendedNote); ``` ### \[Aztec.nr] rand oracle is now called unsafe\_rand[​](#aztecnr-rand-oracle-is-now-called-unsafe_rand "Direct link to \[Aztec.nr] rand oracle is now called unsafe_rand") `oracle::rand::rand` has been renamed to `oracle::unsafe_rand::unsafe_rand`. This change was made to communicate that we do not constrain the value in circuit and instead we just trust our PXE. ``` - let random_value = rand(); + let random_value = unsafe_rand(); ``` ### \[AztecJS] Simulate and get return values for ANY call and introducing `prove()`[​](#aztecjs-simulate-and-get-return-values-for-any-call-and-introducing-prove "Direct link to aztecjs-simulate-and-get-return-values-for-any-call-and-introducing-prove") Historically it have been possible to "view" `unconstrained` functions to simulate them and get the return values, but not for `public` nor `private` functions. This has lead to a lot of bad code where we have the same function implemented thrice, once in `private`, once in `public` and once in `unconstrained`. It is not possible to call `simulate` on any call to get the return values! However, beware that it currently always returns a Field array of size 4 for private and public. This will change to become similar to the return values of the `unconstrained` functions with proper return types. ``` - #[aztec(private)] - fn get_shared_immutable_constrained_private() -> pub Leader { - storage.shared_immutable.read_private() - } - - unconstrained fn get_shared_immutable() -> pub Leader { - storage.shared_immutable.read_public() - } + #[aztec(private)] + fn get_shared_immutable_private() -> pub Leader { + storage.shared_immutable.read_private() + } - const returnValues = await contract.methods.get_shared_immutable().view(); + const returnValues = await contract.methods.get_shared_immutable_private().simulate(); ``` ``` await expect( - asset.withWallet(wallets[1]).methods.update_admin(newAdminAddress).simulate()).rejects.toThrow( + asset.withWallet(wallets[1]).methods.update_admin(newAdminAddress).prove()).rejects.toThrow( "Assertion failed: caller is not admin 'caller_roles.is_admin'", ); ``` ## 0.31.0[​](#0310 "Direct link to 0.31.0") ### \[Aztec.nr] Public storage historical read API improvement[​](#aztecnr-public-storage-historical-read-api-improvement "Direct link to \[Aztec.nr] Public storage historical read API improvement") `history::public_value_inclusion::prove_public_value_inclusion` has been renamed to `history::storage::public_storage_historical_read`, and its API changed slightly. Instead of receiving a `value` parameter it now returns the historical value stored at that slot. If you were using an oracle to get the value to pass to `prove_public_value_inclusion`, drop the oracle and use the return value from `public_storage_historical_read` instead: ``` - let value = read_storage(); - prove_public_value_inclusion(value, storage_slot, contract_address, context); + let value = public_storage_historical_read(storage_slot, contract_address, context); ``` If you were proving historical existence of a value you got via some other constrained means, perform an assertion against the return value of `public_storage_historical_read` instead: ``` - prove_public_value_inclusion(value, storage_slot, contract_address, context); + assert(public_storage_historical_read(storage_slot, contract_address, context) == value); ``` ## 0.30.0[​](#0300 "Direct link to 0.30.0") ### \[AztecJS] Simplify authwit syntax[​](#aztecjs-simplify-authwit-syntax "Direct link to \[AztecJS] Simplify authwit syntax") ``` - const messageHash = computeAuthWitMessageHash(accounts[1].address, action.request()); - await wallets[0].setPublicAuth(messageHash, true).send().wait(); + await wallets[0].setPublicAuthWit({ caller: accounts[1].address, action }, true).send().wait(); ``` ``` const action = asset .withWallet(wallets[1]) .methods.unshield(accounts[0].address, accounts[1].address, amount, nonce); -const messageHash = computeAuthWitMessageHash(accounts[1].address, action.request()); -const witness = await wallets[0].createAuthWitness(messageHash); +const witness = await wallets[0].createAuthWit({ caller: accounts[1].address, action }); await wallets[1].addAuthWitness(witness); ``` Also note some of the naming changes: `setPublicAuth` -> `setPublicAuthWit` `createAuthWitness` -> `createAuthWit` ### \[Aztec.nr] Automatic NoteInterface implementation and selector changes[​](#aztecnr-automatic-noteinterface-implementation-and-selector-changes "Direct link to \[Aztec.nr] Automatic NoteInterface implementation and selector changes") Implementing a note required a fair amount of boilerplate code, which has been substituted by the `#[aztec(note)]` attribute. ``` + #[aztec(note)] struct AddressNote { address: AztecAddress, owner: AztecAddress, randomness: Field, header: NoteHeader } impl NoteInterface for AddressNote { - fn serialize_content(self) -> [Field; ADDRESS_NOTE_LEN]{ - [self.address.to_field(), self.owner.to_field(), self.randomness] - } - - fn deserialize_content(serialized_note: [Field; ADDRESS_NOTE_LEN]) -> Self { - AddressNote { - address: AztecAddress::from_field(serialized_note[0]), - owner: AztecAddress::from_field(serialized_note[1]), - randomness: serialized_note[2], - header: NoteHeader::empty(), - } - } - - fn compute_note_content_hash(self) -> Field { - pedersen_hash(self.serialize_content(), 0) - } - fn compute_nullifier(self, context: &mut PrivateContext) -> Field { let note_hash_for_nullify = compute_note_hash_for_consumption(self); let secret = context.request_nullifier_secret_key(self.owner); pedersen_hash([ note_hash_for_nullify, secret.low, secret.high, ],0) } fn compute_nullifier_without_context(self) -> Field { let note_hash_for_nullify = compute_note_hash_for_consumption(self); let secret = get_nullifier_secret_key(self.owner); pedersen_hash([ note_hash_for_nullify, secret.low, secret.high, ],0) } - fn set_header(&mut self, header: NoteHeader) { - self.header = header; - } - - fn get_header(note: Self) -> NoteHeader { - note.header - } fn broadcast(self, context: &mut PrivateContext, slot: Field) { let encryption_pub_key = get_public_key(self.owner); emit_encrypted_log( context, (*context).this_address(), slot, Self::get_note_type_id(), encryption_pub_key, self.serialize_content(), ); } - fn get_note_type_id() -> Field { - 6510010011410111511578111116101 - } } ``` Automatic note (de)serialization implementation also means it is now easier to filter notes using `NoteGetterOptions.select` via the `::properties()` helper: Before: ``` let options = NoteGetterOptions::new().select(0, amount, Option::none()).select(1, owner.to_field(), Option::none()).set_limit(1); ``` After: ``` let options = NoteGetterOptions::new().select(ValueNote::properties().value, amount, Option::none()).select(ValueNote::properties().owner, owner.to_field(), Option::none()).set_limit(1); ``` The helper returns a metadata struct that looks like this (if autogenerated) ``` ValueNoteProperties { value: PropertySelector { index: 0, offset: 0, length: 32 }, owner: PropertySelector { index: 1, offset: 0, length: 32 }, randomness: PropertySelector { index: 2, offset: 0, length: 32 }, } ``` It can also be used for the `.sort` method. ## 0.27.0[​](#0270 "Direct link to 0.27.0") ### `initializer` macro replaces `constructor`[​](#initializer-macro-replaces-constructor "Direct link to initializer-macro-replaces-constructor") Before this version, every contract was required to have exactly one `constructor` private function, that was used for deployment. We have now removed this requirement, and made `constructor` a function like any other. To signal that a function can be used to **initialize** a contract, you must now decorate it with the `#[aztec(initializer)]` attribute. Initializers are regular functions that set an "initialized" flag (a nullifier) for the contract. A contract can only be initialized once, and contract functions can only be called after the contract has been initialized, much like a constructor. However, if a contract defines no initializers, it can be called at any time. Additionally, you can define as many initializer functions in a contract as you want, both private and public. To migrate from current code, simply add an initializer attribute to your constructor functions. ``` + #[aztec(initializer)] #[aztec(private)] fn constructor() { ... } ``` If your private constructor was used to just call a public internal initializer, then remove the private constructor and flag the public function as initializer. And if your private constructor was an empty one, just remove it. ## 0.25.0[​](#0250 "Direct link to 0.25.0") ### \[Aztec.nr] Static calls[​](#aztecnr-static-calls "Direct link to \[Aztec.nr] Static calls") It is now possible to perform static calls from both public and private functions. Static calls forbid any modification to the state, including L2->L1 messages or log generation. Once a static context is set through a static all, every subsequent call will also be treated as static via context propagation. ``` context.static_call_private_function(targetContractAddress, targetSelector, args); context.static_call_public_function(targetContractAddress, targetSelector, args); ``` ### \[Aztec.nr] Introduction to `prelude`[​](#aztecnr-introduction-to-prelude "Direct link to aztecnr-introduction-to-prelude") A new `prelude` module to include common Aztec modules and types. This simplifies dependency syntax. For example: ``` use dep::aztec::protocol::address::AztecAddress; use dep::aztec::{ context::{PrivateContext, Context}, note::{note_header::NoteHeader, utils as note_utils}, state_vars::Map }; ``` Becomes: ``` use dep::aztec::prelude::{AztecAddress, NoteHeader, PrivateContext, Map}; use dep::aztec::context::Context; use dep::aztec::notes::utils as note_utils; ``` This will be further simplified in future versions (See [4496](https://github.com/AztecProtocol/aztec-packages/pull/4496) for further details). The prelude consists of \[Edit: removed because the prelude no-longer exists] ### `internal` is now a macro[​](#internal-is-now-a-macro "Direct link to internal-is-now-a-macro") The `internal` keyword is now removed from Noir, and is replaced by an `aztec(internal)` attribute in the function. The resulting behavior is exactly the same: these functions will only be callable from within the same contract. Before: ``` #[aztec(private)] internal fn double(input: Field) -> Field { input * 2 } ``` After: ``` #[aztec(private)] #[aztec(internal)] fn double(input: Field) -> Field { input * 2 } ``` ### \[Aztec.nr] No SafeU120 anymore\![​](#aztecnr-no-safeu120-anymore "Direct link to \[Aztec.nr] No SafeU120 anymore!") Noir now have overflow checks by default. So we don't need SafeU120 like libraries anymore. You can replace it with `U128` instead Before: ``` SafeU120::new(0) ``` Now: ``` U128::from_integer(0) ``` ### \[Aztec.nr] `compute_note_hash_and_nullifier` is now autogenerated[​](#aztecnr-compute_note_hash_and_nullifier-is-now-autogenerated "Direct link to aztecnr-compute_note_hash_and_nullifier-is-now-autogenerated") Historically developers have been required to include a `compute_note_hash_and_nullifier` function in each of their contracts. This function is now automatically generated, and all instances of it in contract code can be safely removed. It is possible to provide a user-defined implementation, in which case auto-generation will be skipped (though there are no known use cases for this). ### \[Aztec.nr] Updated naming of state variable wrappers[​](#aztecnr-updated-naming-of-state-variable-wrappers "Direct link to \[Aztec.nr] Updated naming of state variable wrappers") We have decided to change the naming of our state variable wrappers because the naming was not clear. The changes are as follows: 1. `Singleton` -> `PrivateMutable` 2. `ImmutableSingleton` -> `PrivateImmutable` 3. `StablePublicState` -> `SharedImmutable` 4. `PublicState` -> `PublicMutable` This is the meaning of "private", "public" and "shared": Private: read (R) and write (W) from private, not accessible from public Public: not accessible from private, R/W from public Shared: R from private, R/W from public Note: `SlowUpdates` will be renamed to `SharedMutable` once the implementation is ready. ### \[Aztec.nr] Authwit updates[​](#aztecnr-authwit-updates "Direct link to \[Aztec.nr] Authwit updates") Authentication Witnesses have been updates such that they are now cancellable and scoped to a specific consumer. This means that the `authwit` nullifier must be emitted from the account contract, which require changes to the interface. Namely, the `assert_current_call_valid_authwit_public` and `assert_current_call_valid_authwit` in `auth.nr` will **NO LONGER** emit a nullifier. Instead it will call a `spend_*_authwit` function in the account contract - which will emit the nullifier and perform a few checks. This means that the `is_valid` functions have been removed to not confuse it for a non-mutating function (static). Furthermore, the `caller` parameter of the "authwits" have been moved "further out" such that the account contract can use it in validation, allowing scoped approvals from the account POV. For most contracts, this won't be changing much, but for the account contract, it will require a few changes. Before: ``` #[aztec(public)] fn is_valid_public(message_hash: Field) -> Field { let actions = AccountActions::public(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); actions.is_valid_public(message_hash) } #[aztec(private)] fn is_valid(message_hash: Field) -> Field { let actions = AccountActions::private(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); actions.is_valid(message_hash) } ``` After: ``` #[aztec(private)] fn verify_private_authwit(inner_hash: Field) -> Field { let actions = AccountActions::private(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); actions.verify_private_authwit(inner_hash) } #[aztec(public)] fn spend_public_authwit(inner_hash: Field) -> Field { let actions = AccountActions::public(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); actions.spend_public_authwit(inner_hash) } ``` ## 0.24.0[​](#0240 "Direct link to 0.24.0") ### Introduce Note Type IDs[​](#introduce-note-type-ids "Direct link to Introduce Note Type IDs") Note Type IDs are a new feature which enable contracts to have multiple `Map`s with different underlying note types, something that was not possible before. This is done almost without any user intervention, though some minor changes are required. The mandatory `compute_note_hash_and_nullifier` now has a fifth parameter `note_type_id`. Use this instead of `storage_slot` to determine which deserialization function to use. Before: ``` unconstrained fn compute_note_hash_and_nullifier( contract_address: AztecAddress, nonce: Field, storage_slot: Field, preimage: [Field; TOKEN_NOTE_LEN] ) -> pub [Field; 4] { let note_header = NoteHeader::new(contract_address, nonce, storage_slot); if (storage_slot == storage.pending_shields.get_storage_slot()) { note_utils::compute_note_hash_and_nullifier(TransparentNote::deserialize_content, note_header, preimage) } else if (note_type_id == storage.slow_update.get_storage_slot()) { note_utils::compute_note_hash_and_nullifier(FieldNote::deserialize_content, note_header, preimage) } else { note_utils::compute_note_hash_and_nullifier(TokenNote::deserialize_content, note_header, preimage) } ``` Now: ``` unconstrained fn compute_note_hash_and_nullifier( contract_address: AztecAddress, nonce: Field, storage_slot: Field, note_type_id: Field, preimage: [Field; TOKEN_NOTE_LEN] ) -> pub [Field; 4] { let note_header = NoteHeader::new(contract_address, nonce, storage_slot); if (note_type_id == TransparentNote::get_note_type_id()) { note_utils::compute_note_hash_and_nullifier(TransparentNote::deserialize_content, note_header, preimage) } else if (note_type_id == FieldNote::get_note_type_id()) { note_utils::compute_note_hash_and_nullifier(FieldNote::deserialize_content, note_header, preimage) } else { note_utils::compute_note_hash_and_nullifier(TokenNote::deserialize_content, note_header, preimage) } ``` The `NoteInterface` trait now has an additional `get_note_type_id()` function. This implementation will be autogenerated in the future, but for now providing any unique ID will suffice. The suggested way to do it is by running the Python command shown in the comment below: ``` impl NoteInterface for MyCustomNote { fn get_note_type_id() -> Field { // python -c "print(int(''.join(str(ord(c)) for c in 'MyCustomNote')))" 771216711711511611110978111116101 } } ``` ### \[js] Importing contracts in JS[​](#js-importing-contracts-in-js "Direct link to \[js] Importing contracts in JS") `@aztec/noir-contracts` is now `@aztec/noir-contracts.js`. You'll need to update your package.json & imports. Before: ``` import { TokenContract } from "@aztec/noir-contracts/Token"; ``` Now: ``` import { TokenContract } from "@aztec/noir-contracts.js/Token"; ``` ### \[Aztec.nr] Aztec.nr contracts location change in Nargo.toml[​](#aztecnr-aztecnr-contracts-location-change-in-nargotoml "Direct link to \[Aztec.nr] Aztec.nr contracts location change in Nargo.toml") Aztec contracts are now moved outside of the `yarn-project` folder and into `noir-projects`, so you need to update your imports. Before: ``` easy_private_token_contract = {git = "https://github.com/AztecProtocol/aztec-packages/", tag ="v0.23.0", directory = "yarn-project/noir-contracts/contracts/easy_private_token_contract"} ``` Now, update the `yarn-project` folder for `noir-projects`: ``` easy_private_token_contract = {git = "https://github.com/AztecProtocol/aztec-packages/", tag ="v0.24.0", directory = "noir-projects/noir-contracts/contracts/easy_private_token_contract"} ``` ## 0.22.0[​](#0220 "Direct link to 0.22.0") ### `Note::compute_note_hash` renamed to `Note::compute_note_content_hash`[​](#notecompute_note_hash-renamed-to-notecompute_note_content_hash "Direct link to notecompute_note_hash-renamed-to-notecompute_note_content_hash") The `compute_note_hash` function in of the `Note` trait has been renamed to `compute_note_content_hash` to avoid being confused with the actual note hash. Before: ``` impl NoteInterface for CardNote { fn compute_note_hash(self) -> Field { pedersen_hash([ self.owner.to_field(), ], 0) } ``` Now: ``` impl NoteInterface for CardNote { fn compute_note_content_hash(self) -> Field { pedersen_hash([ self.owner.to_field(), ], 0) } ``` ### Introduce `compute_note_hash_for_consumption` and `compute_note_hash_for_insertion`[​](#introduce-compute_note_hash_for_consumption-and-compute_note_hash_for_insertion "Direct link to introduce-compute_note_hash_for_consumption-and-compute_note_hash_for_insertion") Makes a split in logic for note hash computation for consumption and insertion. This is to avoid confusion between the two, and to make it clear that the note hash for consumption is different from the note hash for insertion (sometimes). `compute_note_hash_for_consumption` replaces `compute_note_hash_for_read_or_nullify`. `compute_note_hash_for_insertion` is new, and mainly used in \`lifecycle.nr\`\` ### `Note::serialize_content` and `Note::deserialize_content` added to \`NoteInterface[​](#noteserialize_content-and-notedeserialize_content-added-to-noteinterface "Direct link to noteserialize_content-and-notedeserialize_content-added-to-noteinterface") The `NoteInterface` have been extended to include `serialize_content` and `deserialize_content` functions. This is to convey the difference between serializing the full note, and just the content. This change allows you to also add a `serialize` function to support passing in a complete note to a function. Before: ``` impl Serialize for AddressNote { fn serialize(self) -> [Field; ADDRESS_NOTE_LEN]{ [self.address.to_field(), self.owner.to_field(), self.randomness] } } impl Deserialize for AddressNote { fn deserialize(serialized_note: [Field; ADDRESS_NOTE_LEN]) -> Self { AddressNote { address: AztecAddress::from_field(serialized_note[0]), owner: AztecAddress::from_field(serialized_note[1]), randomness: serialized_note[2], header: NoteHeader::empty(), } } ``` Now ``` impl NoteInterface for AddressNote { fn serialize_content(self) -> [Field; ADDRESS_NOTE_LEN]{ [self.address.to_field(), self.owner.to_field(), self.randomness] } fn deserialize_content(serialized_note: [Field; ADDRESS_NOTE_LEN]) -> Self { AddressNote { address: AztecAddress::from_field(serialized_note[0]), owner: AztecAddress::from_field(serialized_note[1]), randomness: serialized_note[2], header: NoteHeader::empty(), } } ... } ``` ### \[Aztec.nr] No storage.init() and `Serialize`, `Deserialize`, `NoteInterface` as Traits, removal of SerializationMethods and SERIALIZED\_LEN[​](#aztecnr-no-storageinit-and-serialize-deserialize-noteinterface-as-traits-removal-of-serializationmethods-and-serialized_len "Direct link to aztecnr-no-storageinit-and-serialize-deserialize-noteinterface-as-traits-removal-of-serializationmethods-and-serialized_len") Storage definition and initialization has been simplified. Previously: ``` struct Storage { leader: PublicState, legendary_card: Singleton, profiles: Map>, test: Set, imm_singleton: PrivateImmutable, } impl Storage { fn init(context: Context) -> Self { Storage { leader: PublicMutable::new( context, 1, LeaderSerializationMethods, ), legendary_card: PrivateMutable::new(context, 2, CardNoteMethods), profiles: Map::new( context, 3, |context, slot| { PrivateMutable::new(context, slot, CardNoteMethods) }, ), test: Set::new(context, 4, CardNoteMethods), imm_singleton: PrivateImmutable::new(context, 4, CardNoteMethods), } } } ``` Now: ``` struct Storage { leader: PublicMutable, legendary_card: Singleton, profiles: Map>, test: Set, imm_singleton: PrivateImmutable, } ``` For this to work, Notes must implement Serialize, Deserialize and NoteInterface Traits. Previously: ``` use dep::aztec::protocol::address::AztecAddress; use dep::aztec::{ note::{ note_header::NoteHeader, note_interface::NoteInterface, utils::compute_note_hash_for_read_or_nullify, }, oracle::{ nullifier_key::get_nullifier_secret_key, get_public_key::get_public_key, }, log::emit_encrypted_log, hash::pedersen_hash, context::PrivateContext, }; // Shows how to create a custom note global CARD_NOTE_LEN: Field = 1; impl CardNote { pub fn new(owner: AztecAddress) -> Self { CardNote { owner, } } pub fn serialize(self) -> [Field; CARD_NOTE_LEN] { [self.owner.to_field()] } pub fn deserialize(serialized_note: [Field; CARD_NOTE_LEN]) -> Self { CardNote { owner: AztecAddress::from_field(serialized_note[1]), } } pub fn compute_note_hash(self) -> Field { pedersen_hash([ self.owner.to_field(), ],0) } pub fn compute_nullifier(self, context: &mut PrivateContext) -> Field { let note_hash_for_nullify = compute_note_hash_for_read_or_nullify(CardNoteMethods, self); let secret = context.request_nullifier_secret_key(self.owner); pedersen_hash([ note_hash_for_nullify, secret.high, secret.low, ],0) } pub fn compute_nullifier_without_context(self) -> Field { let note_hash_for_nullify = compute_note_hash_for_read_or_nullify(CardNoteMethods, self); let secret = get_nullifier_secret_key(self.owner); pedersen_hash([ note_hash_for_nullify, secret.high, secret.low, ],0) } pub fn set_header(&mut self, header: NoteHeader) { self.header = header; } // Broadcasts the note as an encrypted log on L1. pub fn broadcast(self, context: &mut PrivateContext, slot: Field) { let encryption_pub_key = get_public_key(self.owner); emit_encrypted_log( context, (*context).this_address(), slot, encryption_pub_key, self.serialize(), ); } } fn deserialize(serialized_note: [Field; CARD_NOTE_LEN]) -> CardNote { CardNote::deserialize(serialized_note) } fn serialize(note: CardNote) -> [Field; CARD_NOTE_LEN] { note.serialize() } fn compute_note_hash(note: CardNote) -> Field { note.compute_note_hash() } fn compute_nullifier(note: CardNote, context: &mut PrivateContext) -> Field { note.compute_nullifier(context) } fn compute_nullifier_without_context(note: CardNote) -> Field { note.compute_nullifier_without_context() } fn get_header(note: CardNote) -> NoteHeader { note.header } fn set_header(note: &mut CardNote, header: NoteHeader) { note.set_header(header) } // Broadcasts the note as an encrypted log on L1. fn broadcast(context: &mut PrivateContext, slot: Field, note: CardNote) { note.broadcast(context, slot); } global CardNoteMethods = NoteInterface { deserialize, serialize, compute_note_hash, compute_nullifier, compute_nullifier_without_context, get_header, set_header, broadcast, }; ``` Now: ``` use dep::aztec::{ note::{ note_header::NoteHeader, note_interface::NoteInterface, utils::compute_note_hash_for_read_or_nullify, }, oracle::{ nullifier_key::get_nullifier_secret_key, get_public_key::get_public_key, }, log::emit_encrypted_log, hash::pedersen_hash, context::PrivateContext, protocol::{ address::AztecAddress, traits::{Serialize, Deserialize, Empty} } }; // Shows how to create a custom note global CARD_NOTE_LEN: Field = 1; impl CardNote { pub fn new(owner: AztecAddress) -> Self { CardNote { owner, } } } impl NoteInterface for CardNote { fn compute_note_content_hash(self) -> Field { pedersen_hash([ self.owner.to_field(), ],0) } fn compute_nullifier(self, context: &mut PrivateContext) -> Field { let note_hash_for_nullify = compute_note_hash_for_read_or_nullify(self); let secret = context.request_nullifier_secret_key(self.owner); pedersen_hash([ note_hash_for_nullify, secret.high, secret.low, ],0) } fn compute_nullifier_without_context(self) -> Field { let note_hash_for_nullify = compute_note_hash_for_read_or_nullify(self); let secret = get_nullifier_secret_key(self.owner); pedersen_hash([ note_hash_for_nullify, secret.high, secret.low, ],0) } fn set_header(&mut self, header: NoteHeader) { self.header = header; } fn get_header(note: CardNote) -> NoteHeader { note.header } fn serialize_content(self) -> [Field; CARD_NOTE_LEN]{ [self.owner.to_field()] } fn deserialize_content(serialized_note: [Field; CARD_NOTE_LEN]) -> Self { AddressNote { owner: AztecAddress::from_field(serialized_note[0]), header: NoteHeader::empty(), } } // Broadcasts the note as an encrypted log on L1. fn broadcast(self, context: &mut PrivateContext, slot: Field) { let encryption_pub_key = get_public_key(self.owner); emit_encrypted_log( context, (*context).this_address(), slot, encryption_pub_key, self.serialize(), ); } } ``` Public state must implement Serialize and Deserialize traits. It is still possible to manually implement the storage initialization (for custom storage wrappers or internal types that don't implement the required traits). For the above example, the `impl Storage` section would look like this: ``` impl Storage { fn init(context: Context) -> Self { Storage { leader: PublicMutable::new( context, 1 ), legendary_card: PrivateMutable::new(context, 2), profiles: Map::new( context, 3, |context, slot| { PrivateMutable::new(context, slot) }, ), test: Set::new(context, 4), imm_singleton: PrivateImmutable::new(context, 4), } } } ``` ## 0.20.0[​](#0200 "Direct link to 0.20.0") ### \[Aztec.nr] Changes to `NoteInterface`[​](#aztecnr-changes-to-noteinterface-2 "Direct link to aztecnr-changes-to-noteinterface-2") 1. Changing `compute_nullifier()` to `compute_nullifier(private_context: PrivateContext)` This API is invoked for nullifier generation within private functions. When using a secret key for nullifier creation, retrieve it through: `private_context.request_nullifier_secret_key(account_address)` The private context will generate a request for the kernel circuit to validate that the secret key does belong to the account. Before: ``` pub fn compute_nullifier(self) -> Field { let secret = oracle.get_secret_key(self.owner); pedersen_hash([ self.value, secret.low, secret.high, ]) } ``` Now: ``` pub fn compute_nullifier(self, context: &mut PrivateContext) -> Field { let secret = context.request_nullifier_secret_key(self.owner); pedersen_hash([ self.value, secret.low, secret.high, ]) } ``` 2. New API `compute_nullifier_without_context()`. This API is used within unconstrained functions where the private context is not available, and using an unverified nullifier key won't affect the network or other users. For example, it's used in `compute_note_hash_and_nullifier()` to compute values for the user's own notes. ``` pub fn compute_nullifier_without_context(self) -> Field { let secret = oracle.get_nullifier_secret_key(self.owner); pedersen_hash([ self.value, secret.low, secret.high, ]) } ``` > Note that the `get_secret_key` oracle API has been renamed to `get_nullifier_secret_key`. ## 0.18.0[​](#0180 "Direct link to 0.18.0") ### \[Aztec.nr] Remove `protocol` from Nargo.toml[​](#aztecnr-remove-protocol-from-nargotoml "Direct link to aztecnr-remove-protocol-from-nargotoml") The `protocol` package is now being reexported from `aztec`. It can be accessed through `dep::aztec::protocol`. ``` aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v5.0.0-rc.1", directory="yarn-project/aztec-nr/aztec" } ``` ### \[Aztec.nr] key type definition in Map[​](#aztecnr-key-type-definition-in-map "Direct link to \[Aztec.nr] key type definition in Map") The `Map` class now requires defining the key type in its declaration which *must* implement the `ToField` trait. Before: ``` struct Storage { balances: Map> } let user_balance = balances.at(owner.to_field()) ``` Now: ``` struct Storage { balances: Map> } let user_balance = balances.at(owner) ``` ### \[js] Updated function names[​](#js-updated-function-names "Direct link to \[js] Updated function names") * `waitForSandbox` renamed to `waitForPXE` in `@aztec/aztec.js` * `getSandboxAccountsWallets` renamed to `getInitialTestAccountsWallets` in `@aztec/accounts/testing` ## 0.17.0[​](#0170 "Direct link to 0.17.0") ### \[js] New `@aztec/accounts` package[​](#js-new-aztecaccounts-package "Direct link to js-new-aztecaccounts-package") Before: ``` import { getSchnorrAccount } from "@aztec/aztec.js"; // previously you would get the default accounts from the `aztec.js` package: ``` Now, import them from the new package `@aztec/accounts` ``` import { getSchnorrAccount } from "@aztec/accounts"; ``` ### Typed Addresses[​](#typed-addresses "Direct link to Typed Addresses") Address fields in Aztec.nr now is of type `AztecAddress` as opposed to `Field` Before: ``` unconstrained fn compute_note_hash_and_nullifier(contract_address: Field, nonce: Field, storage_slot: Field, serialized_note: [Field; VALUE_NOTE_LEN]) -> [Field; 4] { let note_header = NoteHeader::new(_address, nonce, storage_slot); ... ``` Now: ``` unconstrained fn compute_note_hash_and_nullifier( contract_address: AztecAddress, nonce: Field, storage_slot: Field, serialized_note: [Field; VALUE_NOTE_LEN] ) -> pub [Field; 4] { let note_header = NoteHeader::new(contract_address, nonce, storage_slot); ``` Similarly, there are changes when using aztec.js to call functions. To parse a `AztecAddress` to BigInt, use `.inner` Before: ``` const tokenBigInt = await bridge.methods.token().simulate(); ``` Now: ``` const tokenBigInt = (await bridge.methods.token().simulate()).inner; ``` ### \[Aztec.nr] Add `protocol` to Nargo.toml[​](#aztecnr-add-protocol-to-nargotoml "Direct link to aztecnr-add-protocol-to-nargotoml") ``` aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v5.0.0-rc.1", directory="yarn-project/aztec-nr/aztec" } protocol = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v5.0.0-rc.1", directory="yarn-project/noir-protocol-circuits/crates/types"} ``` ### \[Aztec.nr] moving compute\_address func to AztecAddress[​](#aztecnr-moving-compute_address-func-to-aztecaddress "Direct link to \[Aztec.nr] moving compute_address func to AztecAddress") Before: ``` let calculated_address = compute_address(pub_key_x, pub_key_y, partial_address); ``` Now: ``` let calculated_address = AztecAddress::compute(pub_key_x, pub_key_y, partial_address); ``` ### \[Aztec.nr] moving `compute_selector` to FunctionSelector[​](#aztecnr-moving-compute_selector-to-functionselector "Direct link to aztecnr-moving-compute_selector-to-functionselector") Before: ``` let selector = compute_selector("_initialize((Field))"); ``` Now: ``` let selector = FunctionSelector::from_signature("_initialize((Field))"); ``` ### \[js] Importing contracts in JS[​](#js-importing-contracts-in-js-1 "Direct link to \[js] Importing contracts in JS") Contracts are now imported from a file with the type's name. Before: ``` import { TokenContract } from "@aztec/noir-contracts/types"; ``` Now: ``` import { TokenContract } from "@aztec/noir-contracts/Token"; ``` ### \[Aztec.nr] Aztec example contracts location change in Nargo.toml[​](#aztecnr-aztec-example-contracts-location-change-in-nargotoml "Direct link to \[Aztec.nr] Aztec example contracts location change in Nargo.toml") Aztec contracts are now moved outside of the `src` folder, so you need to update your imports. Before: ``` easy_private_token_contract = {git = "https://github.com/AztecProtocol/aztec-packages/", tag ="v0.16.9", directory = "noir-projects/noir-contracts/contracts/easy_private_token_contract"} ``` Now, just remove the `src` folder,: ``` easy_private_token_contract = {git = "https://github.com/AztecProtocol/aztec-packages/", tag ="v0.17.0", directory = "noir-projects/noir-contracts/contracts/easy_private_token_contract"} ``` --- # Video lessons Prefer watching to reading? These short explainers, presented by Ciara Nightingale from the Aztec team, each cover a core Aztec concept in just a few minutes. Written pages that go deeper are linked below each video. ## What is Aztec?[​](#what-is-aztec "Direct link to What is Aztec?") Aztec is a privacy-first Layer 2 on Ethereum: a zero-knowledge rollup where smart contracts can have both public and private state, and private execution happens locally on your own device. This video explains the core idea in under 90 seconds. [What is Aztec: Explained in Under 90 Seconds](https://www.youtube-nocookie.com/embed/urcBvo2QJp0) Related reading: [Aztec overview](/developers/testnet/overview.md), [foundational topics](/developers/testnet/docs/foundational-topics.md) ## Private and public state in one transaction[​](#private-and-public-state-in-one-transaction "Direct link to Private and public state in one transaction") A single Aztec transaction can span private and public execution. Using a private voting contract as the example, this video shows how private execution runs first on your device, producing a proof and side effects (nullifiers, note commitments, and enqueued public calls) that the sequencer then applies in public, keeping your vote private while the tally stays public. [One Transaction, Two Worlds: Private and Public State on Aztec](https://www.youtube-nocookie.com/embed/MayopgQ1FjI) Related reading: [transactions](/developers/testnet/docs/foundational-topics/transactions.md), [state management](/developers/testnet/docs/foundational-topics/state_management.md) ## What is private composability?[​](#what-is-private-composability "Direct link to What is private composability?") On Aztec, smart contracts can call each other privately. Because transactions execute and prove locally, not only the state but the call stack itself can stay private: nobody watching the chain learns which contract called which. This video explains how that lets you build on top of other apps permissionlessly, just like Ethereum, without leaking what you are doing. [What is Private Composability? An Aztec Explainer](https://www.youtube-nocookie.com/embed/idxRuGQnQKs) Related reading: [call types](/developers/testnet/docs/foundational-topics/call_types.md), [calling other contracts](/developers/testnet/docs/aztec-nr/framework-description/calling_contracts.md) ## How authorization works (authwits)[​](#how-authorization-works-authwits "Direct link to How authorization works (authwits)") Authentication witnesses (authwits) are Aztec's generalized alternative to Ethereum's approve and transferFrom pattern: they authorize a specific action for a specific caller, work in both private and public execution, and prevent replay. This lesson walks through the message hash structure, the private and public flows, and the `#[authorize_once]` macro. [How Authorization Works on Aztec](https://www.youtube-nocookie.com/embed/VRZVOCdjGZ4) Related reading: [authentication witness concepts](/developers/testnet/docs/foundational-topics/advanced/authwit.md), [using authwits in aztec.nr](/developers/testnet/docs/aztec-nr/framework-description/authentication_witnesses.md) ## Get started in under 60 seconds[​](#get-started-in-under-60-seconds "Direct link to Get started in under 60 seconds") Ready to build? This video walks through installing the Aztec tooling, creating a new contract project, compiling it, and deploying it to a local network, all in under a minute. [Get Started on Aztec in Under 60 Seconds](https://www.youtube-nocookie.com/embed/_jgHNdNgFOg) Related reading: [getting started on a local network](/developers/testnet/getting_started_on_local_network.md) ## More videos[​](#more-videos "Direct link to More videos") For a full-length course and more explainers, visit the [Aztec Network YouTube channel](https://www.youtube.com/@aztecnetwork). --- # Counter contract In this guide, we will create our first Aztec.nr smart contract. We will build a simple private counter, where each account keeps its own counter as encrypted private state, so the count stays known only to you. This contract will get you started with the basic setup and syntax of Aztec.nr, but doesn't showcase all of the awesome stuff Aztec is capable of. This tutorial is compatible with the Aztec version `v5.0.0-rc.2`. Install the correct version with `VERSION=5.0.0-rc.2 bash -i <(curl -sL https://install.aztec.network/5.0.0-rc.2)`. Or if you'd like to use a different version, you can find the relevant tutorial by clicking the version dropdown at the top of the page. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * You have followed the [quickstart](/developers/testnet/getting_started_on_local_network.md) * Running Aztec local network * Installed [Noir LSP](/developers/testnet/docs/aztec-nr/installation.md) (optional) ## Set up a project[​](#set-up-a-project "Direct link to Set up a project") Run this to create a new contract project: ``` aztec new counter ``` Your structure should look like this: ``` . |-counter | |-Nargo.toml <-- workspace root | |-counter_contract | | |-src | | | |-main.nr | | |-Nargo.toml <-- contract package config | |-counter_test | | |-src | | | |-lib.nr | | |-Nargo.toml <-- test package config ``` The `aztec new` command creates a two-crate workspace: a `counter_contract` crate for your contract and a `counter_test` crate for tests. The file `counter_contract/src/main.nr` will soon turn into our smart contract! Add the following dependency to `counter_contract/Nargo.toml` under the existing `aztec` dependency: ``` [dependencies] aztec = { git="https://github.com/AztecProtocol/aztec-nr/", tag="v5.0.0-rc.2", directory="aztec" } balance_set = { git="https://github.com/AztecProtocol/aztec-nr/", tag="v5.0.0-rc.2", directory="balance-set" } ``` ## Define the functions[​](#define-the-functions "Direct link to Define the functions") Go to `counter_contract/src/main.nr`, and replace the boilerplate code with this contract initialization: ``` use aztec::macros::aztec; #[aztec] pub contract Counter { } ``` Clear the scaffold's placeholder test The scaffolded `counter_test/src/lib.nr` imports the default contract name (`Main`) we just replaced above, so it now fails to compile. Tests aren't used in this tutorial, so replace its contents with a single-line stub to keep `aztec compile` clean: ``` // Tests are out of scope for this tutorial. See https://docs.aztec.network/aztec-nr/testing_contracts for examples. ``` This defines a contract called `Counter`. ## Imports[​](#imports "Direct link to Imports") We need to define some imports. Write this inside your contract, ie inside these brackets: ``` pub contract Counter { // imports go here! } ``` imports ``` use aztec::{ macros::{functions::{external, initializer}, storage::storage}, messages::delivery::MessageDelivery, oracle::logging::debug_log_format, protocol::{address::AztecAddress, traits::ToField}, state_vars::Owned, }; use balance_set::BalanceSet; ``` > [Source code: docs/examples/contracts/counter\_contract/src/main.nr#L7-L16](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/counter_contract/src/main.nr#L7-L16) * `macros::{functions::{external, initializer}, storage::storage}` Imports the macros needed to define function types (`external`, `initializer`) and the `storage` macro for declaring contract storage structures. * `messages::delivery::MessageDelivery` Imports `MessageDelivery` for specifying how note delivery should be handled (e.g., constrained onchain delivery). * `oracle::logging::debug_log_format` Imports a debug logging utility for printing formatted messages during contract execution. * `protocol::{address::AztecAddress, traits::ToField}` Brings in `AztecAddress` (used to identify accounts/contracts) and traits for converting values to field elements, necessary for serialization and formatting inside Aztec. * `state_vars::Owned` Brings in `Owned`, a wrapper for state variables that have a single owner. * `use balance_set::BalanceSet` Imports `BalanceSet` from the `balance_set` dependency, which provides functionality for managing private balances (used for our counter). ## Declare storage[​](#declare-storage "Direct link to Declare storage") Add this below the imports. It declares the storage variables for our contract. We use an `Owned` state variable wrapping a `BalanceSet` to manage private balances for each owner. storage\_struct ``` #[storage] struct Storage { counters: Owned, Context>, } ``` > [Source code: docs/examples/contracts/counter\_contract/src/main.nr#L18-L23](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/counter_contract/src/main.nr#L18-L23) ## Keep the counter private[​](#keep-the-counter-private "Direct link to Keep the counter private") Now we’ve got a mechanism for storing our private state, we can start using it to ensure the privacy of balances. Let’s create a constructor method to run on deployment that assigns an initial count to a specified owner. We name it `constructor` here, but the name is arbitrary; it is the `#[initializer]` decorator that marks it to run once when the contract is deployed. Write this: constructor ``` #[initializer] #[external("private")] // We can name our initializer anything we want as long as it's marked as #[initializer] fn constructor(initial_value: u128, owner: AztecAddress) { self.storage.counters.at(owner).add(initial_value).deliver( MessageDelivery::onchain_constrained(), ); } ``` > [Source code: docs/examples/contracts/counter\_contract/src/main.nr#L25-L34](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/counter_contract/src/main.nr#L25-L34) This function accesses the counters from storage. It adds the `initial_value` to the `owner`'s counter using `at().add()`, then calls `.deliver(MessageDelivery::onchain_constrained())` to ensure the note is delivered onchain. We have annotated this and other functions with `#[external("private")]` which are ABI macros so the compiler understands it will handle private inputs. ## Incrementing our counter[​](#incrementing-our-counter "Direct link to Incrementing our counter") Now let's implement an `increment` function to increase the counter. increment ``` #[external("private")] fn increment(owner: AztecAddress) { debug_log_format("Incrementing counter for owner {0}", [owner.to_field()]); self.storage.counters.at(owner).add(1).deliver(MessageDelivery::onchain_constrained()); } ``` > [Source code: docs/examples/contracts/counter\_contract/src/main.nr#L36-L42](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/counter_contract/src/main.nr#L36-L42) The `increment` function works similarly to the `constructor`. It logs a debug message, then adds 1 to the `owner`'s counter and delivers the note onchain. ## Getting a counter[​](#getting-a-counter "Direct link to Getting a counter") The last thing we need to implement is a function to retrieve a counter value. get\_counter ``` #[external("utility")] unconstrained fn get_counter(owner: AztecAddress) -> pub u128 { self.storage.counters.at(owner).balance_of() } ``` > [Source code: docs/examples/contracts/counter\_contract/src/main.nr#L44-L49](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/counter_contract/src/main.nr#L44-L49) This is a `utility` function used to obtain the counter value outside of a transaction. We access the `owner`'s balance from the `counters` storage variable using `at(owner)`, then call `balance_of()` to retrieve the current count. This yields a private counter that only the owner can decrypt. ## Compile[​](#compile "Direct link to Compile") Now we've written a simple Aztec.nr smart contract, we can compile it. ### Compile the smart contract[​](#compile-the-smart-contract "Direct link to Compile the smart contract") In the `./counter/` directory, run: ``` aztec compile ``` This command compiles your Noir contract and creates a `target` folder with a `.json` artifact inside. After compiling, you can generate a TypeScript class using the `aztec codegen` command. In the same directory, run this: ``` aztec codegen -o src/artifacts target ``` You can now use the artifact and/or the TS class in your Aztec.js! ## Next steps[​](#next-steps "Direct link to Next steps") ### Optional: learn more about concepts mentioned here[​](#optional-learn-more-about-concepts-mentioned-here "Direct link to Optional: learn more about concepts mentioned here") * [Functions and annotations like `#[external("private")]`](/developers/testnet/docs/aztec-nr/framework-description/functions/function_transforms.md#private-functions) --- # Verify Noir Proofs in Aztec Contracts ## Overview[​](#overview "Direct link to Overview") In this tutorial, you will build a system that generates zero-knowledge proofs offchain using a Noir circuit and verifies them onchain within an Aztec Protocol smart contract. You will create a simple circuit that proves two values are not equal, generate an UltraHonk proof, deploy an Aztec contract that stores a verification key hash, and submit the proof for onchain verification. This pattern enables trustless computation where anyone can verify that a computation was performed correctly without revealing the private inputs. Why "Recursive" Verification? This is called "recursive" verification because the proof is verified inside an Aztec private function, which itself gets compiled into a ZK circuit. The result is a proof being verified inside another proof. The Noir circuit you write is not recursive; the recursion happens at the Aztec protocol level when the private function execution (including the `verify_honk_proof` call) is proven. Full Working Example The complete code for this tutorial is available in the [docs/examples](https://github.com/AztecProtocol/aztec-packages/tree/v5.0.0-rc.2/docs/examples) directory. Clone it to follow along or use it as a reference. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before starting, ensure you have the following installed and configured: * Node.js (v22 or later) * yarn package manager * Aztec CLI (version v5.0.0-rc.2) * Nargo * Familiarity with [Noir syntax](https://noir-lang.org/docs) and [Aztec contract basics](/developers/testnet/docs/aztec-nr.md) Install the required tools: ``` # Install Aztec CLI VERSION=5.0.0-rc.2 bash -i <(curl -sL https://install.aztec.network/5.0.0-rc.2) ``` ## Part 1: Understanding the Architecture[​](#part-1-understanding-the-architecture "Direct link to Part 1: Understanding the Architecture") ### The Core Problem[​](#the-core-problem "Direct link to The Core Problem") Aztec contracts have inherent [limits on function inputs and transaction complexity](/developers/testnet/docs/resources/considerations/limitations.md#circuit-limitations). These constraints stem from the circuit-based nature of private execution. When your computation requires more inputs than these limits allow, or when the computation itself is too complex to fit in a single function, **recursive proof verification** provides an escape hatch. For example, consider a machine learning inference that needs 10,000 input features, or a Merkle tree verification with 1,000 leaves. These cannot fit within a single Aztec function's input constraints. Instead, you can: 1. Perform the computation offchain in a vanilla Noir circuit with no input limits 2. Generate a proof of correct execution 3. Verify only the proof onchain (115 fields for VK + 457-508 fields for proof + N public inputs) This pattern transforms arbitrarily large computations into fixed-size proof verification. ### Data Flow[​](#data-flow "Direct link to Data Flow") The recursive verification pattern follows this data flow: 1. **Circuit Definition**: Write a Noir circuit that defines the computation you want to prove 2. **Compilation**: Compile the circuit with `aztec-nargo compile` (or your own `nargo compile` install) to produce bytecode 3. **Proof Generation**: Execute the circuit offchain and generate an UltraHonk proof using [Barretenberg](https://github.com/AztecProtocol/barretenberg) 4. **Onchain Verification**: Submit the proof to an Aztec contract that verifies it using the stored [verification key](/developers/testnet/docs/resources/glossary.md#verification-key) hash **Why this separation matters**: The circuit defines *what* you're proving. The proof is *evidence* that you executed the circuit correctly with valid inputs. The onchain verifier checks the evidence without re-running the computation. This is what makes ZK proofs powerful: verification is orders of magnitude cheaper than computation. ### Why Verify Proofs in Aztec Contracts?[​](#why-verify-proofs-in-aztec-contracts "Direct link to Why Verify Proofs in Aztec Contracts?") Proof verification enables several patterns: * **Bypassing Input Limits**: Aztec private functions have strict input constraints. A proof verification call uses \~624 fields (115 VK + 508 proof + 1 public input), but can attest to computations with arbitrarily many inputs. For example, proving membership in a set of 10,000 elements becomes a fixed-size verification. * **Cross-System Verification**: Verify proofs generated by external Noir circuits within your Aztec application. This enables composability: your contract can trust computations performed by other systems without those systems needing to be Aztec-native. * **Batching Operations**: Aggregate multiple operations into a single proof. Instead of making N separate contract calls, prove all N operations were done correctly and verify once. ### Why Use Aztec for Proof Verification?[​](#why-use-aztec-for-proof-verification "Direct link to Why Use Aztec for Proof Verification?") Aztec provides a unique advantage: **private function execution**. When you verify a proof in an Aztec private function: 1. The proof verification happens inside a zero-knowledge circuit 2. The inputs to verification (the proof itself) can remain private 3. You can compose proof verification with other private operations This enables patterns impossible on transparent blockchains, like proving you have a valid credential without revealing which credential or when you obtained it. ### UX Considerations: Multiple Proof Generation[​](#ux-considerations-multiple-proof-generation "Direct link to UX Considerations: Multiple Proof Generation") When using [recursive verification](https://noir-lang.org/docs/noir/standard_library/recursion) in Aztec, users experience **two distinct proof generation phases**: 1. **Noir Proof Generation** (application-specific): * Happens before interacting with the Aztec contract * Proves the computation (e.g., "I know values x and y where x ≠ y") * Time depends on circuit complexity (seconds to minutes) * Produces the proof and verification key that will be verified 2. **Aztec Transaction Proof** (protocol-level): * Generated by the [PXE](/developers/testnet/docs/foundational-topics/pxe.md) when calling the private function * Proves correct execution of the Aztec contract (including the `verify_honk_proof` call) With this foundation in mind, let's build a complete example. You'll create a Noir circuit, generate a proof, and verify it inside an Aztec contract. ## Part 2: Writing the Noir Circuit[​](#part-2-writing-the-noir-circuit "Direct link to Part 2: Writing the Noir Circuit") Start by writing a simple circuit that proves two field values are not equal. This minimal example demonstrates the core pattern—you can extend it for more complex computations like Merkle proofs, credential verification, or something else entirely. ### Create the Circuit Project[​](#create-the-circuit-project "Direct link to Create the Circuit Project") Use `aztec-nargo new` to generate the project structure (the Aztec installer ships `nargo` as `aztec-nargo`; substitute your own `nargo` if its version matches `aztec-nargo --version`): ``` aztec-nargo new circuit ``` This creates the following structure: ``` circuit/ ├── src/ │ └── main.nr # Circuit code └── Nargo.toml # Circuit configuration ``` ### Circuit Code[​](#circuit-code "Direct link to Circuit Code") Replace the contents of `circuit/src/main.nr` with: circuit ``` fn main(x: u64, y: pub u64) { assert(x != y); } #[test] fn test_main() { main(1, 2); } ``` > [Source code: docs/examples/circuits/hello\_circuit/src/main.nr#L1-L10](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/circuits/hello_circuit/src/main.nr#L1-L10) This is intentionally minimal to focus on the verification pattern. In production, you would replace `assert(x != y)` with meaningful computations like: * Merkle tree membership proofs * Hash preimage verification * Range proofs (proving a value is within bounds) * Credential verification * Email verification (proving you received an email from a domain without revealing its contents, like [zkEmail](https://www.prove.email/)) ### Understanding Private vs Public Inputs[​](#understanding-private-vs-public-inputs "Direct link to Understanding Private vs Public Inputs") The circuit has two inputs with different visibility: * `x: Field` - A **private input** known only to the prover. This value is never revealed onchain or included in the proof data. The verifier cannot determine what value was used—only that *some* valid value exists. * `y: pub Field` - A **public input** that is visible to the verifier. This value is included in the proof data, but since proof verification happens within a private function, it isn't exposed onchain unless you explicitly reveal it. **Why this distinction matters**: The circuit asserts that `x != y`. The prover demonstrates they know a secret value `x` that differs from the public value `y`. Public inputs don't have to come from the caller. During verification, the Aztec contract can read values from its own storage and use them as public inputs. This pattern ties the proof to contract state—the prover must generate a proof against the *current* stored value and cannot substitute a different public input. To make the "public input" truly public, the contract developer can enqueue a public function call from the private function that verifies the proof, passing the public input to a public function to be logged or verified against public state. For example, you could create a zkpassport proof demonstrating that you are over a certain age. The proof is verified in a private function, then the age (the public input) is passed to a public function where it's compared against a mutable threshold in public storage. ### Circuit Configuration[​](#circuit-configuration "Direct link to Circuit Configuration") Update `circuit/Nargo.toml` (see [Noir crates and packages](https://noir-lang.org/docs/noir/modules_packages_crates/crates_and_packages) for more details): circuit\_nargo\_toml ``` [package] name = "hello_circuit" type = "bin" authors = [""] [dependencies] ``` > [Source code: docs/examples/circuits/hello\_circuit/Nargo.toml#L1-L8](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/circuits/hello_circuit/Nargo.toml#L1-L8) **Note**: This is a vanilla Noir circuit, not an Aztec contract. It has `type = "bin"` (binary) and no Aztec dependencies. The circuit is compiled with `nargo`, not `aztec compile`. This distinction is important—you can verify proofs from *any* Noir circuit inside Aztec contracts. ### Compile the Circuit[​](#compile-the-circuit "Direct link to Compile the Circuit") ``` cd circuit aztec-nargo compile ``` This generates `target/hello_circuit.json` containing: * **Bytecode**: The compiled circuit representation * **ABI (Application Binary Interface)**: Describes the circuit's inputs and outputs, including which are public The TypeScript code uses the ABI to correctly format inputs during witness generation. ### Test the Circuit[​](#test-the-circuit "Direct link to Test the Circuit") ``` aztec-nargo test ``` Expected output: ``` [hello_circuit] Running 1 test function [hello_circuit] Testing test_main ... ok [hello_circuit] 1 test passed ``` **Tip**: Circuit tests run without generating proofs, making them fast for development. Use them to verify your circuit logic before the more expensive proof generation step. ## Part 3: Writing the Aztec Contract[​](#part-3-writing-the-aztec-contract "Direct link to Part 3: Writing the Aztec Contract") The Aztec contract stores the verification key hash and verifies proofs submitted by users. When a valid proof is submitted, it increments a counter for the caller. ### Why This Contract Design?[​](#why-this-contract-design "Direct link to Why This Contract Design?") The contract demonstrates several important patterns: 1. **VK Hash Storage**: Instead of storing the full 115-field verification key onchain (expensive), we store only its hash (1 field). The prover submits the full VK with each proof, and the contract verifies it matches the stored hash. 2. **Private-to-Public Flow**: Proof verification happens in a [private function](/developers/testnet/docs/aztec-nr/framework-description/functions/visibility.md) (generating a ZK proof of the verification), but the counter update happens in a public function (visible state change). This separation is fundamental to Aztec's architecture. 3. **Self-Only Public Functions**: The `_increment_public` function can only be called by the contract itself, not external accounts (similar to `internal` functions in Solidity). This ensures the counter can only be modified after successful proof verification. ### Create the Contract Project[​](#create-the-contract-project "Direct link to Create the Contract Project") Use `aztec new` to generate the workspace: ``` aztec new ValueNotEqual ``` This creates a two-crate workspace under `ValueNotEqual/`: the contract crate is named `ValueNotEqual_contract` and the test crate is named `ValueNotEqual_test`, both derived from the positional argument. The Noir `contract` identifier declared inside `main.nr` is independent of the crate name and determines the compiled artifact filename. ``` ValueNotEqual/ ├── Nargo.toml # [workspace] members ├── ValueNotEqual_contract/ │ ├── src/ │ │ └── main.nr # Contract code │ └── Nargo.toml # Contract package (type = "contract") └── ValueNotEqual_test/ ├── src/ │ └── lib.nr # Noir tests └── Nargo.toml # Test package (type = "lib") ``` ### Contract Configuration[​](#contract-configuration "Direct link to Contract Configuration") Update `ValueNotEqual/ValueNotEqual_contract/Nargo.toml` with the required dependencies: ``` [package] name = "ValueNotEqual_contract" type = "contract" authors = ["[YOUR_NAME]"] [dependencies] aztec = { git = "https://github.com/AztecProtocol/aztec-nr/", tag = "v5.0.0-rc.2", directory = "aztec" } bb_proof_verification = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v5.0.0-rc.2", directory = "barretenberg/noir/bb_proof_verification" } ``` **Key differences from the circuit's Nargo.toml** (in `ValueNotEqual/ValueNotEqual_contract/Nargo.toml`): * `type = "contract"` (not `"bin"`) * Depends on `aztec` for Aztec-specific features * Depends on `bb_proof_verification` for `verify_honk_proof` ### Contract Structure[​](#contract-structure "Direct link to Contract Structure") Replace the contents of `ValueNotEqual/ValueNotEqual_contract/src/main.nr` with: full\_contract ``` use aztec::macros::aztec; #[aztec] pub contract ValueNotEqual { use aztec::{ macros::{functions::{external, initializer, only_self, view}, storage::storage}, oracle::logging::debug_log_format, protocol::{address::AztecAddress, traits::ToField}, state_vars::{Map, PublicImmutable, PublicMutable}, }; use bb_proof_verification::{UltraHonkVerificationKey, UltraHonkZKProof, verify_honk_proof}; #[storage] struct Storage { counters: Map, Context>, vk_hash: PublicImmutable, } #[initializer] #[external("public")] fn constructor(headstart: Field, owner: AztecAddress, vk_hash: Field) { self.storage.counters.at(owner).write(headstart); self.storage.vk_hash.initialize(vk_hash); } #[external("private")] fn increment( owner: AztecAddress, verification_key: UltraHonkVerificationKey, proof: UltraHonkZKProof, public_inputs: [Field; 1], ) { debug_log_format("Incrementing counter for owner {0}", [owner.to_field()]); // Read the stored VK hash - this is readable from private context // because PublicImmutable values are committed at deployment let vk_hash = self.storage.vk_hash.read(); // Verify the proof - this is the core operation // The function checks: // 1. The VK hashes to the stored vk_hash // 2. The proof is valid for the given VK and public inputs verify_honk_proof(verification_key, proof, public_inputs, vk_hash); // If we reach here, the proof is valid // Enqueue a public function call to update state self.enqueue_self._increment_public(owner); } #[only_self] #[external("public")] fn _increment_public(owner: AztecAddress) { let current = self.storage.counters.at(owner).read(); self.storage.counters.at(owner).write(current + 1); } #[view] #[external("public")] fn get_counter(owner: AztecAddress) -> Field { self.storage.counters.at(owner).read() } } ``` > [Source code: docs/examples/contracts/recursive\_verification\_contract/src/main.nr#L1-L64](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/recursive_verification_contract/src/main.nr#L1-L64) Clear the scaffold's placeholder test The scaffolded `ValueNotEqual/ValueNotEqual_test/src/lib.nr` imports the default contract name (`Main`) we just replaced above, so it now fails to compile. Tests aren't used in this tutorial — replace its contents with a single-line stub so `aztec compile` stays clean: ``` // Tests are out of scope for this tutorial. See https://docs.aztec.network/aztec-nr/testing_contracts for examples. ``` ### Storage Variables Explained[​](#storage-variables-explained "Direct link to Storage Variables Explained") The contract uses two [storage types](/developers/testnet/docs/aztec-nr/framework-description/state_variables.md) with different characteristics: **`vk_hash: PublicImmutable`** `PublicImmutable` is perfect for values that: * Are set once during contract initialization * Never change after deployment * Need to be readable from both public and private contexts The VK hash fits all these criteria. Once you deploy a contract to verify proofs from a specific circuit, the circuit (and thus its VK) shouldn't change. **Why store the hash instead of the full VK?** * Storage costs: 1 field vs 115 fields * The prover already has the full VK (needed to generate the proof) * Hash verification is cheap compared to storing/loading 115 fields **`counters: Map>`** `PublicMutable` is used for values that: * Change over time * Are updated by public functions * Need to be visible onchain The counter must be `PublicMutable` because it's modified by `_increment_public`, a public function. Private functions cannot directly write to public state; they can only enqueue public function calls. ### Function Breakdown[​](#function-breakdown "Direct link to Function Breakdown") **1. `constructor` (public initializer)** ``` #[initializer] #[external("public")] fn constructor(headstart: Field, owner: AztecAddress, vk_hash: Field) { self.storage.counters.at(owner).write(headstart); self.storage.vk_hash.initialize(vk_hash); } ``` * `#[initializer]`: Marks this as the constructor, called once during deployment * `#[external("public")]`: Executes publicly (visible onchain) * Sets the initial counter value for the owner * Stores the VK hash using `initialize()` (required for `PublicImmutable`) **2. `increment` (private function)** ``` #[external("private")] fn increment( owner: AztecAddress, verification_key: UltraHonkVerificationKey, proof: UltraHonkZKProof, public_inputs: [Field; 1], ) { let vk_hash = self.storage.vk_hash.read(); verify_honk_proof(verification_key, proof, public_inputs, vk_hash); self.enqueue_self._increment_public(owner); } ``` * `#[external("private")]`: Executes privately (generates a ZK proof of execution in the PXE) * Reads VK hash from storage (allowed because `PublicImmutable` is readable in private context) * Calls `verify_honk_proof()` which: * Computes the hash of the provided verification key * Checks it matches the stored `vk_hash` * Verifies the proof against the VK and public inputs * Fails (reverts) if any check fails * Uses `enqueue_self._increment_public(owner)` to schedule a public function call **Why `enqueue_self` instead of a direct call?** In Aztec, private functions cannot directly modify public state. Instead, they enqueue public function calls that execute after the private phase completes. This ensures: * Private execution remains private (no public state reads during private execution) * State updates are atomic (all enqueued calls execute or none do) * The execution order is deterministic **3. `_increment_public` (public, self-only)** ``` #[only_self] #[external("public")] fn _increment_public(owner: AztecAddress) { let current = self.storage.counters.at(owner).read(); self.storage.counters.at(owner).write(current + 1); } ``` * `#[only_self]`: Only callable by the contract itself (via `enqueue_self`) * `#[external("public")]`: Executes publicly * Reads the current counter and increments it **Why `#[only_self]`?** Without this modifier, anyone could call `_increment_public` directly, bypassing proof verification. The `#[only_self]` modifier ensures the function is only reachable through the private `increment` function, which requires a valid proof. **4. `get_counter` (public view)** ``` #[view] #[external("public")] fn get_counter(owner: AztecAddress) -> Field { self.storage.counters.at(owner).read() } ``` * `#[view]`: Read-only function, doesn't modify state * Returns the counter value for any address ## Part 4: TypeScript Setup and Proof Generation[​](#part-4-typescript-setup-and-proof-generation "Direct link to Part 4: TypeScript Setup and Proof Generation") Before compiling the contract or running any TypeScript scripts, set up the project with the necessary configuration files and dependencies. ### Project Setup[​](#project-setup "Direct link to Project Setup") Create the following files in your project root directory. **Create `package.json`:** ``` { "name": "recursive-verification-tutorial", "type": "module", "scripts": { "ccc": "cd ValueNotEqual && aztec compile && aztec codegen target -o ../artifacts", "data": "tsx scripts/generate_data.ts", "recursion": "tsx index.ts" }, "dependencies": { "@aztec/accounts": "5.0.0-rc.2", "@aztec/aztec.js": "5.0.0-rc.2", "@aztec/bb.js": "5.0.0-rc.2", "@aztec/kv-store": "5.0.0-rc.2", "@aztec/noir-contracts.js": "5.0.0-rc.2", "@aztec/noir-noir_js": "5.0.0-rc.2", "@aztec/pxe": "5.0.0-rc.2", "@aztec/wallets": "5.0.0-rc.2", "tsx": "^4.20.6" }, "devDependencies": { "@types/node": "^22.0.0" }, "peerDependencies": { "typescript": "^5.0.0" } } ``` **Create `tsconfig.json`:** ``` { "compilerOptions": { "lib": ["ESNext"], "target": "ESNext", "module": "ESNext", "moduleDetection": "force", "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "verbatimModuleSyntax": true, "noEmit": true, "strict": true, "skipLibCheck": true, "noFallthroughCasesInSwitch": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true } } ``` **Install dependencies:** ``` yarn install ``` This installs all the Aztec packages needed for proof generation and contract interaction. The installation may take a few minutes due to the size of the cryptographic libraries. ### Compile the Contract[​](#compile-the-contract "Direct link to Compile the Contract") Now compile the Aztec contract and generate TypeScript bindings: ``` yarn ccc ``` **What this command does** (see [How to Compile a Contract](/developers/testnet/docs/aztec-nr/compiling_contracts.md) for details): 1. `aztec compile`: Compiles the Noir contract and post-processes it for Aztec (different from `nargo compile`) 2. `aztec codegen`: Generates TypeScript bindings from the contract artifact, enabling type-safe contract interaction This generates: * `ValueNotEqual/target/ValueNotEqual_contract-ValueNotEqual.json` - Contract artifact (bytecode, ABI, etc.) * `artifacts/ValueNotEqual.ts` - TypeScript class for deploying and interacting with the contract ### Proof Generation Script[​](#proof-generation-script "Direct link to Proof Generation Script") The proof generation script executes the circuit offchain and produces the proof data needed for onchain verification. Create `scripts/generate_data.ts`: ``` import circuitJson from "../circuit/target/hello_circuit.json" with { type: "json" }; import { Noir } from "@aztec/noir-noir_js"; import { Barretenberg, UltraHonkBackend, deflattenFields } from "@aztec/bb.js"; import fs from "fs"; import { exit } from "process"; // Step 1: Initialize Barretenberg API (the proving system backend) // Barretenberg is the C++ library that implements UltraHonk // threads: 1 uses single-threaded mode (increase for faster proofs on multi-core machines) const barretenbergAPI = await Barretenberg.new({ threads: 1 }); // Step 2: Create Noir circuit instance from compiled bytecode // This loads the circuit definition so we can execute it const helloWorld = new Noir(circuitJson as any); // Step 3: Execute circuit with inputs to generate witness // The witness is all intermediate values computed during circuit execution // x=1 (private), y=2 (public) - proves that 1 != 2 const { witness: mainWitness } = await helloWorld.execute({ x: 1, y: 2 }); // Step 4: Create UltraHonk backend with circuit bytecode // The backend handles proof generation and verification const mainBackend = new UltraHonkBackend(circuitJson.bytecode, barretenbergAPI); // Step 5: Generate proof targeting the noir-recursive verifier // verifierTarget: 'noir-recursive' creates a proof format suitable for // verification inside another Noir circuit (which is what Aztec contracts are) const mainProofData = await mainBackend.generateProof(mainWitness, { verifierTarget: "noir-recursive", }); // Step 6: Verify proof locally before saving // This catches errors early - if verification fails here, it will fail onchain too const isValid = await mainBackend.verifyProof(mainProofData, { verifierTarget: "noir-recursive", }); console.log(`Proof verification: ${isValid ? "SUCCESS" : "FAILED"}`); // Step 7: Generate recursive artifacts for onchain use // This converts the proof and VK into field element arrays that can be // passed to the Aztec contract const recursiveArtifacts = await mainBackend.generateRecursiveProofArtifacts( mainProofData.proof, mainProofData.publicInputs.length, ); // Step 8: Convert proof to field elements if needed // Some versions return empty proofAsFields, requiring manual conversion let proofAsFields = recursiveArtifacts.proofAsFields; if (proofAsFields.length === 0) { console.log("Using deflattenFields to convert proof..."); proofAsFields = deflattenFields(mainProofData.proof).map((f) => f.toString()); } const vkAsFields = recursiveArtifacts.vkAsFields; console.log(`VK size: ${vkAsFields.length}`); // Should be 115 console.log(`Proof size: ${proofAsFields.length}`); // Should be ~500 console.log(`Public inputs: ${mainProofData.publicInputs.length}`); // Should be 1 // Step 9: Save all data to JSON for contract interaction const data = { vkAsFields: vkAsFields, // 115 field elements - the verification key vkHash: recursiveArtifacts.vkHash, // Hash of VK - stored in contract proofAsFields: proofAsFields, // ~500 field elements - the proof publicInputs: mainProofData.publicInputs.map((p: string) => p.toString()), }; fs.writeFileSync("data.json", JSON.stringify(data, null, 2)); await barretenbergAPI.destroy(); console.log("Done"); exit(); ``` ### Understanding the Proof Generation Pipeline[​](#understanding-the-proof-generation-pipeline "Direct link to Understanding the Proof Generation Pipeline") #### Setup[​](#setup "Direct link to Setup") * Initialize Barretenberg (the cryptographic backend) * Load the compiled circuit #### Witness Generation[​](#witness-generation "Direct link to Witness Generation") ``` const { witness: mainWitness } = await helloWorld.execute({ x: 1, y: 2 }); ``` The witness contains all values computed during circuit execution, not just inputs and outputs, but every intermediate value. The prover needs the witness to construct the proof. The verifier never sees the witness (that's the point of ZK proofs). #### Proof Generation[​](#proof-generation "Direct link to Proof Generation") ``` const mainProofData = await mainBackend.generateProof(mainWitness, { verifierTarget: "noir-recursive", }); ``` **Why `verifierTarget: 'noir-recursive'`?** There are different proof formats optimized for different verifiers: * Native verifiers (standalone programs) * Smart contract verifiers (Solidity) * Recursive verifiers (inside other ZK circuits) Aztec contracts are compiled to ZK circuits, so `verify_honk_proof` runs inside a circuit. We need the recursive-friendly proof format. #### Local Verification[​](#local-verification "Direct link to Local Verification") ``` const isValid = await mainBackend.verifyProof(mainProofData, { verifierTarget: "noir-recursive", }); ``` Always verify locally before submitting onchain. Onchain verification costs gas/fees and takes time. Local verification is free and instant. #### Field Element Conversion[​](#field-element-conversion "Direct link to Field Element Conversion") ZK proofs are arrays of bytes, but Aztec contracts work with field elements. We convert the proof and VK to arrays of 115 and 508 field elements respectively. ``` let proofAsFields = recursiveArtifacts.proofAsFields; if (proofAsFields.length === 0) { console.log("Using deflattenFields to convert proof..."); proofAsFields = deflattenFields(mainProofData.proof).map((f) => f.toString()); } const vkAsFields = recursiveArtifacts.vkAsFields; ``` Some versions of the library return an empty `proofAsFields` array, requiring manual conversion via `deflattenFields`. #### Saving Data for Contract Interaction[​](#saving-data-for-contract-interaction "Direct link to Saving Data for Contract Interaction") ``` const data = { vkAsFields: vkAsFields, vkHash: recursiveArtifacts.vkHash, proofAsFields: proofAsFields, publicInputs: mainProofData.publicInputs.map((p: string) => p.toString()), }; fs.writeFileSync("data.json", JSON.stringify(data, null, 2)); await barretenbergAPI.destroy(); ``` The data is saved as JSON so the deployment script can load it. We call `barretenbergAPI.destroy()` to clean up the WebAssembly resources used by Barretenberg. This is important because Barretenberg allocates significant memory for cryptographic operations, and not destroying it can cause memory leaks in long-running processes. ### Run Proof Generation[​](#run-proof-generation "Direct link to Run Proof Generation") ``` yarn data ``` Expected output: ``` Proof verification: SUCCESS Using deflattenFields to convert proof... VK size: 115 Proof size: 500 Public inputs: 1 Done ``` ### Output Format[​](#output-format "Direct link to Output Format") The generated `data.json` contains: ``` { "vkAsFields": ["0x...", "0x...", ...], // 115 field elements "vkHash": "0x...", // Single field element "proofAsFields": ["0x...", "0x...", ...], // 508 field elements "publicInputs": ["2"] // The public input y=2 } ``` **What each field is used for**: * `vkHash`: Passed to the contract constructor, stored permanently * `vkAsFields`: Passed to `increment()`, verified against stored hash * `proofAsFields`: Passed to `increment()`, verified by `verify_honk_proof` * `publicInputs`: Passed to `increment()`, must match what was used during proof generation ## Part 5: Deploying and Verifying[​](#part-5-deploying-and-verifying "Direct link to Part 5: Deploying and Verifying") The deployment script connects to the Aztec network, creates an account, deploys the contract, and submits a proof for verification. ### Deployment Script[​](#deployment-script "Direct link to Deployment Script") Create `index.ts`: run\_recursion ``` import { SponsoredFeePaymentMethod } from "@aztec/aztec.js/fee"; import type { FieldLike } from "@aztec/aztec.js/abi"; import { getSponsoredFPCInstance } from "./scripts/sponsored_fpc.js"; import { SponsoredFPCContract } from "@aztec/noir-contracts.js/SponsoredFPC"; import { ValueNotEqualContract } from "./artifacts/ValueNotEqual.js"; import { EmbeddedWallet } from "@aztec/wallets/embedded"; import { NO_FROM } from "@aztec/aztec.js/account"; import { Fr } from "@aztec/aztec.js/fields"; import assert from "node:assert"; import fs from "node:fs"; if (!fs.existsSync("data.json")) { console.error( "data.json not found. Run 'yarn data' first to generate proof data.", ); process.exit(1); } const data = JSON.parse(fs.readFileSync("data.json", "utf-8")); export const NODE_URL = process.env.AZTEC_NODE_URL ?? "http://localhost:8080"; // Setup sponsored fee payment - the FPC pays transaction fees for us const sponsoredFPC = await getSponsoredFPCInstance(); const sponsoredPaymentMethod = new SponsoredFeePaymentMethod( sponsoredFPC.address, ); // Initialize wallet and connect to local network // The wallet manages accounts and sends transactions through the PXE export const setupWallet = async (): Promise => { try { // Create wallet with embedded PXE // The wallet manages accounts and connects to the node let wallet = await EmbeddedWallet.create(NODE_URL, { ephemeral: true }); // Register the sponsored FPC so the wallet knows about it await wallet.registerContract(sponsoredFPC, SponsoredFPCContract.artifact); return wallet; } catch (error) { console.error("Failed to setup local network:", error); throw error; } }; async function main() { // Step 1: Setup wallet and create account // Accounts in Aztec are smart contracts (account abstraction) const wallet = await setupWallet(); const manager = await wallet.createSchnorrAccount(Fr.random(), Fr.random()); // Deploy the account contract const deployMethod = await manager.getDeployMethod(); await deployMethod.send({ from: NO_FROM, fee: { paymentMethod: sponsoredPaymentMethod }, }); const accounts = await wallet.getAccounts(); // Step 2: Deploy ValueNotEqual contract // Constructor args: initial counter (10), owner, VK hash const { contract: valueNotEqual } = await ValueNotEqualContract.deploy( wallet, 10, // Initial counter value accounts[0].item, // Owner address data.vkHash as unknown as FieldLike, // VK hash for verification ).send({ from: accounts[0].item, fee: { paymentMethod: sponsoredPaymentMethod }, }); console.log(`Contract deployed at: ${valueNotEqual.address}`); const opts = { from: accounts[0].item, fee: { paymentMethod: sponsoredPaymentMethod }, }; // Step 3: Read initial counter value // simulate() executes without submitting a transaction let counterValue = ( await valueNotEqual.methods .get_counter(accounts[0].item) .simulate({ from: accounts[0].item }) ).result; console.log(`Counter value: ${counterValue}`); // Should be 10 // Step 4: Call increment() with proof data // This creates a transaction that: // 1. Executes the private increment() function (client-side) // 2. Generates a ZK proof of correct execution // 3. Submits the proof to the network // 4. Network verifies the proof // 5. Executes enqueued _increment_public() const interaction = await valueNotEqual.methods.increment( accounts[0].item, data.vkAsFields as unknown as FieldLike[], // 115 field VK data.proofAsFields as unknown as FieldLike[], // 508 field proof data.publicInputs as unknown as FieldLike[], // Public inputs ); // Step 5: Send transaction and wait for inclusion await interaction.send(opts); // Step 6: Read updated counter counterValue = ( await valueNotEqual.methods .get_counter(accounts[0].item) .simulate({ from: accounts[0].item }) ).result; console.log(`Counter value: ${counterValue}`); // Should be 11 assert(counterValue === 11n, "Counter should be 11 after verification"); } main().catch((error) => { console.error(error); process.exit(1); }); ``` > [Source code: docs/examples/ts/recursive\_verification/index.ts#L1-L121](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/recursive_verification/index.ts#L1-L121) ### Understanding the Deployment Script[​](#understanding-the-deployment-script "Direct link to Understanding the Deployment Script") #### Sponsored Fee Payment[​](#sponsored-fee-payment "Direct link to Sponsored Fee Payment") Aztec transactions require fees. For testing, we use a Sponsored Fee Payment Contract (FPC) that pays fees on behalf of users: ``` const sponsoredFPC = await getSponsoredFPCInstance(); const sponsoredPaymentMethod = new SponsoredFeePaymentMethod( sponsoredFPC.address, ); ``` In production, you would use real [fee payment methods](/developers/testnet/docs/aztec-js/how_to_pay_fees.md) (native tokens, ERC20, etc.). #### What Happens During `increment().send().wait()`[​](#what-happens-during-incrementsendwait "Direct link to what-happens-during-incrementsendwait") This single line triggers a complex flow: 1. **Private Execution** (client-side, in PXE): * Execute `increment()` with provided arguments * Read `vk_hash` from contract storage * Execute `verify_honk_proof()` inside the private function * Generate the `enqueue_self._increment_public(owner)` call 2. **Proof Generation** (client-side, in PXE): * Generate a ZK proof that the private execution was correct * This proof doesn't reveal inputs (including the 508-field proof!) 3. **Transaction Submission**: * Send the proof + encrypted logs + public function calls to the network 4. **Verification & Public Execution** (onchain): * Network verifies the private execution proof * Execute `_increment_public(owner)` publicly * Update the counter in storage ### Supporting Utility[​](#supporting-utility "Direct link to Supporting Utility") Create `scripts/sponsored_fpc.ts`: sponsored\_fpc ``` import { getContractInstanceFromInstantiationParams } from "@aztec/aztec.js/contracts"; import { Fr } from "@aztec/aztec.js/fields"; import { SponsoredFPCContract } from "@aztec/noir-contracts.js/SponsoredFPC"; const SPONSORED_FPC_SALT = new Fr(BigInt(0)); export async function getSponsoredFPCInstance() { return await getContractInstanceFromInstantiationParams( SponsoredFPCContract.artifact, { salt: SPONSORED_FPC_SALT, }, ); } ``` > [Source code: docs/examples/ts/recursive\_verification/scripts/sponsored\_fpc.ts#L1-L16](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/recursive_verification/scripts/sponsored_fpc.ts#L1-L16) This utility computes the address of the pre-deployed sponsored FPC contract. The salt ensures we get the same address every time. For more information about fee payment options, see [Paying Fees](/developers/testnet/docs/aztec-js/how_to_pay_fees.md). ### Start the Local Network[​](#start-the-local-network "Direct link to Start the Local Network") In a separate terminal, start the [Aztec local network](/developers/testnet/getting_started_on_local_network.md): ``` aztec start --local-network ``` **What this starts**: * **Anvil**: A local Ethereum node (L1) * **Aztec Node**: The L2 rollup node * **PXE**: Private eXecution Environment (embedded in node for local development) Wait for the network to fully initialize. You should see logs indicating readiness. The PXE will be available at `http://localhost:8080`. ### Deploy and Verify[​](#deploy-and-verify "Direct link to Deploy and Verify") Run the deployment script: ``` yarn recursion ``` Expected output: ``` Contract deployed at: 0x... Counter value: 10 Counter value: 11 ``` The counter starts at 10 (set during deployment), and after successful proof verification, it increments to 11. This confirms that the Noir proof was verified inside the Aztec contract. ## Quick Reference[​](#quick-reference "Direct link to Quick Reference") If you want to run all commands at once, or if you're starting fresh, here's the complete workflow. You can also reference the [full working example](https://github.com/AztecProtocol/aztec-packages/tree/v5.0.0-rc.2/docs/examples) in the main repository. ``` # Install dependencies (after creating package.json and tsconfig.json) yarn install # Compile the Noir circuit cd circuit && aztec-nargo compile && cd .. # Compile the Aztec contract and generate TypeScript bindings yarn ccc # Generate proof data yarn data # Start the local network (in a separate terminal) aztec start --local-network # Deploy and verify yarn recursion ``` ## Next Steps[​](#next-steps "Direct link to Next Steps") Now that you understand the basics of proof verification in Aztec contracts, explore these topics: * **Simpler Contract Examples**: If you're new to Aztec contracts, the [Counter Tutorial](/developers/testnet/docs/tutorials/contract_tutorials/counter_contract.md) provides a gentler introduction to contract development patterns. * **Multiple Public Inputs**: Extend the circuit to have multiple public inputs. Update `public_inputs: [Field; 1]` in the contract to match. * **Noir Language Reference**: Explore advanced Noir features like loops, arrays, and standard library functions at [noir-lang.org](https://noir-lang.org/docs). --- # Private Token Contract ## The Privacy Challenge: Mental Health Benefits at Giggle[​](#the-privacy-challenge-mental-health-benefits-at-giggle "Direct link to The Privacy Challenge: Mental Health Benefits at Giggle") Giggle (a fictional tech company) wants to support their employees' mental health by providing BOB tokens that can be spent at Bob's Psychology Clinic. However, employees have a crucial requirement: **complete privacy**. They don't want Giggle to know: * How many BOB tokens they've actually used * When they're using mental health services * Their therapy patterns or frequency In this tutorial, we'll build a token contract that allows Giggle to mint BOB tokens for employees while ensuring complete privacy in how those tokens are spent. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") This is an intermediate tutorial that assumes you have: * Completed the [Counter Contract tutorial](/developers/testnet/docs/tutorials/contract_tutorials/counter_contract.md) * A Running Aztec local network (see the Counter tutorial for setup) * Basic understanding of Aztec.nr syntax and structure * Aztec toolchain installed (`VERSION=5.0.0-rc.2 bash -i <(curl -sL https://install.aztec.network/5.0.0-rc.2)`) If you haven't completed the Counter Contract tutorial, please do so first as we'll skip the basic setup steps covered there. ## What We're Building[​](#what-were-building "Direct link to What We're Building") We'll create BOB tokens with: * **Public and Private minting**: Giggle can mint tokens in private or public * **Public and Private transfers**: Employees can spend tokens at Bob's clinic with full privacy ### Project Setup[​](#project-setup "Direct link to Project Setup") Let's create a simple yarn + aztec.nr project: ``` aztec new bob_token cd bob_token yarn init -y # This is to ensure yarn uses node_modules instead of pnp for dependency installation yarn config set nodeLinker node-modules yarn add @aztec/aztec.js@v5.0.0-rc.2 @aztec/accounts@v5.0.0-rc.2 @aztec/kv-store@v5.0.0-rc.2 @aztec/wallets@v5.0.0-rc.2 ``` ## Contract structure[​](#contract-structure "Direct link to Contract structure") The `aztec new` command created a workspace with two crates: a `bob_token_contract` crate for your smart contract code and a `bob_token_test` crate for Noir tests. In `bob_token_contract/src/main.nr` we have a proto-contract. Let's replace it with a simple starting point: ``` use aztec::macros::aztec; #[aztec] pub contract BobToken { // We'll build the mental health token here } ``` Clear the scaffold's placeholder test The scaffolded `bob_token_test/src/lib.nr` imports the default contract name (`Main`) we just replaced above, so it now fails to compile. Tests aren't used in this tutorial — replace its contents with a single-line stub so `aztec compile` stays clean: ``` // Tests are out of scope for this tutorial. See https://docs.aztec.network/aztec-nr/testing_contracts for examples. ``` The `#[aztec]` macro transforms our contract code to work with Aztec's privacy protocol. Make sure the Aztec.nr library is listed as a dependency in `bob_token_contract/Nargo.toml`: ``` [package] name = "bob_token_contract" type = "contract" [dependencies] aztec = { git = "https://github.com/AztecProtocol/aztec-nr/", tag = "v5.0.0-rc.2", directory = "aztec" } ``` Since we're here, let's import more specific stuff from this library: ``` #[aztec] pub contract BobToken { use aztec::{ macros::{functions::{external, initializer, only_self}, storage::storage}, messages::delivery::MessageDelivery, protocol::address::AztecAddress, state_vars::{Map, Owned, PublicMutable}, }; } ``` These are the different macros we need to define the visibility of functions, and some handy types and functions. note You may see "unused import" warnings from your IDE or compiler for `only_self`, `MessageDelivery`, and `Owned`. That's expected at this stage — we'll start using them in Part 2 when we add the private half of the contract. ## Building the Mental Health Token System[​](#building-the-mental-health-token-system "Direct link to Building the Mental Health Token System") ### The Privacy Architecture[​](#the-privacy-architecture "Direct link to The Privacy Architecture") Before we start coding, let's understand how privacy works in our mental health token system: 1. **Public Layer**: Giggle mints tokens publicly - transparent and auditable 2. **Private Layer**: Employees transfer and spend tokens privately - completely confidential 3. **Cross-layer Transfer**: Employees can move tokens between public and private domains as needed This architecture ensures that while the initial allocation is transparent (important for corporate governance), the actual usage remains completely private. Privacy Note In Aztec, private state uses a UTXO model with "notes" - think of them as encrypted receipts that only the owner can decrypt and spend. When an employee receives BOB tokens privately, they get encrypted notes that only they can see and use. Let's start building! Remember to import types as needed - your IDE's Noir extension can help with auto-imports. ## Part 1: Public Minting for Transparency[​](#part-1-public-minting-for-transparency "Direct link to Part 1: Public Minting for Transparency") Let's start with the public components that Giggle will use to mint and track initial token allocations. ### Setting Up Storage[​](#setting-up-storage "Direct link to Setting Up Storage") First, define the storage for our BOB tokens: ``` #[storage] struct Storage { // Giggle's admin address owner: PublicMutable, // Public balances - visible for transparency public_balances: Map, Context>, } ``` This storage structure allows: * `owner`: Stores Giggle's admin address (who can mint tokens) * `public_balances`: Tracks public token balances (employees can verify their allocations) Why Public Balances? While employees want privacy when spending, having public balances during minting allows: 1. Employees to verify they received their mental health benefits 2. Auditors to confirm fair distribution 3. Transparency in the allocation process ### Initializing Giggle as Owner[​](#initializing-giggle-as-owner "Direct link to Initializing Giggle as Owner") When deploying the contract, we need to set Giggle as the owner: setup ``` #[initializer] #[external("public")] fn setup() { // Giggle becomes the owner who can mint mental health tokens self.storage.owner.write(self.msg_sender()); } ``` > [Source code: docs/examples/contracts/bob\_token\_contract/src/main.nr#L32-L39](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/bob_token_contract/src/main.nr#L32-L39) The `#[initializer]` decorator ensures this runs once during deployment. Only Giggle's address will have the power to mint new BOB tokens for employees. ### Minting BOB Tokens for Employees[​](#minting-bob-tokens-for-employees "Direct link to Minting BOB Tokens for Employees") Giggle needs a way to allocate mental health tokens to employees: mint\_public ``` #[external("public")] fn mint_public(employee: AztecAddress, amount: u64) { // Only Giggle can mint tokens assert_eq(self.msg_sender(), self.storage.owner.read(), "Only Giggle can mint BOB tokens"); // Add tokens to employee's public balance let current_balance = self.storage.public_balances.at(employee).read(); self.storage.public_balances.at(employee).write(current_balance + amount); } ``` > [Source code: docs/examples/contracts/bob\_token\_contract/src/main.nr#L41-L51](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/bob_token_contract/src/main.nr#L41-L51) This public minting function: 1. Verifies that only Giggle (the owner) is calling 2. Transparently adds tokens to the employee's public balance 3. Creates an auditable record of the allocation Real-World Scenario Imagine Giggle allocating 100 BOB tokens to each employee at the start of the year. This public minting ensures employees can verify they received their benefits, while their actual usage remains private. ### Public Transfers (Optional Transparency)[​](#public-transfers-optional-transparency "Direct link to Public Transfers (Optional Transparency)") While most transfers will be private, we'll add public transfers for cases where transparency is desired: transfer\_public ``` #[external("public")] fn transfer_public(to: AztecAddress, amount: u64) { let sender = self.msg_sender(); let sender_balance = self.storage.public_balances.at(sender).read(); assert(sender_balance >= amount, "Insufficient BOB tokens"); // Deduct from sender self.storage.public_balances.at(sender).write(sender_balance - amount); // Add to recipient let recipient_balance = self.storage.public_balances.at(to).read(); self.storage.public_balances.at(to).write(recipient_balance + amount); } ``` > [Source code: docs/examples/contracts/bob\_token\_contract/src/main.nr#L53-L67](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/bob_token_contract/src/main.nr#L53-L67) This might be used when: * An employee transfers tokens to a colleague who's comfortable with transparency * Bob's clinic makes a public refund * Any scenario where privacy isn't required ### Admin Transfer (Future-Proofing)[​](#admin-transfer-future-proofing "Direct link to Admin Transfer (Future-Proofing)") In case Giggle's mental health program administration changes: transfer\_ownership ``` #[external("public")] fn transfer_ownership(new_owner: AztecAddress) { assert_eq( self.msg_sender(), self.storage.owner.read(), "Only current admin can transfer ownership", ); self.storage.owner.write(new_owner); } ``` > [Source code: docs/examples/contracts/bob\_token\_contract/src/main.nr#L69-L79](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/bob_token_contract/src/main.nr#L69-L79) ## Your First Deployment - Let's See It Work[​](#your-first-deployment---lets-see-it-work "Direct link to Your First Deployment - Let's See It Work") ### Compile Your Contract[​](#compile-your-contract "Direct link to Compile Your Contract") You've written enough code to have a working token! Let's compile and test it: ``` aztec compile ``` ### Generate TypeScript Interface[​](#generate-typescript-interface "Direct link to Generate TypeScript Interface") ``` aztec codegen target --outdir artifacts ``` You should now have a nice typescript interface in a new `artifacts` folder. Pretty useful! ### Deploy and Test[​](#deploy-and-test "Direct link to Deploy and Test") Create `index.ts`. We will connect to our running local network and its wallet, then deploy the test accounts and get three wallets out of it. Ensure that your local network is running: ``` aztec start --local-network ``` Then we will use the `giggleWallet` to deploy our contract, mint 100 BOB to Alice, then transfer 10 of those to Bob's Clinic publicly... for now. Let's go: ``` import { BobTokenContract } from "./artifacts/BobToken.js"; import { AztecAddress } from "@aztec/aztec.js/addresses"; import { createAztecNodeClient } from "@aztec/aztec.js/node"; import { getInitialTestAccountsData } from "@aztec/accounts/testing"; import { EmbeddedWallet } from "@aztec/wallets/embedded"; async function main() { // Connect to local network const node = createAztecNodeClient("http://localhost:8080"); // `ephemeral: true` keeps PXE state in memory, so restarting the local // network won't leave this script pointing at stale block hashes. const wallet = await EmbeddedWallet.create(node, { ephemeral: true }); const [giggleWalletData, aliceWalletData, bobClinicWalletData] = await getInitialTestAccountsData(); const giggleAccountManager = await wallet.createSchnorrInitializerlessAccount( giggleWalletData.secret, giggleWalletData.salt, ); const aliceAccountManager = await wallet.createSchnorrInitializerlessAccount( aliceWalletData.secret, aliceWalletData.salt, ); const bobClinicAccountManager = await wallet.createSchnorrInitializerlessAccount( bobClinicWalletData.secret, bobClinicWalletData.salt, ); const giggleAddress = giggleAccountManager.address; const aliceAddress = aliceAccountManager.address; const bobClinicAddress = bobClinicAccountManager.address; const { contract: bobToken } = await BobTokenContract.deploy(wallet).send({ from: giggleAddress, }); await bobToken.methods .mint_public(aliceAddress, 100n) .send({ from: giggleAddress }); await bobToken.methods .transfer_public(bobClinicAddress, 10n) .send({ from: aliceAddress }); } main().catch(console.error); ``` Run your test: ``` npx tsx index.ts ``` tip What's this `tsx` dark magic? `tsx` is a tool that compiles and runs TypeScript using reasonable defaults. `npx` will auto-install it if you don't have it. If you'd prefer to install it explicitly, run `yarn add -D tsx` first. Ephemeral PXE state We pass `{ ephemeral: true }` to `EmbeddedWallet.create`. This tells the PXE to keep its state in memory instead of writing it to `pxe_data_*` / `wallet_data_*` folders on disk. If you ever stop and restart your local network (or wipe its state), the next run starts clean instead of failing with errors like `No local block hash for block number …` because on-disk PXE state no longer matches the chain. For real applications you typically want persistent state, but for tutorials that spin up a fresh network each run, ephemeral is the safer default. ### 🎉 Celebrate[​](#-celebrate "Direct link to 🎉 Celebrate") Congratulations! You've just deployed a working token contract on Aztec! You can: * ✅ Mint BOB tokens as Giggle * ✅ Transfer tokens between employees * ✅ Track balances publicly But there's a problem... **Giggle can see everything!** They know: * Who's transferring tokens * How much is being spent * When mental health services are being used This defeats the whole purpose of our mental health privacy initiative. Let's fix this by adding private functionality! ## Part 2: Adding Privacy - The Real Magic Begins[​](#part-2-adding-privacy---the-real-magic-begins "Direct link to Part 2: Adding Privacy - The Real Magic Begins") Now let's add the privacy features that make our mental health benefits truly confidential. ### Understanding Private Notes[​](#understanding-private-notes "Direct link to Understanding Private Notes") Here's where Aztec's privacy magic happens. Unlike public balances (a single number), private balances are collections of encrypted "notes". Think of it this way: * **Public balance**: "Alice has 100 BOB tokens" (visible to everyone) * **Private balance**: Alice has encrypted notes \[Note1: 30 BOB, Note2: 50 BOB, Note3: 20 BOB] that only she can decrypt When Alice spends 40 BOB tokens at Bob's clinic: 1. She consumes Note1 (30 BOB) and Note2 (50 BOB) = 80 BOB total 2. She creates a new note for Bob's clinic (40 BOB) 3. She creates a "change" note for herself (40 BOB) 4. The consumed notes are nullified (marked as spent) What is a nullifier? A **nullifier** is a unique, one-way tag emitted when a private note is spent. The network adds it to a nullifier tree so the same note can't be spent twice, but because the nullifier is derived from secrets only the note's owner knows, nobody can link a nullifier back to the note it invalidated. See [State Management](/developers/testnet/docs/foundational-topics/state_management.md#private-state) for more. In this case, all that the network sees (including Giggle) is just "something happening to some state in some contract". How cool is that? ### Updating Storage for Privacy[​](#updating-storage-for-privacy "Direct link to Updating Storage for Privacy") For something like balances, you can use a simple library called `balance_set` which abstracts away a custom private Note. A Note is at the core of how private state works in Aztec and you can read about it [here](/developers/testnet/docs/foundational-topics/state_management.md). For now, let's add it by replacing the `[dependencies]` section in `Nargo.toml`: ``` [dependencies] aztec = { git="https://github.com/AztecProtocol/aztec-nr", tag="v5.0.0-rc.2", directory="aztec" } balance_set = { git = "https://github.com/AztecProtocol/aztec-nr/", tag = "v5.0.0-rc.2", directory = "balance-set" } ``` Then import `BalanceSet` in our contract: ``` use aztec::macros::aztec; #[aztec] pub contract BobToken { // ... other imports use balance_set::BalanceSet; // ... } ``` We need to update the contract storage to have private balances as well: storage ``` #[storage] struct Storage { // Giggle's admin address owner: PublicMutable, // Public balances - visible for transparency public_balances: Map, Context>, // Private balances - only the owner can see these private_balances: Owned, Context>, } ``` > [Source code: docs/examples/contracts/bob\_token\_contract/src/main.nr#L19-L30](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/bob_token_contract/src/main.nr#L19-L30) The `private_balances` use `BalanceSet` which manages encrypted notes automatically. ### Moving Tokens to Privateland[​](#moving-tokens-to-privateland "Direct link to Moving Tokens to Privateland") Great, now our contract knows about private balances. Let's implement a method to allow users to move their publicly minted tokens there: public\_to\_private ``` #[external("private")] fn public_to_private(amount: u64) { let sender = self.msg_sender(); // This will enqueue a public function to deduct from public balance self.enqueue_self._deduct_public_balance(sender, amount); // Add to private balance self.storage.private_balances.at(sender).add(amount as u128).deliver( MessageDelivery::onchain_constrained(), ); } ``` > [Source code: docs/examples/contracts/bob\_token\_contract/src/main.nr#L81-L92](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/bob_token_contract/src/main.nr#L81-L92) And the helper function: \_deduct\_public\_balance ``` #[external("public")] #[only_self] fn _deduct_public_balance(owner: AztecAddress, amount: u64) { let balance = self.storage.public_balances.at(owner).read(); assert(balance >= amount, "Insufficient public BOB tokens"); self.storage.public_balances.at(owner).write(balance - amount); } ``` > [Source code: docs/examples/contracts/bob\_token\_contract/src/main.nr#L94-L102](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/bob_token_contract/src/main.nr#L94-L102) By calling `public_to_private` we're telling the network "deduct this amount from my balance" while simultaneously creating a Note with that balance in privateland. ### Private Transfers[​](#private-transfers "Direct link to Private Transfers") Now for the crucial privacy feature - transferring BOB tokens in privacy. This is actually pretty simple: transfer\_private ``` #[external("private")] fn transfer_private(to: AztecAddress, amount: u64) { let sender = self.msg_sender(); // Spend sender's notes (consumes existing notes) self.storage.private_balances.at(sender).sub(amount as u128).deliver( MessageDelivery::onchain_constrained(), ); // Create new notes for recipient self.storage.private_balances.at(to).add(amount as u128).deliver( MessageDelivery::onchain_constrained(), ); } ``` > [Source code: docs/examples/contracts/bob\_token\_contract/src/main.nr#L104-L117](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/bob_token_contract/src/main.nr#L104-L117) This function simply nullifies the sender's notes, while adding them to the recipient. Real-World Impact When an employee uses 50 BOB tokens at Bob's clinic, this private transfer ensures Giggle has no visibility into: * The fact that the employee is seeking mental health services * The frequency of visits * The amount spent on treatment ### Checking Balances[​](#checking-balances "Direct link to Checking Balances") Employees can check their BOB token balances without hitting the network by using utility unconstrained functions: check\_balances ``` #[external("utility")] unconstrained fn private_balance_of(owner: AztecAddress) -> pub u128 { self.storage.private_balances.at(owner).balance_of() } #[external("utility")] unconstrained fn public_balance_of(owner: AztecAddress) -> pub u64 { self.storage.public_balances.at(owner).read() } ``` > [Source code: docs/examples/contracts/bob\_token\_contract/src/main.nr#L119-L129](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/bob_token_contract/src/main.nr#L119-L129) ## Part 3: Securing Private Minting[​](#part-3-securing-private-minting "Direct link to Part 3: Securing Private Minting") Let's make this a little bit harder, and more interesting. Let's say Giggle doesn't want to mint the tokens in public. Can we have private minting on Aztec? Sure we can. Let's see. ### Understanding Execution Domains[​](#understanding-execution-domains "Direct link to Understanding Execution Domains") Our BOB token system operates in two domains: 1. **Public Domain**: Where Giggle mints tokens transparently 2. **Private Domain**: Where employees spend tokens confidentially The key challenge: How do we ensure only Giggle can mint tokens when the minting happens in a private function? Privacy Trade-off Private functions can't directly read current public state (like who the owner is). They can only read historical public state or enqueue public function calls for validation. ### The Access Control Challenge[​](#the-access-control-challenge "Direct link to The Access Control Challenge") We want Giggle to mint BOB tokens directly to employees' private balances (for maximum privacy), but we need to ensure only Giggle can do this. The challenge: ownership is stored publicly, but private functions can't read current public state. Let's use a clever pattern where private functions enqueue public validation checks. First we make a little helper function in public. Remember, public functions always run *after* private functions, since private functions run client-side. \_assert\_is\_owner ``` #[external("public")] #[only_self] fn _assert_is_owner(address: AztecAddress) { assert_eq(address, self.storage.owner.read(), "Only Giggle can mint BOB tokens"); } ``` > [Source code: docs/examples/contracts/bob\_token\_contract/src/main.nr#L131-L137](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/bob_token_contract/src/main.nr#L131-L137) Now we can add a secure private minting function. It looks pretty easy, and it is, since the whole thing will revert if the public function fails: mint\_private ``` #[external("private")] fn mint_private(employee: AztecAddress, amount: u64) { // Enqueue ownership check (will revert if not Giggle) self.enqueue_self._assert_is_owner(self.msg_sender()); // If check passes, mint tokens privately self.storage.private_balances.at(employee).add(amount as u128).deliver( MessageDelivery::onchain_constrained(), ); } ``` > [Source code: docs/examples/contracts/bob\_token\_contract/src/main.nr#L139-L150](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/bob_token_contract/src/main.nr#L139-L150) This pattern ensures: 1. The private minting executes first (creating the proof) 2. The public ownership check executes after 3. If the check fails, the entire transaction (including the private part) reverts 4. Only Giggle can successfully mint BOB tokens ## Part 4: Converting Back to Public[​](#part-4-converting-back-to-public "Direct link to Part 4: Converting Back to Public") For the sake of completeness, let's also have a function that brings the tokens back to publicland: private\_to\_public ``` #[external("private")] fn private_to_public(amount: u64) { let sender = self.msg_sender(); // Remove from private balance self.storage.private_balances.at(sender).sub(amount as u128).deliver( MessageDelivery::onchain_constrained(), ); // Enqueue public credit self.enqueue_self._credit_public_balance(sender, amount); } #[external("public")] #[only_self] fn _credit_public_balance(owner: AztecAddress, amount: u64) { let balance = self.storage.public_balances.at(owner).read(); self.storage.public_balances.at(owner).write(balance + amount); } ``` > [Source code: docs/examples/contracts/bob\_token\_contract/src/main.nr#L152-L170](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/bob_token_contract/src/main.nr#L152-L170) Now you've made changes to your contract, you need to recompile your contract. Here are the steps from above, for reference: ``` aztec compile aztec codegen target --outdir artifacts ``` ## Testing the Complete Privacy System[​](#testing-the-complete-privacy-system "Direct link to Testing the Complete Privacy System") Before running the updated script, double-check your local network is still running: ``` aztec start --local-network ``` If you stopped it between parts of the tutorial, start it again here. Because we set `ephemeral: true` when creating the wallet, restarting the network is safe — the script won't try to reuse stale PXE state from a previous run. Now that you've implemented all the privacy features, let's update our test script to showcase the full privacy flow: ### Update Your Test Script[​](#update-your-test-script "Direct link to Update Your Test Script") Let's stop being lazy and add a nice little "log" function that just spits out everyone's balances to the console, for example: ``` // at the top of your file async function getBalances( contract: BobTokenContract, aliceAddress: AztecAddress, bobAddress: AztecAddress, ) { await Promise.all([ contract.methods .public_balance_of(aliceAddress) .simulate({ from: aliceAddress }) .then(({ result }) => result), contract.methods .private_balance_of(aliceAddress) .simulate({ from: aliceAddress }) .then(({ result }) => result), contract.methods .public_balance_of(bobAddress) .simulate({ from: bobAddress }) .then(({ result }) => result), contract.methods .private_balance_of(bobAddress) .simulate({ from: bobAddress }) .then(({ result }) => result), ]).then( ([ alicePublicBalance, alicePrivateBalance, bobPublicBalance, bobPrivateBalance, ]) => { console.log( `📊 Alice has ${alicePublicBalance} public BOB tokens and ${alicePrivateBalance} private BOB tokens`, ); console.log( `📊 Bob's Clinic has ${bobPublicBalance} public BOB tokens and ${bobPrivateBalance} private BOB tokens`, ); }, ); } ``` Looks ugly but it does what it says: prints Alice's and Bob's balances. This will make it easier to see our contract working. Now let's add some more stuff to our `index.ts`: ``` async function main() { // ...etc await bobToken.methods .mint_public(aliceAddress, 100n) .send({ from: giggleAddress }); await getBalances(bobToken, aliceAddress, bobClinicAddress); await bobToken.methods .transfer_public(bobClinicAddress, 10n) .send({ from: aliceAddress }); await getBalances(bobToken, aliceAddress, bobClinicAddress); await bobToken.methods.public_to_private(90n).send({ from: aliceAddress }); await getBalances(bobToken, aliceAddress, bobClinicAddress); await bobToken.methods .transfer_private(bobClinicAddress, 50n) .send({ from: aliceAddress }); await getBalances(bobToken, aliceAddress, bobClinicAddress); await bobToken.methods.private_to_public(10n).send({ from: aliceAddress }); await getBalances(bobToken, aliceAddress, bobClinicAddress); await bobToken.methods .mint_private(aliceAddress, 100n) .send({ from: giggleAddress }); await getBalances(bobToken, aliceAddress, bobClinicAddress); } main().catch(console.error); ``` The flow is something like: * Giggle mints Alice 100 BOB in public * Alice transfers 10 BOB to Bob in public * Alice makes the remaining 90 BOB private * Alice transfers 50 of those to Bob, in private * Of the remaining 40 BOB, she makes 10 public again * Giggle mints 100 BOB tokens for Alice, in private Let's give it a try: ``` npx tsx index.ts ``` You should see the complete privacy journey from transparent allocation to confidential usage. The final pair of log lines should look like: ``` 📊 Alice has 10 public BOB tokens and 130 private BOB tokens 📊 Bob's Clinic has 10 public BOB tokens and 50 private BOB tokens ``` If your output doesn't match, double-check that the local network is running and that you started this run with a fresh `aztec start --local-network`. ## Summary[​](#summary "Direct link to Summary") You've built a privacy-preserving token system that solves a real-world problem: enabling corporate mental health benefits while protecting employee privacy. This demonstrates Aztec's unique ability to provide both transparency and privacy where each is most needed. The BOB token shows how blockchain can enable new models of corporate benefits that weren't possible before - where verification and privacy coexist, empowering employees to seek help without fear of judgment or career impact. ### What You Learned[​](#what-you-learned "Direct link to What You Learned") * How to create tokens with both public and private states * How to bridge between public and private domains * How to implement access control across execution contexts * How to build real-world privacy solutions on Aztec ## Going Further: The AIP-20 Token Standard[​](#going-further-the-aip-20-token-standard "Direct link to Going Further: The AIP-20 Token Standard") The BOB token you built in this tutorial implements a simplified version of the patterns formalized in **AIP-20**, Aztec's fungible token standard. AIP-20 extends these patterns with commitment-based transfers for DeFi composability, recursive note consumption for large balances, and tokenized vault support (AIP-4626). Read the full [AIP-20 standard reference](/developers/testnet/docs/aztec-nr/standards/aip-20.md) for details, or explore all [Aztec Contract Standards](/developers/testnet/docs/aztec-nr/standards.md). ### Continue Your Journey[​](#continue-your-journey "Direct link to Continue Your Journey") * Explore [cross-chain communication](/developers/testnet/docs/foundational-topics/ethereum-aztec-messaging.md) to integrate with existing health systems * Learn about [account abstraction](/developers/testnet/docs/foundational-topics/accounts.md) for recovery mechanisms --- # Deposit to Aave from Aztec ## Why DeFi from Aztec?[​](#why-defi-from-aztec "Direct link to Why DeFi from Aztec?") Imagine you hold DAI on Aztec L2. Gas is cheap, transactions are private, but your tokens are just sitting there. What if you could deposit them into Aave on Ethereum, earn yield, and then bring those yield-bearing tokens back to Aztec? In this tutorial, you'll build exactly that: a **cross-chain DeFi bridge** that moves tokens between Aztec and Aave's lending pool on Ethereum. By the end, you'll understand how to compose L1 DeFi protocols with Aztec's cross-chain messaging system. ## What You'll Build[​](#what-youll-build "Direct link to What You'll Build") The diagram below shows the full round-trip, starting from tokens the user already holds on L2: You'll create: * **AaveBridge (L2)** — A Noir contract that burns/mints tokens and sends/consumes cross-chain messages * **AavePortal (L1)** — A Solidity contract that interacts with Aave and handles L1↔L2 messaging * **Mock Aave contracts** — Simplified mocks of Aave's lending pool for local testing * **Integration script** — A TypeScript script that deploys everything and runs the full flow ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * [Aztec local network running at version v5.0.0-rc.2](/developers/testnet/getting_started_on_local_network.md) (includes Aztec CLI and Node.js v24+) * [Hardhat](https://hardhat.org/getting-started) installed for Solidity compilation and deployment * Familiarity with the [Token Bridge tutorial](/developers/testnet/docs/tutorials/js_tutorials/token_bridge.md) (recommended) * Basic understanding of [cross-chain messaging](/developers/testnet/docs/foundational-topics/ethereum-aztec-messaging.md) ## Understanding the Flow[​](#understanding-the-flow "Direct link to Understanding the Flow") The bridge has two directions: **depositing** tokens from L2 into Aave on L1, and **claiming** them back (with yield) on L2. ### Deposit Flow (L2 → Aave)[​](#deposit-flow-l2--aave "Direct link to Deposit Flow (L2 → Aave)") ### Claim Flow (Aave → L2)[​](#claim-flow-aave--l2 "Direct link to Claim Flow (Aave → L2)") ## Project Setup[​](#project-setup "Direct link to Project Setup") Start with the Hardhat + Aztec template. This provides a pre-configured Hardhat project with Aztec dependencies and Solidity compilation settings: note This template is a community-maintained starter. If the repository is unavailable, you can set up a Hardhat project manually and add the `@aztec/*` Solidity remappings from the [cross-chain messaging docs](/developers/testnet/docs/foundational-topics/ethereum-aztec-messaging.md). You may need to update the `@aztec/l1-contracts` tag in the template's `package.json` to match your Aztec version, e.g.: ``` "@aztec/l1-contracts": "git+https://github.com/AztecProtocol/l1-contracts.git#v5.0.0-nightly.20260311" ``` ``` git clone https://github.com/critesjosh/hardhat-aztec-example cd hardhat-aztec-example ``` When complete, your project will have this structure: ``` hardhat-aztec-example/ contracts/ # Solidity contracts (Hardhat default) MockERC20.sol MockAToken.sol MockAavePool.sol AavePortal.sol contracts/aztec/ # Noir contracts aave_bridge/ contract/src/main.nr contract/src/config.nr contract/Nargo.toml aave_bridge_test/ src/Nargo.toml src/lib.nr scripts/ index.ts # Integration script artifacts/ # Generated by aztec codegen ``` Add the Aztec dependencies: ``` yarn add @aztec/aztec.js@5.0.0-rc.2 @aztec/accounts@5.0.0-rc.2 @aztec/wallets@5.0.0-rc.2 @aztec/stdlib@5.0.0-rc.2 @aztec/foundation@5.0.0-rc.2 @aztec/ethereum@5.0.0-rc.2 @aztec/noir-contracts.js@5.0.0-rc.2 @aztec/viem@2.38.2 tsx ``` Start the local network in another terminal: ``` aztec start --local-network ``` ## Part 1: The L2 Bridge Contract[​](#part-1-the-l2-bridge-contract "Direct link to Part 1: The L2 Bridge Contract") The L2 bridge is the simpler side. It doesn't know anything about Aave — it just burns/mints tokens and passes messages. All the Aave-specific logic lives on L1. note The L2 bridge is intentionally protocol-agnostic — it just burns/mints tokens and relays messages. All Aave-specific logic lives on L1. This means you can compose with any L1 protocol without changing your L2 contract. If you've completed the [Token Bridge tutorial](/developers/testnet/docs/tutorials/js_tutorials/token_bridge.md), you'll recognize the pattern and can skim to [Part 2](#part-2-the-ethereum-side). Create the bridge contract: ``` aztec new contracts/aztec/aave_bridge cd contracts/aztec/aave_bridge ``` The `aztec new` command creates a workspace with a `contract` crate and a `test` crate. Replace the generated test file at `test/src/lib.nr` with a basic constructor test: ``` use aztec::protocol::address::{AztecAddress, EthAddress}; use aztec::protocol::traits::FromField; use aztec::test::helpers::test_environment::TestEnvironment; use aave_bridge::AaveBridge; #[test] unconstrained fn test_constructor() { let mut env = TestEnvironment::new(); let deployer = env.create_light_account(); let token = AztecAddress::from_field(1); let portal = EthAddress::from_field(2); let initializer = AaveBridge::interface().constructor(token, portal); let _contract_address = env.deploy("@aave_bridge/AaveBridge").with_public_initializer(deployer, initializer); } ``` The bridge reuses the existing `Token` contract and the `token_portal_content_hash_lib` for content hash functions. Add these dependencies to `contracts/aztec/aave_bridge/aave_bridge_contract/Nargo.toml`: ``` [dependencies] aztec = { git="https://github.com/AztecProtocol/aztec-packages", tag = "v5.0.0-rc.2", directory = "noir-projects/aztec-nr/aztec" } token_portal_content_hash_lib = { git="https://github.com/AztecProtocol/aztec-packages", tag = "v5.0.0-rc.2", directory = "noir-projects/noir-contracts/contracts/libs/token_portal_content_hash_lib" } token = { git="https://github.com/AztecProtocol/aztec-packages", tag = "v5.0.0-rc.2", directory = "noir-projects/noir-contracts/contracts/app/token_contract" } ``` ### Bridge Storage[​](#bridge-storage "Direct link to Bridge Storage") The bridge stores two things: the L2 token address and the L1 portal address. First, create the config module at `contracts/aztec/aave_bridge/aave_bridge_contract/src/config.nr`: config ``` use aztec::protocol::{ address::{AztecAddress, EthAddress}, traits::{Deserialize, Packable, Serialize}, }; use std::meta::derive; #[derive(Deserialize, Eq, Packable, Serialize)] pub struct Config { pub token: AztecAddress, pub portal: EthAddress, } ``` > [Source code: docs/examples/contracts/aave\_bridge/src/config.nr#L1-L13](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/aave_bridge/src/config.nr#L1-L13) Then replace `contracts/aztec/aave_bridge/aave_bridge_contract/src/main.nr`: ``` mod config; // A bridge contract that allows users to deposit tokens into Aave on L1 from Aztec L2, // and claim yield-bearing tokens back on L2. The bridge mirrors TokenBridge's pattern: // all Aave-specific logic lives on L1, while L2 simply burns/mints tokens and passes messages. use aztec::macros::aztec; #[aztec] pub contract AaveBridge { use crate::config::Config; use aztec::{protocol::address::{AztecAddress, EthAddress}, state_vars::PublicImmutable}; use token_portal_content_hash_lib::{ get_mint_to_private_content_hash, get_mint_to_public_content_hash, get_withdraw_content_hash, }; use token::Token; use aztec::macros::{functions::{external, initializer, view}, storage::storage}; #[storage] struct Storage { config: PublicImmutable, } #[external("public")] #[initializer] fn constructor(token: AztecAddress, portal: EthAddress) { self.storage.config.initialize(Config { token, portal }); } #[external("private")] #[view] fn get_config() -> Config { self.storage.config.read() } } ``` Assembling the Contract The code above shows the contract opening — imports, storage, constructor, and a getter — followed by a closing `}`. In the sections below, you'll add more functions **inside** this contract body. Place them before the final `}` so they are part of `pub contract AaveBridge { ... }`. ### Public Claim and Exit[​](#public-claim-and-exit "Direct link to Public Claim and Exit") Add the following functions inside the `AaveBridge` contract body (before the closing `}`). `claim_public` consumes an L1→L2 message and mints tokens. `exit_to_l1_public` burns tokens and sends an L2→L1 message: claim\_public ``` /// Consume an L1->L2 message and mint tokens publicly. /// Called after the L1 AavePortal withdraws from Aave and sends a message. #[external("public")] fn claim_public(to: AztecAddress, amount: u128, secret: Field, message_leaf_index: Field) { let content_hash = get_mint_to_public_content_hash(to, amount); let config = self.storage.config.read(); // Consume message and emit nullifier self.context.consume_l1_to_l2_message( content_hash, secret, config.portal, message_leaf_index, ); // Mint tokens (including any yield from Aave) self.call(Token::at(config.token).mint_to_public(to, amount)); } ``` > [Source code: docs/examples/contracts/aave\_bridge/src/main.nr#L42-L61](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/aave_bridge/src/main.nr#L42-L61) exit\_to\_l1\_public ``` /// Burn tokens publicly and create an L2->L1 message. /// The L1 AavePortal will consume this message and deposit into Aave. #[external("public")] fn exit_to_l1_public( recipient: EthAddress, amount: u128, caller_on_l1: EthAddress, authwit_nonce: Field, ) { let config = self.storage.config.read(); // Send an L2 to L1 message let content = get_withdraw_content_hash(recipient, amount, caller_on_l1); self.context.message_portal(config.portal, content); // Burn tokens self.call(Token::at(config.token).burn_public(self.msg_sender(), amount, authwit_nonce)); } ``` > [Source code: docs/examples/contracts/aave\_bridge/src/main.nr#L89-L108](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/aave_bridge/src/main.nr#L89-L108) The `authwit_nonce` parameter supports [authentication witnesses](/developers/testnet/docs/aztec-js/how_to_use_authwit.md). When the caller is the token owner (`msg.sender`), pass `0` — no authorization witness is needed. If a third party calls this function on behalf of the owner, they must provide a valid nonce from an authwit the owner previously created. ### Private Claim and Exit[​](#private-claim-and-exit "Direct link to Private Claim and Exit") Still inside the contract body, add the private variants. They work the same way but use private token operations. The recipient's address is hidden when claiming privately: claim\_private ``` /// Consume an L1->L2 message and mint tokens privately. /// The recipient's address is not revealed, but the amount is. #[external("private")] fn claim_private( recipient: AztecAddress, amount: u128, secret_for_L1_to_L2_message_consumption: Field, message_leaf_index: Field, ) { let config = self.storage.config.read(); // Consume L1 to L2 message and emit nullifier let content_hash = get_mint_to_private_content_hash(amount); self.context.consume_l1_to_l2_message( content_hash, secret_for_L1_to_L2_message_consumption, config.portal, message_leaf_index, ); // Mint tokens privately self.call(Token::at(config.token).mint_to_private(recipient, amount)); } ``` > [Source code: docs/examples/contracts/aave\_bridge/src/main.nr#L63-L87](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/aave_bridge/src/main.nr#L63-L87) exit\_to\_l1\_private ``` /// Burn tokens privately and create an L2->L1 message. /// The L1 AavePortal will consume this message and deposit into Aave. #[external("private")] fn exit_to_l1_private( token: AztecAddress, recipient: EthAddress, amount: u128, caller_on_l1: EthAddress, authwit_nonce: Field, ) { let config = self.storage.config.read(); // Assert that user provided token address is same as seen in storage assert_eq(config.token, token, "Token address is not the same as seen in storage"); // Send an L2 to L1 message let content = get_withdraw_content_hash(recipient, amount, caller_on_l1); self.context.message_portal(config.portal, content); // Burn tokens privately self.call(Token::at(token).burn_private(self.msg_sender(), amount, authwit_nonce)); } ``` > [Source code: docs/examples/contracts/aave\_bridge/src/main.nr#L110-L133](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/aave_bridge/src/main.nr#L110-L133) Content Hash Matching The content hash is the critical link between L1 and L2. Both sides must produce the exact same hash for a message to be consumed. The `token_portal_content_hash_lib` handles this by encoding parameters identically to the Solidity side's `abi.encodeWithSignature`. For example, `get_mint_to_public_content_hash(to, amount)` on L2 matches `Hash.sha256ToField(abi.encodeWithSignature("mint_to_public(bytes32,uint256)", to, amount))` on L1. ### Compile[​](#compile "Direct link to Compile") ``` aztec compile ``` Generate TypeScript bindings: ``` aztec codegen target --outdir ../artifacts ``` Token Contract The integration script imports `TokenContract` from `@aztec/noir-contracts.js`, which provides pre-built bindings for the standard Token contract. Only the custom `AaveBridge` contract needs codegen. ## Part 2: The Ethereum Side[​](#part-2-the-ethereum-side "Direct link to Part 2: The Ethereum Side") ### Mock Aave Contracts[​](#mock-aave-contracts "Direct link to Mock Aave Contracts") For local testing, you'll use simplified mocks of Aave's lending pool. The mock pool accepts deposits and returns them with a configurable yield — 10% in this tutorial (1000 basis points, where 10000 bps = 100%). Mock vs Real Aave In production, replace `MockAavePool` with Aave V3's `IPool` interface at `0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2` (Ethereum mainnet). The portal contract's `IAavePool` interface already matches Aave V3's function signatures. For realistic testing, fork mainnet with `aztec-anvil --fork-url ` (the Aztec installer ships Foundry's `anvil` as `aztec-anvil`; substitute your own `anvil` if its version matches `aztec-anvil --version`). Create the following mock contracts in `contracts/`. `contracts/MockERC20.sol` — a minimal ERC20 with public minting: ``` import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract MockERC20 is ERC20 { constructor(string memory name, string memory symbol) ERC20(name, symbol) {} function mint(address to, uint256 amount) external { _mint(to, amount); } } ``` `contracts/MockAToken.sol` — Aave's yield-bearing token mock: ``` import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract MockAToken is ERC20 { constructor(string memory name, string memory symbol) ERC20(name, symbol) {} function mint(address to, uint256 amount) external { _mint(to, amount); } function burn(address from, uint256 amount) external { _burn(from, amount); } } ``` `contracts/MockAavePool.sol` — simplified Aave lending pool that returns a configurable yield: ``` import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {MockERC20} from "./MockERC20.sol"; import {MockAToken} from "./MockAToken.sol"; /// @notice A simplified mock of Aave V3's lending pool for tutorial purposes. /// Supports supply and withdraw with a configurable yield in basis points. contract MockAavePool { MockERC20 public underlyingToken; MockAToken public aToken; uint256 public yieldBps; // e.g. 1000 = 10% constructor(address _underlyingToken, address _aToken, uint256 _yieldBps) { underlyingToken = MockERC20(_underlyingToken); aToken = MockAToken(_aToken); yieldBps = _yieldBps; } /// @notice Deposit underlying tokens and receive aTokens (mimics Aave V3 IPool.supply) function supply( address asset, uint256 amount, address onBehalfOf, uint16 /* referralCode */ ) external { require(asset == address(underlyingToken), "Wrong asset"); IERC20(asset).transferFrom(msg.sender, address(this), amount); aToken.mint(onBehalfOf, amount); } /// @notice Withdraw underlying tokens by burning aTokens (mimics Aave V3 IPool.withdraw) /// Returns the original amount plus simulated yield function withdraw(address asset, uint256 amount, address to) external returns (uint256) { require(asset == address(underlyingToken), "Wrong asset"); // Burn caller's aTokens aToken.burn(msg.sender, amount); // Simulate yield: return amount + yield uint256 yieldAmount = (amount * yieldBps) / 10000; uint256 totalReturn = amount + yieldAmount; // Mint extra underlying to cover yield (mock-only behavior) underlyingToken.mint(address(this), yieldAmount); // Transfer underlying + yield to recipient underlyingToken.transfer(to, totalReturn); return totalReturn; } } ``` ### AavePortal Contract[​](#aaveportal-contract "Direct link to AavePortal Contract") The portal is where the magic happens. It bridges Aztec's cross-chain messages with Aave's lending pool. Create `contracts/AavePortal.sol`: ``` import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IRegistry} from "@aztec/l1-contracts/src/governance/interfaces/IRegistry.sol"; import {IInbox} from "@aztec/l1-contracts/src/core/interfaces/messagebridge/IInbox.sol"; import {IOutbox} from "@aztec/l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol"; import {IRollup} from "@aztec/l1-contracts/src/core/interfaces/IRollup.sol"; import {DataStructures} from "@aztec/l1-contracts/src/core/libraries/DataStructures.sol"; import {Hash} from "@aztec/l1-contracts/src/core/libraries/crypto/Hash.sol"; import {Epoch} from "@aztec/l1-contracts/src/core/libraries/TimeLib.sol"; interface IAavePool { function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) external; function withdraw(address asset, uint256 amount, address to) external returns (uint256); } contract AavePortal { using SafeERC20 for IERC20; IRegistry public registry; IERC20 public underlying; IERC20 public aToken; IAavePool public aavePool; bytes32 public l2Bridge; IRollup public rollup; IOutbox public outbox; IInbox public inbox; uint256 public rollupVersion; bool private _initialized; function initialize(address _registry, address _underlying, address _aToken, address _aavePool, bytes32 _l2Bridge) external { require(!_initialized, "Already initialized"); _initialized = true; registry = IRegistry(_registry); underlying = IERC20(_underlying); aToken = IERC20(_aToken); aavePool = IAavePool(_aavePool); l2Bridge = _l2Bridge; rollup = IRollup(address(registry.getCanonicalRollup())); outbox = rollup.getOutbox(); inbox = rollup.getInbox(); rollupVersion = rollup.getVersion(); } } ``` Assembling the Contract Like the L2 contract, the code above shows the contract opening — imports, state variables, and `initialize()`. The subsequent function snippets go **inside** this contract body, before the closing `}`. The portal has three key functions. First, `depositToAave` consumes an L2→L1 message (proving the user burned tokens on L2) and deposits the underlying tokens into Aave: portal\_deposit\_to\_aave ``` /// @notice Consume an L2->L1 withdraw message and deposit the underlying tokens into Aave /// @dev The content hash must match what the L2 bridge emits via get_withdraw_content_hash function depositToAave( address _recipient, uint256 _amount, bool _withCaller, Epoch _epoch, uint256 _numCheckpointsInEpoch, uint256 _leafIndex, bytes32[] calldata _path ) external { // Reconstruct the L2->L1 message (must match the L2 bridge's exit_to_l1_public/private) DataStructures.L2ToL1Msg memory message = DataStructures.L2ToL1Msg({ sender: DataStructures.L2Actor(l2Bridge, rollupVersion), recipient: DataStructures.L1Actor(address(this), block.chainid), content: Hash.sha256ToField( abi.encodeWithSignature( "withdraw(address,uint256,address)", _recipient, _amount, _withCaller ? msg.sender : address(0) ) ) }); // Consume the message from the outbox (verifies merkle proof) outbox.consume(message, _epoch, _numCheckpointsInEpoch, _leafIndex, _path); // Deposit into Aave instead of sending tokens to the recipient. // The portal must already hold the underlying tokens (pre-funded or bridged separately). underlying.approve(address(aavePool), _amount); aavePool.supply(address(underlying), _amount, address(this), 0); } ``` > [Source code: docs/examples/solidity/aave\_bridge/AavePortal.sol#L57-L89](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/solidity/aave_bridge/AavePortal.sol#L57-L89) Then, `claimFromAavePublic` withdraws from Aave (including any yield earned) and sends an L1→L2 message so the user can mint tokens on L2: portal\_claim\_public ``` /// @notice Withdraw from Aave and send an L1->L2 message to mint tokens publicly on L2 function claimFromAavePublic(uint256 _aTokenAmount, bytes32 _to, bytes32 _secretHash) external returns (bytes32, uint256) { // Withdraw from Aave (returns underlying + yield) aToken.approve(address(aavePool), _aTokenAmount); uint256 withdrawn = aavePool.withdraw(address(underlying), _aTokenAmount, address(this)); // Send L1->L2 message with the total withdrawn amount (including yield) DataStructures.L2Actor memory actor = DataStructures.L2Actor(l2Bridge, rollupVersion); bytes32 contentHash = Hash.sha256ToField(abi.encodeWithSignature("mint_to_public(bytes32,uint256)", _to, withdrawn)); (bytes32 key, uint256 index) = inbox.sendL2Message(actor, contentHash, _secretHash); return (key, index); } ``` > [Source code: docs/examples/solidity/aave\_bridge/AavePortal.sol#L91-L110](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/solidity/aave_bridge/AavePortal.sol#L91-L110) There's also a private variant that lets the user claim without revealing their L2 address: portal\_claim\_private ``` /// @notice Withdraw from Aave and send an L1->L2 message to mint tokens privately on L2 function claimFromAavePrivate(uint256 _aTokenAmount, bytes32 _secretHash) external returns (bytes32, uint256) { // Withdraw from Aave (returns underlying + yield) aToken.approve(address(aavePool), _aTokenAmount); uint256 withdrawn = aavePool.withdraw(address(underlying), _aTokenAmount, address(this)); // Send L1->L2 message for private minting DataStructures.L2Actor memory actor = DataStructures.L2Actor(l2Bridge, rollupVersion); bytes32 contentHash = Hash.sha256ToField(abi.encodeWithSignature("mint_to_private(uint256)", withdrawn)); (bytes32 key, uint256 index) = inbox.sendL2Message(actor, contentHash, _secretHash); return (key, index); } ``` > [Source code: docs/examples/solidity/aave\_bridge/AavePortal.sol#L112-L126](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/solidity/aave_bridge/AavePortal.sol#L112-L126) ### Compile[​](#compile-1 "Direct link to Compile") ``` npx hardhat compile ``` Solidity Artifact Paths Hardhat compiles Solidity contracts to `artifacts/contracts/` by default. The integration script imports ABIs from this location (e.g., `../artifacts/contracts/AavePortal.sol/AavePortal.json`). ## Part 3: Deploying and Testing[​](#part-3-deploying-and-testing "Direct link to Part 3: Deploying and Testing") Create `scripts/index.ts` to run the full flow. This script deploys all contracts, initializes them, deposits tokens into Aave from L2, and claims them back with yield. ### Setup[​](#setup "Direct link to Setup") ``` import { getInitialTestAccountsData } from "@aztec/accounts/testing"; import { AztecAddress, EthAddress } from "@aztec/aztec.js/addresses"; import { SetPublicAuthwitContractInteraction } from "@aztec/aztec.js/authorization"; import { Fr } from "@aztec/aztec.js/fields"; import { createAztecNodeClient, waitForNode } from "@aztec/aztec.js/node"; import { createExtendedL1Client } from "@aztec/ethereum/client"; import { deployL1Contract } from "@aztec/ethereum/deploy-l1-contract"; import { sha256ToField } from "@aztec/foundation/crypto/sha256"; import { computeL2ToL1MessageHash, computeSecretHash, } from "@aztec/stdlib/hash"; import { EmbeddedWallet } from "@aztec/wallets/embedded"; import { decodeEventLog, pad, toFunctionSelector } from "@aztec/viem"; import { foundry } from "@aztec/viem/chains"; import AavePortal from "../artifacts/contracts/AavePortal.sol/AavePortal.json" with { type: "json" }; import MockERC20 from "../artifacts/contracts/MockERC20.sol/MockERC20.json" with { type: "json" }; import MockAToken from "../artifacts/contracts/MockAToken.sol/MockAToken.json" with { type: "json" }; import MockAavePool from "../artifacts/contracts/MockAavePool.sol/MockAavePool.json" with { type: "json" }; import { TokenContract } from "@aztec/noir-contracts.js/Token"; import { AaveBridgeContract } from "../contracts/aztec/artifacts/AaveBridge.js"; // Setup L1 client using anvil's default mnemonic const MNEMONIC = "test test test test test test test test test test test junk"; const l1Client = createExtendedL1Client( [process.env.ETHEREUM_HOST ?? "http://localhost:8545"], MNEMONIC, ); // Setup L2 using Aztec's local network console.log("Setting up L2...\n"); const node = createAztecNodeClient( process.env.AZTEC_NODE_URL ?? "http://localhost:8080", ); await waitForNode(node); const aztecWallet = await EmbeddedWallet.create(node, { ephemeral: true }); const [accData] = await getInitialTestAccountsData(); const account = await aztecWallet.createSchnorrInitializerlessAccount( accData.secret, accData.salt, accData.signingKey, ); console.log(`Account: ${account.address.toString()}\n`); // Get node info const nodeInfo = await node.getNodeInfo(); const registryAddress = nodeInfo.l1ContractAddresses.registryAddress.toString(); const inboxAddress = nodeInfo.l1ContractAddresses.inboxAddress.toString(); ``` About EmbeddedWallet `EmbeddedWallet` is a simplified wallet for local development. It handles key management, transaction signing, and proof generation in-process. Code written against `EmbeddedWallet` works with any `Wallet` implementation, so your application logic transfers directly to production. ### Deploy L1 Contracts[​](#deploy-l1-contracts "Direct link to Deploy L1 Contracts") deploy\_l1 ``` console.log("Deploying L1 contracts...\n"); // Deploy MockERC20 (underlying token, e.g. DAI) const { address: underlyingAddress } = await deployL1Contract( l1Client, MockERC20.abi, MockERC20.bytecode.object as `0x${string}`, ["Mock DAI", "mDAI"], ); // Deploy MockAToken (Aave's yield-bearing token) const { address: aTokenAddress } = await deployL1Contract( l1Client, MockAToken.abi, MockAToken.bytecode.object as `0x${string}`, ["Aave Mock DAI", "amDAI"], ); // Deploy MockAavePool with 10% yield (1000 basis points) const { address: poolAddress } = await deployL1Contract( l1Client, MockAavePool.abi, MockAavePool.bytecode.object as `0x${string}`, [underlyingAddress.toString(), aTokenAddress.toString(), 1000n], ); // Deploy AavePortal const { address: portalAddress } = await deployL1Contract( l1Client, AavePortal.abi, AavePortal.bytecode.object as `0x${string}`, ); console.log(`MockERC20 (DAI): ${underlyingAddress}`); console.log(`MockAToken (aDAI): ${aTokenAddress}`); console.log(`MockAavePool: ${poolAddress}`); console.log(`AavePortal: ${portalAddress}\n`); ``` > [Source code: docs/examples/ts/aave\_bridge/index.ts#L52-L90](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aave_bridge/index.ts#L52-L90) ### Deploy L2 Contracts[​](#deploy-l2-contracts "Direct link to Deploy L2 Contracts") deploy\_l2 ``` console.log("Deploying L2 contracts...\n"); // Deploy the Token contract on L2 (this is the standard Aztec token) const { contract: l2Token } = await TokenContract.deploy( aztecWallet, account.address, // admin "Bridged DAI", "bDAI", 18, ).send({ from: account.address }); // Deploy the AaveBridge on L2 const { contract: l2Bridge } = await AaveBridgeContract.deploy( aztecWallet, l2Token.address, EthAddress.fromString(portalAddress.toString()), ).send({ from: account.address }); console.log(`L2 Token: ${l2Token.address.toString()}`); console.log(`L2 Bridge: ${l2Bridge.address.toString()}\n`); ``` > [Source code: docs/examples/ts/aave\_bridge/index.ts#L92-L113](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aave_bridge/index.ts#L92-L113) ### Initialize[​](#initialize "Direct link to Initialize") initialize ``` console.log("Initializing contracts..."); // Initialize the L1 portal // @ts-expect-error - viem type inference doesn't work with JSON-imported ABIs const initHash = await l1Client.writeContract({ address: portalAddress.toString() as `0x${string}`, abi: AavePortal.abi, functionName: "initialize", args: [ registryAddress, underlyingAddress.toString(), aTokenAddress.toString(), poolAddress.toString(), l2Bridge.address.toString(), ], }); await l1Client.waitForTransactionReceipt({ hash: initHash }); // Set the bridge as a minter on the L2 token so it can mint when claiming await l2Token.methods .set_minter(l2Bridge.address, true) .send({ from: account.address }); console.log("All contracts initialized\n"); ``` > [Source code: docs/examples/ts/aave\_bridge/index.ts#L115-L140](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aave_bridge/index.ts#L115-L140) ### Fund the User[​](#fund-the-user "Direct link to Fund the User") For this tutorial, you need tokens in two places: * **L2 tokens for the user** — The user needs tokens on L2 to burn and bridge to L1. In production, these would come from a prior bridge operation. * **L1 underlying tokens at the portal** — When the portal calls `depositToAave`, it transfers underlying tokens to Aave. The portal must already hold these tokens. In production, the tokens would arrive via a separate bridging mechanism. For simplicity, mint directly to both: fund\_user ``` // Pre-fund the portal with L1 tokens and mint L2 tokens to the user // In a real scenario, tokens would already exist on L2 from a prior bridge console.log("Funding user with tokens on L2..."); const depositAmount = 1000n * 10n ** 18n; // 1000 DAI // Mint underlying tokens on L1 // @ts-expect-error - viem type inference doesn't work with JSON-imported ABIs const mintHash = await l1Client.writeContract({ address: underlyingAddress.toString() as `0x${string}`, abi: MockERC20.abi, functionName: "mint", args: [portalAddress.toString(), depositAmount], }); await l1Client.waitForTransactionReceipt({ hash: mintHash }); // Also mint tokens directly to the user on L2 (admin mints for simplicity) await l2Token.methods .mint_to_public(account.address, depositAmount) .send({ from: account.address }); console.log(`User funded with ${depositAmount / 10n ** 18n} tokens on L2\n`); ``` > [Source code: docs/examples/ts/aave\_bridge/index.ts#L142-L165](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aave_bridge/index.ts#L142-L165) ### Deposit to Aave (L2 → L1)[​](#deposit-to-aave-l2--l1 "Direct link to Deposit to Aave (L2 → L1)") Now for the main flow. Burn tokens on L2 and send a message to L1. Why is the portal the recipient? The `recipient` in `exit_to_l1_public` is the L1 address that receives the withdrawal message. Since the AavePortal contract needs to deposit the tokens into Aave, the portal itself is the recipient. Setting `caller_on_l1` to `EthAddress.ZERO` means anyone can relay the message on L1 — there's no access restriction on who calls `depositToAave`. deposit\_to\_aave ``` // ============================================================ // STEP 1: Deposit to Aave (L2 -> L1 flow) // ============================================================ console.log("=== Depositing to Aave ===\n"); const amountToDeposit = 500n * 10n ** 18n; // 500 DAI // Create authwit for the bridge to burn tokens on our behalf. // The bridge calls Token::burn_public(user, amount, nonce), where msg_sender // is the bridge, so the token contract requires a public authwit. const burnNonce = Fr.random(); const burnAuthwit = await SetPublicAuthwitContractInteraction.create( aztecWallet, account.address, { caller: l2Bridge.address, action: l2Token.methods.burn_public( account.address, amountToDeposit, burnNonce, ), }, true, ); await burnAuthwit.send(); // On L2: burn tokens and send L2->L1 message. // exit_to_l1_public sends tokens to the portal as the L1 recipient, // and caller_on_l1 is set to ZERO so anyone can relay the message. const { receipt: exitReceipt } = await l2Bridge.methods .exit_to_l1_public( EthAddress.fromString(portalAddress.toString()), // recipient on L1 (the portal itself) amountToDeposit, EthAddress.ZERO, // caller_on_l1: anyone can relay burnNonce, // authwit nonce authorizing the bridge to burn on our behalf ) .send({ from: account.address }); console.log(`Exit sent (block: ${exitReceipt.blockNumber})`); ``` > [Source code: docs/examples/ts/aave\_bridge/index.ts#L192-L232](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aave_bridge/index.ts#L192-L232) Compute the membership witness to prove the message on L1: get\_deposit\_witness ``` // Compute the L2->L1 content hash for the withdrawal witness. // This must match what the L1 portal reconstructs via abi.encodeWithSignature. // toFunctionSelector computes keccak256 of the signature and takes the first 4 bytes. const portalEthAddress = EthAddress.fromString(portalAddress.toString()); const withdrawContent = sha256ToField([ Buffer.from( toFunctionSelector("withdraw(address,uint256,address)").substring(2), "hex", ), portalEthAddress.toBuffer32(), new Fr(amountToDeposit).toBuffer(), EthAddress.ZERO.toBuffer32(), ]); // @ts-expect-error - viem type inference doesn't work with JSON-imported ABIs const version = (await l1Client.readContract({ address: portalAddress.toString() as `0x${string}`, abi: AavePortal.abi, functionName: "rollupVersion", })) as bigint; const msgLeaf = computeL2ToL1MessageHash({ l2Sender: l2Bridge.address, l1Recipient: portalEthAddress, content: withdrawContent, rollupVersion: new Fr(version), chainId: new Fr(foundry.id), }); // Wait for the block to be proven if (!exitReceipt.blockNumber) { throw new Error("Exit transaction was not included in a block"); } const exitBlockNumber = exitReceipt.blockNumber; console.log("Waiting for block to be proven..."); let provenBlockNumber = await node.getBlockNumber("proven"); while (provenBlockNumber < exitBlockNumber) { console.log( ` Waiting... (proven: ${provenBlockNumber}, needed: ${exitBlockNumber})`, ); await new Promise((resolve) => setTimeout(resolve, 10000)); provenBlockNumber = await node.getBlockNumber("proven"); } console.log("Block proven!\n"); // Compute the membership witness using the message hash and the L2 tx hash. // The node picks the smallest partial-proof root that covers the tx's checkpoint. const witness = await node.getL2ToL1MembershipWitness( exitReceipt.txHash, msgLeaf, ); const epoch = witness!.epochNumber; const numCheckpointsInEpoch = witness!.numCheckpointsInEpoch; const siblingPathHex = witness!.siblingPath .toBufferArray() .map((buf: Buffer) => `0x${buf.toString("hex")}` as `0x${string}`); ``` > [Source code: docs/examples/ts/aave\_bridge/index.ts#L234-L292](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aave_bridge/index.ts#L234-L292) Execute the deposit on L1: execute\_deposit\_l1 ``` // On L1: consume the outbox message and deposit into Aave console.log("Depositing into Aave on L1..."); // @ts-expect-error - viem type inference doesn't work with JSON-imported ABIs const depositToAaveHash = await l1Client.writeContract({ address: portalAddress.toString() as `0x${string}`, abi: AavePortal.abi, functionName: "depositToAave", args: [ portalAddress.toString(), // recipient (matches L2 exit) amountToDeposit, false, // withCaller = false (matches caller_on_l1 = address(0)) BigInt(epoch), BigInt(numCheckpointsInEpoch), BigInt(witness!.leafIndex), siblingPathHex, ], }); await l1Client.waitForTransactionReceipt({ hash: depositToAaveHash }); console.log("Tokens deposited into Aave!\n"); ``` > [Source code: docs/examples/ts/aave\_bridge/index.ts#L294-L314](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aave_bridge/index.ts#L294-L314) ### Claim from Aave with Yield (L1 → L2)[​](#claim-from-aave-with-yield-l1--l2 "Direct link to Claim from Aave with Yield (L1 → L2)") Before withdrawing from Aave, generate a random secret and compute its hash. The secret hash is included in the L1-to-L2 message — only someone who knows the pre-image (the secret) can consume the message on L2. This prevents front-running: without the secret, no one else can claim your tokens. Withdraw from Aave on L1 and send the message to L2. The mock pool returns 10% yield: claim\_from\_aave\_l1 ``` // ============================================================ // STEP 2: Claim from Aave with yield (L1 -> L2 flow) // ============================================================ console.log("=== Claiming from Aave (with yield) ===\n"); const secret = Fr.random(); const secretHash = await computeSecretHash(secret); // On L1: withdraw from Aave and send L1->L2 message // @ts-expect-error - viem type inference doesn't work with JSON-imported ABIs const claimHash = await l1Client.writeContract({ address: portalAddress.toString() as `0x${string}`, abi: AavePortal.abi, functionName: "claimFromAavePublic", args: [ amountToDeposit, // aToken amount to withdraw pad(account.address.toString() as `0x${string}`, { dir: "left", size: 32 }), // L2 recipient pad(secretHash.toString() as `0x${string}`, { dir: "left", size: 32 }), ], }); const claimReceipt = await l1Client.waitForTransactionReceipt({ hash: claimHash, }); console.log("Aave withdrawal complete, L1->L2 message sent"); ``` > [Source code: docs/examples/ts/aave\_bridge/index.ts#L316-L341](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aave_bridge/index.ts#L316-L341) Extract the message leaf index: get\_claim\_leaf\_index ``` // Extract the message leaf index from the MessageSent event const INBOX_ABI = [ { type: "event", name: "MessageSent", inputs: [ { name: "checkpointNumber", type: "uint256", indexed: true }, { name: "index", type: "uint256", indexed: false }, { name: "hash", type: "bytes32", indexed: true }, { name: "rollingHash", type: "bytes16", indexed: false }, ], }, ] as const; const messageSentLogs = claimReceipt.logs .filter((log) => log.address.toLowerCase() === inboxAddress.toLowerCase()) .map((log: any) => { try { const decoded = decodeEventLog({ abi: INBOX_ABI, data: log.data, topics: log.topics, }); return { log, decoded }; } catch { return null; } }) .filter( (item): item is { log: any; decoded: any } => item !== null && (item.decoded as any).eventName === "MessageSent", ); const messageLeafIndex = new Fr(messageSentLogs[0].decoded.args.index); console.log(`Message leaf index: ${messageLeafIndex}\n`); ``` > [Source code: docs/examples/ts/aave\_bridge/index.ts#L343-L379](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aave_bridge/index.ts#L343-L379) On the local network, L2 blocks are only produced when transactions are submitted. L1-to-L2 messages require 2 L2 blocks before they can be consumed on L2. This utility deploys two dummy contracts (with random salts for unique addresses) to force block production. On devnet or testnet, blocks are produced continuously and this step is unnecessary: mine\_blocks ``` // On the local network, L2 blocks are only produced when transactions are submitted. // L1-to-L2 messages require 2 L2 blocks before they can be consumed, so we deploy // two dummy contracts (with random salts for unique addresses) to force block production. async function mine2Blocks( aztecWallet: EmbeddedWallet, accountAddress: AztecAddress, ) { await AaveBridgeContract.deploy( aztecWallet, accountAddress, EthAddress.ZERO, ).send({ from: accountAddress, }); await AaveBridgeContract.deploy( aztecWallet, accountAddress, EthAddress.ZERO, ).send({ from: accountAddress, }); } ``` > [Source code: docs/examples/ts/aave\_bridge/index.ts#L167-L190](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aave_bridge/index.ts#L167-L190) Claim the tokens (with yield) on L2: claim\_on\_l2 ``` // Mine blocks so the L1->L2 message is available await mine2Blocks(aztecWallet, account.address); // The mock Aave pool returns 10% yield, so 500 DAI becomes 550 DAI const expectedWithYield = amountToDeposit + (amountToDeposit * 1000n) / 10000n; console.log( `Expected amount with yield: ${expectedWithYield / 10n ** 18n} tokens`, ); // On L2: consume the L1->L2 message and mint tokens (with yield) console.log("Claiming tokens on L2..."); await l2Bridge.methods .claim_public(account.address, expectedWithYield, secret, messageLeafIndex) .send({ from: account.address }); console.log("Tokens claimed on L2!\n"); ``` > [Source code: docs/examples/ts/aave\_bridge/index.ts#L381-L397](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aave_bridge/index.ts#L381-L397) ### Verify[​](#verify "Direct link to Verify") verify ``` // Verify the user's balance includes yield console.log("=== Verifying balances ===\n"); const { result: finalBalance } = await l2Token.methods .balance_of_public(account.address) .simulate({ from: account.address }); const initialRemaining = depositAmount - amountToDeposit; // 500 DAI not deposited const expectedFinal = initialRemaining + expectedWithYield; // 500 + 550 = 1050 DAI console.log(`Initial deposit: ${depositAmount / 10n ** 18n} tokens`); console.log(`Deposited to Aave: ${amountToDeposit / 10n ** 18n} tokens`); console.log( `Yield earned (10%): ${(expectedWithYield - amountToDeposit) / 10n ** 18n} tokens`, ); console.log(`Expected balance: ${expectedFinal / 10n ** 18n} tokens`); console.log(`Actual balance: ${finalBalance / 10n ** 18n} tokens`); console.log( `\nYield earned successfully: ${finalBalance >= expectedFinal ? "YES" : "NO"}`, ); ``` > [Source code: docs/examples/ts/aave\_bridge/index.ts#L399-L420](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aave_bridge/index.ts#L399-L420) Run the full flow: ``` npx hardhat run scripts/index.ts --network localhost ``` You should see the user start with 1000 tokens, deposit 500 to Aave, and end up with 1050 tokens (500 remaining + 550 from Aave with 10% yield). ## What You Built[​](#what-you-built "Direct link to What You Built") A complete cross-chain DeFi integration with: 1. **L2 Bridge** (Noir) — Burns/mints tokens and handles cross-chain messages. Supports both public and private operations. 2. **L1 Portal** (Solidity) — Deposits into Aave and withdraws with yield. Handles message consumption and creation. 3. **Mock Aave** (Solidity) — Simulates yield generation for local testing. 4. **Full Flow** — Deposit tokens from L2 into Aave, earn yield, and claim back on L2. Production Considerations This tutorial uses mock contracts for simplicity. In production: * Replace `MockAavePool` with a real Aave V3 pool address * Handle Aave's variable interest rates (the withdrawn amount may differ from expectations) * Add slippage protection and error handling for failed messages * Consider that funds are "in flight" between chains — implement recovery mechanisms * Add proper access controls to the portal contract ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Script hangs waiting for block to be published[​](#script-hangs-waiting-for-block-to-be-published "Direct link to Script hangs waiting for block to be published") The deposit flow waits for the L2 block containing your exit transaction to be included in an epoch that is submitted to L1. On the local network, this typically takes 30–60 seconds. If it takes longer, check that your local network is running and producing blocks. ### Content hash mismatch — L1 message consumption reverts[​](#content-hash-mismatch--l1-message-consumption-reverts "Direct link to Content hash mismatch — L1 message consumption reverts") This is the most common cross-chain debugging issue. The content hash computed on L2 (via `get_withdraw_content_hash`) must exactly match what the L1 portal reconstructs via `abi.encodeWithSignature`. Double-check that: * The function signature string matches on both sides (e.g., `"withdraw(address,uint256,address)"`) * Parameters are in the same order and encoded as the same types * The `caller_on_l1` value matches: `EthAddress.ZERO` on L2 corresponds to `address(0)` on L1 ### "Minter not set" — L2 claim fails[​](#minter-not-set--l2-claim-fails "Direct link to \"Minter not set\" — L2 claim fails") If `claim_public` reverts, ensure you called `set_minter(l2Bridge.address, true)` on the Token contract **after** deploying the bridge. The bridge must be authorized as a minter before it can mint tokens on claim. ### L1→L2 message not found — claim reverts after mining blocks[​](#l1l2-message-not-found--claim-reverts-after-mining-blocks "Direct link to L1→L2 message not found — claim reverts after mining blocks") L1-to-L2 messages need 2 L2 blocks after the L1 transaction before they become consumable. Make sure `mine2Blocks` runs before the claim. If the issue persists, verify the `messageLeafIndex` extracted from the `MessageSent` event is correct. ## Next Steps[​](#next-steps "Direct link to Next Steps") * **Test with a mainnet fork**: Use `aztec-anvil --fork-url` (or your own `anvil` install) to test against real Aave * **Add private deposits**: Use the `claim_private` and `exit_to_l1_private` functions for privacy-preserving DeFi * **Build a frontend**: Add a web UI for easy depositing and claiming * **Compose with other protocols**: The same pattern works for Uniswap, Compound, or any L1 DeFi protocol Learn More * [Cross-chain messaging](/developers/testnet/docs/foundational-topics/ethereum-aztec-messaging.md) * [Token Bridge Tutorial](/developers/testnet/docs/tutorials/js_tutorials/token_bridge.md) * [State management](/developers/testnet/docs/foundational-topics/state_management.md) --- # Deploying a Token Contract In this guide, we will retrieve the local network and deploy a pre-written token contract to it using Aztec.js. [Check out the source code](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr). We will then use Aztec.js to interact with this contract and transfer tokens. Before starting, make sure to be running Aztec local network at version 5.0.0-rc.2. Check out [the guide](/developers/testnet/getting_started_on_local_network.md) for info about that. ## Set up the project[​](#set-up-the-project "Direct link to Set up the project") First, create a new directory for your project and initialize it with yarn: ``` mkdir token-tutorial cd token-tutorial yarn init -y ``` Next, add the TypeScript dependencies: ``` yarn add typescript @types/node tsx ``` tip Never heard of `tsx`? Well, it will just run `typescript` with reasonable defaults. Pretty cool for a small example like this one. You may want to tune in your own project's `tsconfig.json` later! Let's also import the Aztec dependencies for this tutorial: ``` yarn add @aztec/aztec.js@5.0.0-rc.2 @aztec/accounts@5.0.0-rc.2 @aztec/noir-contracts.js@5.0.0-rc.2 @aztec/wallets@5.0.0-rc.2 ``` Aztec.js assumes your project is using ESM, so make sure you add `"type": "module"` to `package.json`. You probably also want at least a `start` script. For example: ``` { "type": "module", "scripts": { "start": "tsx index.ts" } } ``` ### Connecting to the local network[​](#connecting-to-the-local-network "Direct link to Connecting to the local network") Now let's connect to the Aztec local network and set up test accounts. **Step 1: Start the Aztec Local Network** In a separate terminal, run: ``` aztec start --local-network ``` Keep this terminal running throughout the tutorial. **Step 2: Create the index.ts file** Create an `index.ts` file in the root of your project with the following code. This connects to the local network and imports test accounts (Alice and Bob): setup ``` import { EmbeddedWallet } from "@aztec/wallets/embedded"; import { getInitialTestAccountsData } from "@aztec/accounts/testing"; const nodeUrl = process.env.AZTEC_NODE_URL ?? "http://localhost:8080"; const wallet = await EmbeddedWallet.create(nodeUrl, { ephemeral: true }); const [alice, bob] = await getInitialTestAccountsData(); await wallet.createSchnorrInitializerlessAccount(alice.secret, alice.salt); await wallet.createSchnorrInitializerlessAccount(bob.secret, bob.salt); ``` > [Source code: docs/examples/ts/aztecjs\_getting\_started/index.ts#L1-L11](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_getting_started/index.ts#L1-L11) **Step 3: Verify the script runs** Run the script to make sure everything is set up correctly: ``` yarn start ``` If there are no errors, you're ready to continue. For more details on connecting to the local network, see [this guide](/developers/testnet/docs/aztec-js/how_to_connect_to_local_network.md). ## Deploy the token contract[​](#deploy-the-token-contract "Direct link to Deploy the token contract") Now that we have our accounts loaded, let's deploy a pre-compiled token contract from the Aztec library. You can find the full code for the contract [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/token_contract/src). Add the following to `index.ts` to import the contract and deploy it with Alice as the admin: deploy ``` import { TokenContract } from "@aztec/noir-contracts.js/Token"; const { contract: token } = await TokenContract.deploy( wallet, alice.address, "TokenName", "TKN", 18, ).send({ from: alice.address }); ``` > [Source code: docs/examples/ts/aztecjs\_getting\_started/index.ts#L13-L23](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_getting_started/index.ts#L13-L23) ## Mint and transfer[​](#mint-and-transfer "Direct link to Mint and transfer") Let's go ahead and have Alice mint herself some tokens, in private: mint ``` await token.methods .mint_to_private(alice.address, 100) .send({ from: alice.address }); ``` > [Source code: docs/examples/ts/aztecjs\_getting\_started/index.ts#L25-L29](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_getting_started/index.ts#L25-L29) Let's check both Alice's and Bob's balances now: check\_balances ``` let { result: aliceBalance } = await token.methods .balance_of_private(alice.address) .simulate({ from: alice.address }); console.log(`Alice's balance: ${aliceBalance}`); let { result: bobBalance } = await token.methods .balance_of_private(bob.address) .simulate({ from: bob.address }); console.log(`Bob's balance: ${bobBalance}`); ``` > [Source code: docs/examples/ts/aztecjs\_getting\_started/index.ts#L31-L40](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_getting_started/index.ts#L31-L40) Alice should have 100 tokens, while Bob has none yet. Great! Let's have Alice transfer some tokens to Bob, also in private: transfer ``` await token.methods.transfer(bob.address, 10).send({ from: alice.address }); ({ result: bobBalance } = await token.methods .balance_of_private(bob.address) .simulate({ from: bob.address })); console.log(`Bob's balance: ${bobBalance}`); ``` > [Source code: docs/examples/ts/aztecjs\_getting\_started/index.ts#L42-L48](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_getting_started/index.ts#L42-L48) Bob should now see 10 tokens in his balance. ## Other cool things[​](#other-cool-things "Direct link to Other cool things") Say that Alice is nice and wants to set Bob as a minter. Even though it's a public function, it can be called in a similar way: set\_minter ``` await token.methods.set_minter(bob.address, true).send({ from: alice.address }); ``` > [Source code: docs/examples/ts/aztecjs\_getting\_started/index.ts#L50-L52](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_getting_started/index.ts#L50-L52) Bob is now the minter, so he can mint some tokens to himself: bob\_mints ``` await token.methods .mint_to_private(bob.address, 100) .send({ from: bob.address }); ({ result: bobBalance } = await token.methods .balance_of_private(bob.address) .simulate({ from: bob.address })); console.log(`Bob's balance: ${bobBalance}`); ``` > [Source code: docs/examples/ts/aztecjs\_getting\_started/index.ts#L54-L62](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/aztecjs_getting_started/index.ts#L54-L62) info Have a look at the [contract source](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr). Notice is that the `mint_to_private` function we used above actually starts a partial note. This allows the total balance to increase while keeping the recipient private! How cool is that? ## Going Further[​](#going-further "Direct link to Going Further") The pre-compiled token contract used in this tutorial is Aztec's reference implementation. It covers the core operations you need to get started: minting, private transfers, and public balance management. For production applications, consider the **AIP-20 Token Standard** maintained by [DeFi Wonderland](https://github.com/defi-wonderland/aztec-standards/tree/dev/src/token_contract). AIP-20 formalizes the same patterns used in the reference contract and adds: * **Commitment-based transfers** for DeFi protocols where the recipient is determined asynchronously * **Recursive note consumption** for handling large balances that span many notes * **Tokenized vault support (AIP-4626)** for yield-bearing tokens that issue shares against an underlying asset To learn how to write a token contract from scratch rather than deploying a pre-compiled one, see the [Private Token Contract tutorial](/developers/testnet/docs/tutorials/contract_tutorials/token_contract.md). For the full specifications of all Aztec contract standards, see the [Aztec Contract Standards](/developers/testnet/docs/aztec-nr/standards.md) reference. --- # Bridge Your NFT to Aztec ## Why Bridge an NFT?[​](#why-bridge-an-nft "Direct link to Why Bridge an NFT?") Imagine you own a CryptoPunk NFT on Ethereum. You want to use it in games, social apps, or DeFi protocols, but gas fees on Ethereum make every interaction expensive. What if you could move your Punk to Aztec (L2), use it **privately** in dozens of applications, and then bring it back to Ethereum when you're ready to sell? In this tutorial, you'll build a **private NFT bridge**. By the end, you'll understand how **portals** work and how **cross-chain messages** flow between L1 and L2. Before starting, make sure you have the Aztec local network running at version v5.0.0-rc.2. Check out [the local network guide](/developers/testnet/getting_started_on_local_network.md) for setup instructions. ## What You'll Build[​](#what-youll-build "Direct link to What You'll Build") You'll create two contracts with **privacy at the core**: * **NFTPunk (L2)** - An NFT contract with encrypted ownership using `PrivateSet` * **NFTBridge (L2)** - A bridge that mints NFTs privately when claiming L1 messages This tutorial focuses on the L2 side to keep things manageable. You'll learn the essential privacy patterns that apply to any asset bridge on Aztec. ## Project Setup[​](#project-setup "Direct link to Project Setup") Let's start simple. Since this is an Ethereum project, it's easier to just start with Hardhat: ``` git clone https://github.com/critesjosh/hardhat-aztec-example ``` You're cloning a repo here to make it easier for Aztec's `l1-contracts` to be mapped correctly. You should now have a `hardhat-aztec-example` folder with Hardhat's default starter, with a few changes in `package.json`. We want to add a few more dependencies now before we start: ``` cd hardhat-aztec-example yarn add @aztec/aztec.js@5.0.0-rc.2 @aztec/accounts@5.0.0-rc.2 @aztec/stdlib@5.0.0-rc.2 @aztec/wallets@5.0.0-rc.2 tsx ``` Match the `@aztec/l1-contracts` version The starter repo pins its `@aztec/l1-contracts` dependency to an older release. In `package.json`, update the tag to match the network version used in this tutorial, then run `yarn install` again: ``` "@aztec/l1-contracts": "git+https://github.com/AztecProtocol/l1-contracts.git#v5.0.0-rc.2" ``` The L1 interfaces the portal imports later in this tutorial must match the contracts deployed by your running network. Now start the local network in another terminal: ``` aztec start --local-network ``` This should start two important services on ports 8080 and 8545, respectively: Aztec and Anvil (an Ethereum development node). ## Part 1: Building the NFT Contract[​](#part-1-building-the-nft-contract "Direct link to Part 1: Building the NFT Contract") Let's start with a basic NFT contract on Aztec. That's the representation of the NFT locked on the L2 side: Let's create that crate in the `contracts` folder so it looks tidy: ``` aztec new contracts/aztec/nft cd contracts/aztec/nft ``` This creates a workspace with two crates: an `nft_contract` crate for the smart contract code and an `nft_test` crate for Noir tests. The `aztec` dependency is already configured in `nft_contract/Nargo.toml`. Noir Language Server If you're using VS Code, install the [Noir Language Support extension](https://marketplace.visualstudio.com/items?itemName=noir-lang.vscode-noir) for syntax highlighting, error checking, and code completion while writing Noir contracts. ### Create the NFT Note[​](#create-the-nft-note "Direct link to Create the NFT Note") First, let's create a custom note type for private NFT ownership. In the `nft_contract/src/` directory, create a new file called `nft.nr`: ``` touch nft_contract/src/nft.nr ``` In this file, you're going to create a **private note** that represents NFT ownership. This is a struct with macros that indicate it is a note that can be compared and packed: nft\_note\_struct ``` use aztec::{macros::notes::note, protocol::traits::Packable}; #[derive(Eq, Packable)] #[note] pub struct NFTNote { pub token_id: Field, } ``` > [Source code: docs/examples/contracts/nft/src/nft.nr#L1-L9](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/nft/src/nft.nr#L1-L9) You now have a note that represents the owner of a particular NFT. Next, move on to the contract itself. Custom Notes Notes are powerful concepts. Learn more about how to use them in the [state management guide](/developers/testnet/docs/foundational-topics/state_management.md). ### Define Storage[​](#define-storage "Direct link to Define Storage") Back in `nft_contract/src/main.nr`, you can now build the contract storage. You need: * **admin**: Who controls the contract (set once, never changes) * **minter**: The bridge address (set once by admin) * **nfts**: Track which NFTs exist (public, needed for bridging) * **owners**: Private ownership using the NFTNote One interesting aspect of this storage configuration is the use of `DelayedPublicMutable`, which allows private functions to read and use public state. You're using it to publicly track which NFTs are already minted while keeping their owners private. Read more about `DelayedPublicMutable` in [the storage guide](/developers/testnet/docs/aztec-nr/framework-description/state_variables.md). Write the storage struct and a simple [initializer](/developers/testnet/docs/foundational-topics/contract_creation.md#initialization) to set the admin in the `nft_contract/src/main.nr` file: ``` use aztec::macros::aztec; pub mod nft; #[aztec] pub contract NFTPunk { use crate::nft::NFTNote; use aztec::{ macros::{functions::{external, initializer, only_self}, storage::storage}, protocol::address::AztecAddress, state_vars::{DelayedPublicMutable, Map, Owned, PrivateSet, PublicImmutable}, }; use aztec::messages::delivery::MessageDelivery; use aztec::note::{ note_getter_options::NoteGetterOptions, note_interface::NoteProperties, note_viewer_options::NoteViewerOptions, }; use aztec::utils::comparison::Comparator; #[storage] struct Storage { admin: PublicImmutable, minter: PublicImmutable, nfts: Map, Context>, owners: Owned, Context>, } #[external("public")] #[initializer] fn constructor(admin: AztecAddress) { self.storage.admin.initialize(admin); } } ``` ### Utility Functions[​](#utility-functions "Direct link to Utility Functions") Add an internal function to handle the `DelayedPublicMutable` value change. Mark the function as public and `#[only_self]` so only the contract can call it: mark\_nft\_exists ``` #[external("public")] #[only_self] fn _mark_nft_exists(token_id: Field, exists: bool) { self.storage.nfts.at(token_id).schedule_value_change(exists); } ``` > [Source code: docs/examples/contracts/nft/src/main.nr#L42-L48](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/nft/src/main.nr#L42-L48) This function is marked with `#[only_self]`, meaning only the contract itself can call it. It uses `schedule_value_change` to update the `nfts` storage, preventing the same NFT from being minted twice or burned when it doesn't exist. You'll call this public function from a private function later using `enqueue_self`. Another useful function checks how many notes a caller has. You can use this later to verify the claim and exit from L2: notes\_of ``` #[external("utility")] unconstrained fn notes_of(from: AztecAddress) -> Field { let notes = self.storage.owners.at(from).view_notes(NoteViewerOptions::new()); notes.len() as Field } ``` > [Source code: docs/examples/contracts/nft/src/main.nr#L67-L73](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/nft/src/main.nr#L67-L73) ### Add Minting and Burning[​](#add-minting-and-burning "Direct link to Add Minting and Burning") Before anything else, you need to set the minter. This will be the bridge contract, so only the bridge contract can mint NFTs. This value doesn't need to change after initialization. Here's how to initialize the `PublicImmutable`: set\_minter ``` #[external("public")] fn set_minter(minter: AztecAddress) { assert(self.storage.admin.read().eq(self.msg_sender()), "caller is not admin"); self.storage.minter.initialize(minter); } ``` > [Source code: docs/examples/contracts/nft/src/main.nr#L34-L40](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/nft/src/main.nr#L34-L40) Now for the magic - minting NFTs **privately**. The bridge will call this to mint to a user, deliver the note using [constrained message delivery](/developers/testnet/docs/aztec-nr/framework-description/events_and_logs.md) (best practice when "sending someone a note") and then [enqueue a public call](/developers/testnet/docs/aztec-nr/framework-description/calling_contracts.md) to the `_mark_nft_exists` function: mint ``` #[external("private")] fn mint(to: AztecAddress, token_id: Field) { assert( self.storage.minter.read().eq(self.msg_sender()), "caller is not the authorized minter", ); // we create an NFT note and insert it to the PrivateSet - a collection of notes meant to be read in private let new_nft = NFTNote { token_id }; self.storage.owners.at(to).insert(new_nft).deliver(MessageDelivery::onchain_constrained()); // calling the internal public function above to indicate that the NFT is taken self.enqueue_self._mark_nft_exists(token_id, true); } ``` > [Source code: docs/examples/contracts/nft/src/main.nr#L50-L65](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/nft/src/main.nr#L50-L65) The bridge will also need to burn NFTs when users withdraw back to L1: burn ``` #[external("private")] fn burn(from: AztecAddress, token_id: Field) { assert( self.storage.minter.read().eq(self.msg_sender()), "caller is not the authorized minter", ); // from the NFTNote properties, selects token_id and compares it against the token_id to be burned let options = NoteGetterOptions::new() .select(NFTNote::properties().token_id, Comparator.EQ, token_id) .set_limit(1); let notes = self.storage.owners.at(from).pop_notes(options); assert(notes.len() == 1, "NFT not found"); self.enqueue_self._mark_nft_exists(token_id, false); } ``` > [Source code: docs/examples/contracts/nft/src/main.nr#L75-L92](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/nft/src/main.nr#L75-L92) ### Compiling\![​](#compiling "Direct link to Compiling!") Let's verify it compiles: ``` aztec compile ``` 🎉 You should see "Compiled successfully!" This means our private NFT contract is ready. Now let's build the bridge. ## Part 2: Building the Bridge[​](#part-2-building-the-bridge "Direct link to Part 2: Building the Bridge") We have built the L2 NFT contract. This is the L2 representation of an NFT that is locked on the L1 bridge. The L2 bridge is the contract that talks to the L1 bridge through cross-chain messaging. You can read more about this protocol [here](/developers/testnet/docs/foundational-topics/ethereum-aztec-messaging.md). Let's create a new contract in the same tidy `contracts/aztec` folder: ``` cd .. aztec new nft_bridge cd nft_bridge ``` Now add the `NFTPunk` contract dependency to `nft_bridge_contract/Nargo.toml`. The `aztec` dependency is already there: ``` [dependencies] aztec = { git="https://github.com/AztecProtocol/aztec-nr", tag = "v5.0.0-rc.2", directory = "aztec" } NFTPunk = { path = "../../nft/nft_contract" } ``` ### Understanding Bridges[​](#understanding-bridges "Direct link to Understanding Bridges") A bridge has two jobs: 1. **Claim**: When someone deposits an NFT on L1, mint it on L2 2. **Exit**: When someone wants to withdraw, burn on L2 and unlock on L1 This means having knowledge about the L2 NFT contract, and the bridge on the L1 side. That's what goes into our bridge's storage. ### Bridge Storage[​](#bridge-storage "Direct link to Bridge Storage") Clean up `nft_bridge_contract/src/main.nr` which is just a placeholder, and let's write the storage struct and the constructor. We'll use `PublicImmutable` since these values never change: ``` use aztec::macros::aztec; #[aztec] pub contract NFTBridge { use aztec::{ macros::{functions::{external, initializer}, storage::storage}, protocol::{address::{AztecAddress, EthAddress}, hash::sha256_to_field}, state_vars::PublicImmutable, }; use NFTPunk::NFTPunk; #[storage] struct Storage { nft: PublicImmutable, portal: PublicImmutable, } #[external("public")] #[initializer] fn constructor(nft: AztecAddress) { self.storage.nft.initialize(nft); } #[external("public")] fn set_portal(portal: EthAddress) { self.storage.portal.initialize(portal); } } ``` You can't initialize the `portal` value in the constructor because the L1 portal hasn't been deployed yet. You'll need another function to set it up after the L1 portal is deployed. ### Adding the Bridge Functions[​](#adding-the-bridge-functions "Direct link to Adding the Bridge Functions") The Aztec network provides a way to consume messages from L1 to L2 called `consume_l1_to_l2_message`. You need to define how to encode messages. Here's a simple approach: when an NFT is being bridged, the L1 portal sends a hash of its `token_id` through the bridge, signaling which `token_id` was locked and can be minted on L2. This approach is simple but sufficient for this tutorial. Build the `claim` function, which consumes the message and mints the NFT on the L2 side: claim ``` #[external("private")] fn claim(to: AztecAddress, token_id: Field, secret: Field, message_leaf_index: Field) { // Compute the message hash that was sent from L1 let token_id_bytes: [u8; 32] = (token_id as Field).to_be_bytes(); let content_hash = sha256_to_field(token_id_bytes); // Consume the L1 -> L2 message self.context.consume_l1_to_l2_message( content_hash, secret, self.storage.portal.read(), message_leaf_index, ); // Mint the NFT on L2 let nft: AztecAddress = self.storage.nft.read(); self.call(NFTPunk::at(nft).mint(to, token_id)); } ``` > [Source code: docs/examples/contracts/nft\_bridge/src/main.nr#L31-L50](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/nft_bridge/src/main.nr#L31-L50) Secret The secret prevents front-running. Certainly you don't want anyone to claim your NFT on the L2 side by just being faster. Adding a secret acts like a "password": you can only claim it if you know it. Similarly, exiting to L1 means burning the NFT on the L2 side and pushing a message through the protocol. To ensure only the L1 recipient can claim it, hash the `token_id` together with the `recipient`: exit ``` #[external("private")] fn exit(token_id: Field, recipient: EthAddress) { // Create L2->L1 message to unlock NFT on L1 let token_id_bytes: [u8; 32] = token_id.to_be_bytes(); let recipient_bytes: [u8; 20] = recipient.to_be_bytes(); let content = sha256_to_field(token_id_bytes.concat(recipient_bytes)); self.context.message_portal(self.storage.portal.read(), content); // Burn the NFT on L2 let nft: AztecAddress = self.storage.nft.read(); self.call(NFTPunk::at(nft).burn(self.msg_sender(), token_id)); } ``` > [Source code: docs/examples/contracts/nft\_bridge/src/main.nr#L52-L65](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/nft_bridge/src/main.nr#L52-L65) Cross-chain messaging on Aztec is powerful because it doesn't conform to any specific format—you can structure messages however you want. Private Functions Both `claim` and `exit` are `#[external("private")]`, which means the bridging process is private—nobody can see who's bridging which NFT by watching the chain. ### Compile the Bridge[​](#compile-the-bridge "Direct link to Compile the Bridge") ``` aztec compile ``` Bridge compiled successfully! Now process both contracts and generate TypeScript bindings: ``` cd ../nft aztec codegen target --outdir ../artifacts cd ../nft_bridge aztec codegen target --outdir ../artifacts ``` An `artifacts` folder should appear with TypeScript bindings for each contract. You'll use these when deploying the contracts. ## Part 3: The Ethereum Side[​](#part-3-the-ethereum-side "Direct link to Part 3: The Ethereum Side") Now build the L1 contracts. You need: * A simple ERC721 NFT contract (the "CryptoPunk") * A portal contract that locks/unlocks NFTs and communicates with Aztec ### Install Dependencies[​](#install-dependencies "Direct link to Install Dependencies") Aztec's contracts are already in your `package.json`. You just need to add the OpenZeppelin contracts that provide the default ERC721 implementation: ``` cd ../../.. yarn add @openzeppelin/contracts ``` ### Create a Simple NFT[​](#create-a-simple-nft "Direct link to Create a Simple NFT") Delete the "Counter" contracts that show up by default in `contracts` and create `contracts/SimpleNFT.sol`: ``` touch contracts/SimpleNFT.sol ``` Create a minimal NFT contract sufficient for demonstrating bridging: simple\_nft ``` pragma solidity >=0.8.27; import {ERC721} from "@oz/token/ERC721/ERC721.sol"; contract SimpleNFT is ERC721 { uint256 private _currentTokenId; constructor() ERC721("SimplePunk", "SPUNK") {} function mint(address to) external returns (uint256) { uint256 tokenId = _currentTokenId++; _mint(to, tokenId); return tokenId; } } ``` > [Source code: docs/examples/solidity/nft\_bridge/SimpleNFT.sol#L2-L18](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/solidity/nft_bridge/SimpleNFT.sol#L2-L18) ### Create the NFT Portal[​](#create-the-nft-portal "Direct link to Create the NFT Portal") The NFT Portal has more code, so build it step-by-step. Create `contracts/NFTPortal.sol`: ``` touch contracts/NFTPortal.sol ``` Initialize it with Aztec's registry, which holds the canonical contracts for Aztec-related contracts, including the Inbox and Outbox. These are the message-passing contracts—Aztec sequencers read any messages on these contracts. ``` import {IERC721} from "@oz/token/ERC721/IERC721.sol"; import {IRegistry} from "@aztec/governance/interfaces/IRegistry.sol"; import {IInbox} from "@aztec/core/interfaces/messagebridge/IInbox.sol"; import {IOutbox} from "@aztec/core/interfaces/messagebridge/IOutbox.sol"; import {IRollup} from "@aztec/core/interfaces/IRollup.sol"; import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; import {Hash} from "@aztec/core/libraries/crypto/Hash.sol"; import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; contract NFTPortal { IRegistry public registry; IERC721 public nftContract; bytes32 public l2Bridge; IRollup public rollup; IOutbox public outbox; IInbox public inbox; uint256 public rollupVersion; function initialize(address _registry, address _nftContract, bytes32 _l2Bridge) external { registry = IRegistry(_registry); nftContract = IERC721(_nftContract); l2Bridge = _l2Bridge; rollup = IRollup(address(registry.getCanonicalRollup())); outbox = rollup.getOutbox(); inbox = rollup.getInbox(); rollupVersion = rollup.getVersion(); } } ``` The core logic is similar to the L2 logic. `depositToAztec` calls the `Inbox` canonical contract to send a message to Aztec, and `withdraw` calls the `Outbox` contract. Add these two functions with explanatory comments: portal\_deposit\_and\_withdraw ``` // Lock NFT and send message to L2 function depositToAztec(uint256 tokenId, bytes32 secretHash) external returns (bytes32, uint256) { // Lock the NFT nftContract.transferFrom(msg.sender, address(this), tokenId); // Prepare L2 message - just a naive hash of our tokenId DataStructures.L2Actor memory actor = DataStructures.L2Actor(l2Bridge, rollupVersion); bytes32 contentHash = Hash.sha256ToField(abi.encode(tokenId)); // Send message to Aztec (bytes32 key, uint256 index) = inbox.sendL2Message(actor, contentHash, secretHash); return (key, index); } // Unlock NFT after L2 burn function withdraw( uint256 tokenId, Epoch epoch, uint256 numCheckpointsInEpoch, uint256 leafIndex, bytes32[] calldata path ) external { // Verify message from L2 DataStructures.L2ToL1Msg memory message = DataStructures.L2ToL1Msg({ sender: DataStructures.L2Actor(l2Bridge, rollupVersion), recipient: DataStructures.L1Actor(address(this), block.chainid), content: Hash.sha256ToField(abi.encodePacked(tokenId, msg.sender)) }); outbox.consume(message, epoch, numCheckpointsInEpoch, leafIndex, path); // Unlock NFT nftContract.transferFrom(address(this), msg.sender, tokenId); } ``` > [Source code: docs/examples/solidity/nft\_bridge/NFTPortal.sol#L37-L72](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/solidity/nft_bridge/NFTPortal.sol#L37-L72) The portal handles two flows: * **depositToAztec**: Locks NFT on L1, sends message to L2 * **withdraw**: Verifies L2 message, unlocks NFT on L1 ### Compile[​](#compile "Direct link to Compile") Let's make sure everything compiles: ``` npx hardhat compile ``` You should see successful compilation of both contracts! ## Part 4: Compiling, Deploying, and Testing[​](#part-4-compiling-deploying-and-testing "Direct link to Part 4: Compiling, Deploying, and Testing") Now deploy everything and test the full flow. This will help you understand how everything fits together. Delete the placeholders in `scripts` and create `index.ts`: ``` touch scripts/index.ts ``` This script will implement the user flow. Testnet This section assumes you're working locally using the local network. For the testnet, you need to account for some things: * Your clients need to point to some Sepolia Node and to the public Aztec Full Node * You need to [deploy your own Aztec accounts](/developers/testnet/docs/aztec-js/how_to_create_account.md) * You need to pay fees in some other way. Learn how in the [fees guide](/developers/testnet/docs/aztec-js/how_to_pay_fees.md) ### Deploying and Initializing[​](#deploying-and-initializing "Direct link to Deploying and Initializing") First, initialize the clients: `aztec.js` for Aztec and `viem` for Ethereum: setup ``` import { getInitialTestAccountsData } from "@aztec/accounts/testing"; import { AztecAddress, EthAddress } from "@aztec/aztec.js/addresses"; import { Fr } from "@aztec/aztec.js/fields"; import { createAztecNodeClient } from "@aztec/aztec.js/node"; import { createExtendedL1Client } from "@aztec/ethereum/client"; import { deployL1Contract } from "@aztec/ethereum/deploy-l1-contract"; import { sha256ToField } from "@aztec/foundation/crypto/sha256"; import { computeL2ToL1MessageHash, computeSecretHash, } from "@aztec/stdlib/hash"; import { EmbeddedWallet } from "@aztec/wallets/embedded"; import { decodeEventLog, pad } from "@aztec/viem"; import { foundry } from "@aztec/viem/chains"; import NFTPortal from "../../../target/solidity/nft_bridge/NFTPortal.sol/NFTPortal.json" with { type: "json" }; import SimpleNFT from "../../../target/solidity/nft_bridge/SimpleNFT.sol/SimpleNFT.json" with { type: "json" }; import { NFTBridgeContract } from "./artifacts/NFTBridge.js"; import { NFTPunkContract } from "./artifacts/NFTPunk.js"; // Setup L1 client using anvil's default mnemonic (same as e2e tests) const MNEMONIC = "test test test test test test test test test test test junk"; const l1Client = createExtendedL1Client(["http://localhost:8545"], MNEMONIC); const ownerEthAddress = l1Client.account.address; // Setup L2 using Aztec's local network and one of its initial accounts console.log("Setting up L2...\n"); const node = createAztecNodeClient("http://localhost:8080"); const aztecWallet = await EmbeddedWallet.create(node); const [accData] = await getInitialTestAccountsData(); const account = await aztecWallet.createSchnorrInitializerlessAccount( accData.secret, accData.salt, ); console.log(`Account: ${account.address.toString()}\n`); // Get node info const nodeInfo = await node.getNodeInfo(); const registryAddress = nodeInfo.l1ContractAddresses.registryAddress.toString(); const inboxAddress = nodeInfo.l1ContractAddresses.inboxAddress.toString(); ``` > [Source code: docs/examples/ts/token\_bridge/index.ts#L1-L41](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/token_bridge/index.ts#L1-L41) Adjust the artifact imports for this project's layout The snippet above comes from the monorepo's runnable example, and its artifact imports point at that repo's layout. In the Hardhat project used in this tutorial, replace the four artifact imports with: ``` import NFTPortal from "../artifacts/contracts/NFTPortal.sol/NFTPortal.json" with { type: "json" }; import SimpleNFT from "../artifacts/contracts/SimpleNFT.sol/SimpleNFT.json" with { type: "json" }; import { NFTBridgeContract } from "../contracts/aztec/artifacts/NFTBridge.js"; import { NFTPunkContract } from "../contracts/aztec/artifacts/NFTPunk.js"; ``` `npx hardhat compile` writes the Solidity artifacts to `artifacts/contracts/`, and the `aztec codegen` commands from earlier wrote the TypeScript bindings to `contracts/aztec/artifacts/`. Hardhat artifacts also store the bytecode as a plain string, so in the deployment snippet below use `SimpleNFT.bytecode` and `NFTPortal.bytecode` instead of `.bytecode.object`. You now have wallets for both chains, correctly connected to their respective chains. Next, deploy the L1 contracts: deploy\_l1\_contracts ``` console.log("Deploying L1 contracts...\n"); const { address: nftAddress } = await deployL1Contract( l1Client, SimpleNFT.abi, SimpleNFT.bytecode.object as `0x${string}`, ); const { address: portalAddress } = await deployL1Contract( l1Client, NFTPortal.abi, NFTPortal.bytecode.object as `0x${string}`, ); console.log(`SimpleNFT: ${nftAddress}`); console.log(`NFTPortal: ${portalAddress}\n`); ``` > [Source code: docs/examples/ts/token\_bridge/index.ts#L43-L60](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/token_bridge/index.ts#L43-L60) Now deploy the L2 contracts. Thanks to the TypeScript bindings generated with `aztec codegen`, deployment is straightforward: deploy\_l2\_contracts ``` console.log("Deploying L2 contracts...\n"); const { contract: l2Nft } = await NFTPunkContract.deploy( aztecWallet, account.address, ).send({ from: account.address, }); const { contract: l2Bridge } = await NFTBridgeContract.deploy( aztecWallet, l2Nft.address, ).send({ from: account.address }); console.log(`L2 NFT: ${l2Nft.address.toString()}`); console.log(`L2 Bridge: ${l2Bridge.address.toString()}\n`); ``` > [Source code: docs/examples/ts/token\_bridge/index.ts#L62-L79](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/token_bridge/index.ts#L62-L79) Now that you have the L2 bridge's contract address, initialize the L1 bridge: initialize\_portal ``` console.log("Initializing portal..."); // Initialize the portal contract // @ts-expect-error - viem type inference doesn't work with JSON-imported ABIs const initHash = await l1Client.writeContract({ address: portalAddress.toString() as `0x${string}`, abi: NFTPortal.abi, functionName: "initialize", args: [registryAddress, nftAddress.toString(), l2Bridge.address.toString()], }); await l1Client.waitForTransactionReceipt({ hash: initHash }); console.log("Portal initialized\n"); ``` > [Source code: docs/examples/ts/token\_bridge/index.ts#L81-L95](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/token_bridge/index.ts#L81-L95) The L2 contracts were already initialized when you deployed them, but you still need to: * Tell the L2 bridge about Ethereum's portal address (by calling `set_portal` on the bridge) * Tell the L2 NFT contract who the minter is (by calling `set_minter` on the L2 NFT contract) Complete these initialization steps: initialize\_l2\_bridge ``` console.log("Setting up L2 bridge..."); await l2Bridge.methods .set_portal(EthAddress.fromString(portalAddress.toString())) .send({ from: account.address }); await l2Nft.methods .set_minter(l2Bridge.address) .send({ from: account.address }); console.log("Bridge configured\n"); ``` > [Source code: docs/examples/ts/token\_bridge/index.ts#L97-L109](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/token_bridge/index.ts#L97-L109) This completes the setup. It's a lot of configuration, but you're dealing with four contracts across two chains. ### L1 → L2 Flow[​](#l1--l2-flow "Direct link to L1 → L2 Flow") Now for the main flow. Mint a CryptoPunk on L1, deposit it to Aztec, and claim it on Aztec. Put everything in the same script. To mint, call the L1 contract with `mint`, which will mint `tokenId = 0`: mint\_nft\_l1 ``` console.log("Minting NFT on L1..."); // @ts-expect-error - viem type inference doesn't work with JSON-imported ABIs const mintHash = await l1Client.writeContract({ address: nftAddress.toString() as `0x${string}`, abi: SimpleNFT.abi, functionName: "mint", args: [ownerEthAddress], }); await l1Client.waitForTransactionReceipt({ hash: mintHash }); // no need to parse logs, this will be tokenId 0 since it's a fresh contract const tokenId = 0n; console.log(`Minted tokenId: ${tokenId}\n`); ``` > [Source code: docs/examples/ts/token\_bridge/index.ts#L111-L127](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/token_bridge/index.ts#L111-L127) To bridge, first approve the portal address to transfer the NFT, then transfer it by calling `depositToAztec`: deposit\_to\_aztec ``` console.log("Depositing NFT to Aztec..."); const secret = Fr.random(); const secretHash = await computeSecretHash(secret); // Approve portal to transfer the NFT // @ts-expect-error - viem type inference doesn't work with JSON-imported ABIs const approveHash = await l1Client.writeContract({ address: nftAddress.toString() as `0x${string}`, abi: SimpleNFT.abi, functionName: "approve", args: [portalAddress.toString(), tokenId], }); await l1Client.waitForTransactionReceipt({ hash: approveHash }); // Deposit to Aztec // @ts-expect-error - viem type inference doesn't work with JSON-imported ABIs const depositHash = await l1Client.writeContract({ address: portalAddress.toString() as `0x${string}`, abi: NFTPortal.abi, functionName: "depositToAztec", args: [ tokenId, pad(secretHash.toString() as `0x${string}`, { dir: "left", size: 32 }), ], }); const depositReceipt = await l1Client.waitForTransactionReceipt({ hash: depositHash, }); ``` > [Source code: docs/examples/ts/token\_bridge/index.ts#L129-L159](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/token_bridge/index.ts#L129-L159) The `Inbox` contract will emit an important log: `MessageSent(inProgress, index, leaf, updatedRollingHash);`. This log provides the **leaf index** of the message in the [L1-L2 Message Tree](/developers/testnet/docs/foundational-topics/ethereum-aztec-messaging.md)—the location of the message in the tree that will appear on L2. You need this index, plus the secret, to correctly claim and decrypt the message. Use viem to extract this information: get\_message\_leaf\_index ``` const INBOX_ABI = [ { type: "event", name: "MessageSent", inputs: [ { name: "checkpointNumber", type: "uint256", indexed: true }, { name: "index", type: "uint256", indexed: false }, { name: "hash", type: "bytes32", indexed: true }, { name: "rollingHash", type: "bytes16", indexed: false }, ], }, ] as const; // Find and decode the MessageSent event from the Inbox contract const messageSentLogs = depositReceipt.logs .filter((log) => log.address.toLowerCase() === inboxAddress.toLowerCase()) .map((log: any) => { try { const decoded = decodeEventLog({ abi: INBOX_ABI, data: log.data, topics: log.topics, }); return { log, decoded }; } catch { // Not a decodable event from this ABI return null; } }) .filter( (item): item is { log: any; decoded: any } => item !== null && (item.decoded as any).eventName === "MessageSent", ); const messageLeafIndex = new Fr(messageSentLogs[0].decoded.args.index); ``` > [Source code: docs/examples/ts/token\_bridge/index.ts#L161-L197](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/token_bridge/index.ts#L161-L197) This extracts the logs from the deposit and retrieves the leaf index. You can now claim it on L2. However, for security reasons, at least 2 blocks must pass before a message can be claimed on L2. If you called `claim` on the L2 contract immediately, it would return "no message available". Add a utility function to mine two blocks (it deploys a contract with a random salt): mine\_blocks ``` async function mine2Blocks( aztecWallet: EmbeddedWallet, accountAddress: AztecAddress, ) { await NFTPunkContract.deploy(aztecWallet, accountAddress).send({ from: accountAddress, }); await NFTPunkContract.deploy(aztecWallet, accountAddress).send({ from: accountAddress, }); } ``` > [Source code: docs/examples/ts/token\_bridge/index.ts#L199-L211](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/token_bridge/index.ts#L199-L211) Now claim the message on L2: claim\_on\_l2 ``` // Mine blocks await mine2Blocks(aztecWallet, account.address); // Check notes before claiming (should be 0) console.log("Checking notes before claim..."); const { result: notesBefore } = await l2Nft.methods .notes_of(account.address) .simulate({ from: account.address }); console.log(` Notes count: ${notesBefore}`); console.log("Claiming NFT on L2..."); await l2Bridge.methods .claim(account.address, new Fr(Number(tokenId)), secret, messageLeafIndex) .send({ from: account.address }); console.log("NFT claimed on L2\n"); // Check notes after claiming (should be 1) console.log("Checking notes after claim..."); const { result: notesAfterClaim } = await l2Nft.methods .notes_of(account.address) .simulate({ from: account.address }); console.log(` Notes count: ${notesAfterClaim}\n`); ``` > [Source code: docs/examples/ts/token\_bridge/index.ts#L213-L236](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/token_bridge/index.ts#L213-L236) ### L2 → L1 Flow[​](#l2--l1-flow "Direct link to L2 → L1 Flow") Great! You can expand the L2 contract to add features like NFT transfers. For now, exit the NFT on L2 and redeem it on L1. Mine two blocks because of `DelayedMutable`: exit\_from\_l2 ``` // L2 -> L1 flow console.log("Exiting NFT from L2..."); // Mine blocks, not necessary on devnet, but must wait for 2 blocks await mine2Blocks(aztecWallet, account.address); const recipientEthAddress = EthAddress.fromString(ownerEthAddress); const { receipt: exitReceipt } = await l2Bridge.methods .exit(new Fr(Number(tokenId)), recipientEthAddress) .send({ from: account.address }); console.log(`Exit message sent (block: ${exitReceipt.blockNumber})\n`); // Check notes after burning (should be 0 again) console.log("Checking notes after burn..."); const { result: notesAfterBurn } = await l2Nft.methods .notes_of(account.address) .simulate({ from: account.address }); console.log(` Notes count: ${notesAfterBurn}\n`); ``` > [Source code: docs/examples/ts/token\_bridge/index.ts#L238-L258](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/token_bridge/index.ts#L238-L258) Just like in the L1 → L2 flow, you need to know what to claim on L1. Where in the message tree is the message you want to claim? Use `node.getL2ToL1MembershipWitness`, which provides the leaf and the sibling path of the message: get\_withdrawal\_witness ``` // Compute the message hash directly from known parameters // This matches what the portal contract expects: Hash.sha256ToField(abi.encodePacked(tokenId, recipient)) const tokenIdBuffer = new Fr(Number(tokenId)).toBuffer(); const recipientBuffer = Buffer.from( recipientEthAddress.toString().slice(2), "hex", ); const content = sha256ToField([tokenIdBuffer, recipientBuffer]); // Get rollup version from the portal contract (it stores it during initialize) // @ts-expect-error - viem type inference doesn't work with JSON-imported ABIs const version = (await l1Client.readContract({ address: portalAddress.toString() as `0x${string}`, abi: NFTPortal.abi, functionName: "rollupVersion", })) as bigint; // Compute the L2->L1 message hash const msgLeaf = computeL2ToL1MessageHash({ l2Sender: l2Bridge.address, l1Recipient: EthAddress.fromString(portalAddress.toString()), content, rollupVersion: new Fr(version), chainId: new Fr(foundry.id), }); // Wait for the block to be proven before withdrawing // Waiting for the block to be proven is not necessary on the local network, but it is necessary on devnet console.log("Waiting for block to be proven..."); console.log(` Exit block number: ${exitReceipt.blockNumber}`); let provenBlockNumber = await node.getBlockNumber("proven"); console.log(` Current proven block: ${provenBlockNumber}`); while (provenBlockNumber < exitReceipt.blockNumber!) { console.log( ` Waiting... (proven: ${provenBlockNumber}, needed: ${exitReceipt.blockNumber})`, ); await new Promise((resolve) => setTimeout(resolve, 10000)); // Wait 10 seconds provenBlockNumber = await node.getBlockNumber("proven"); } console.log("Block proven!\n"); // Compute the membership witness using the message hash and the L2 tx hash. // The node picks the smallest partial-proof root that covers the tx's checkpoint. const witness = await node.getL2ToL1MembershipWitness( exitReceipt.txHash, msgLeaf, ); const epoch = witness!.epochNumber; const numCheckpointsInEpoch = witness!.numCheckpointsInEpoch; console.log(` Epoch for block ${exitReceipt.blockNumber}: ${epoch}`); const siblingPathHex = witness!.siblingPath .toBufferArray() .map((buf: Buffer) => `0x${buf.toString("hex")}` as `0x${string}`); ``` > [Source code: docs/examples/ts/token\_bridge/index.ts#L260-L318](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/token_bridge/index.ts#L260-L318) With this information, call the L1 contract and use the index and the sibling path to claim the L1 NFT: withdraw\_on\_l1 ``` console.log("Withdrawing NFT on L1..."); // @ts-expect-error - viem type inference doesn't work with JSON-imported ABIs const withdrawHash = await l1Client.writeContract({ address: portalAddress.toString() as `0x${string}`, abi: NFTPortal.abi, functionName: "withdraw", args: [ tokenId, BigInt(epoch), BigInt(numCheckpointsInEpoch), BigInt(witness!.leafIndex), siblingPathHex, ], }); await l1Client.waitForTransactionReceipt({ hash: withdrawHash }); console.log("NFT withdrawn to L1\n"); ``` > [Source code: docs/examples/ts/token\_bridge/index.ts#L320-L337](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/token_bridge/index.ts#L320-L337) You can now try the whole flow with: ``` npx tsx scripts/index.ts ``` ## What You Built[​](#what-you-built "Direct link to What You Built") A complete private NFT bridge with: 1. **L1 Contracts** (Solidity) * `SimpleNFT`: Basic ERC721 for testing * `NFTPortal`: Locks/unlocks NFTs and handles L1↔L2 messaging 2. **L2 Contracts** (Noir) * `NFTPunk`: Private NFT with encrypted ownership using `PrivateSet` * `NFTBridge`: Claims L1 messages and mints NFTs privately 3. **Full Flow** * Mint NFT on L1 * Deploy portal and bridge * Lock NFT on L1 → message sent to L2 * Claim on L2 → private NFT minted * Later: Burn on L2 → message to L1 → unlock ## Going Further: The AIP-721 NFT Standard[​](#going-further-the-aip-721-nft-standard "Direct link to Going Further: The AIP-721 NFT Standard") The NFTPunk contract you built in this tutorial implements a simplified NFT with private ownership. The **AIP-721** standard formalizes these patterns and adds partial-note transfers, commitment-based handoffs, all 7 cross-domain transfer patterns, and authwit-based authorization. Read the full [AIP-721 standard reference](/developers/testnet/docs/aztec-nr/standards/aip-721.md) for details, or explore all [Aztec Contract Standards](/developers/testnet/docs/aztec-nr/standards.md). ## Next Steps[​](#next-steps "Direct link to Next Steps") * Add a web frontend for easy bridging * Implement batch bridging for multiple NFTs * Add metadata bridging * Write comprehensive tests * Add proper access controls * Explore the [AIP-721 standard](/developers/testnet/docs/aztec-nr/standards/aip-721.md) for production-grade NFT patterns Learn More * [State management page](/developers/testnet/docs/foundational-topics/state_management.md) * [Cross-chain messaging](/developers/testnet/docs/foundational-topics/ethereum-aztec-messaging.md) * [Aztec Contract Standards](/developers/testnet/docs/aztec-nr/standards.md) --- # Cross-Chain Token Swap (L1 <> L2) ## Why Build a Cross-Chain Swap?[​](#why-build-a-cross-chain-swap "Direct link to Why Build a Cross-Chain Swap?") DeFi liquidity lives on Ethereum L1. Users with tokens on Aztec L2 need a way to access L1 DEXs like Uniswap without manually bridging tokens back and forth. A cross-chain swap automates this: the user initiates the swap on L2, and the protocol handles exiting to L1, performing the swap, and depositing the output back to L2. This tutorial walks you through building a version of this flow. You will learn how [**L2-to-L1 messages**](/developers/testnet/docs/aztec-nr/framework-description/ethereum_aztec_messaging.md) work and how multiple contracts across two chains coordinate to execute a single user action. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before starting this tutorial, you need: 1. **Aztec local network** running at version v5.0.0-rc.2 -- see [the local network guide](/developers/testnet/getting_started_on_local_network.md) for setup instructions 2. **Node.js** (v24+) and a package manager (yarn or npm) 3. **Familiarity with the token bridge tutorial** -- this tutorial builds on concepts from [Bridge Your NFT to Aztec](/developers/testnet/docs/tutorials/js_tutorials/token_bridge.md), especially portal contracts and cross-chain messaging Background Knowledge If you are new to Aztec's cross-chain architecture, review [Ethereum-Aztec Messaging](/developers/testnet/docs/aztec-nr/framework-description/ethereum_aztec_messaging.md) first. Key concepts used throughout this tutorial: * **Portals** -- L1 contracts that communicate with L2 contracts via the Aztec messaging protocol * **L2-to-L1 messages** -- messages sent from Aztec to Ethereum, stored in a Merkle tree and consumed on L1 * **Authorization witnesses (authwit)** -- Aztec's alternative to ERC20 approve/transferFrom ([learn more](/developers/testnet/docs/aztec-nr/framework-description/authentication_witnesses.md)) * **Content hashes** -- cryptographic digests that uniquely identify cross-chain messages, ensuring L1 and L2 agree on message parameters ## Project Setup[​](#project-setup "Direct link to Project Setup") This tutorial walks you through three types of contracts (Solidity, Noir, TypeScript) that work together. You will clone the example project and build it as you follow along. ### Clone the Example Code[​](#clone-the-example-code "Direct link to Clone the Example Code") The example code lives in the Aztec packages repository: ``` git clone --depth 1 --branch v5.0.0-rc.2 https://github.com/AztecProtocol/aztec-packages.git cd aztec-packages/docs/examples ``` ### Project Structure[​](#project-structure "Direct link to Project Structure") The relevant files are spread across three directories: ``` examples/ ├── contracts/ │ └── example_uniswap/ │ ├── Nargo.toml # Noir package config │ └── src/ │ ├── main.nr # L2 uniswap contract │ └── util.nr # Content hash helpers ├── solidity/ │ ├── foundry.toml # Solidity compiler config │ └── example_swap/ │ ├── ExampleERC20.sol # Minimal ERC20 tokens (WETH, DAI) │ ├── ExampleTokenPortal.sol # L1 token bridge portal │ └── ExampleUniswapPortal.sol # L1 swap orchestrator └── ts/ └── example_swap/ ├── index.ts # TypeScript orchestration script └── config.yaml # Build configuration ``` ### Dependencies[​](#dependencies "Direct link to Dependencies") **Solidity** (via Foundry import mappings in `foundry.toml`): * OpenZeppelin ERC20 (`@oz/token/ERC20/`) * Aztec L1 contracts (`@aztec/core`, `@aztec/governance`) **Noir** (in `Nargo.toml`): * `aztec` -- the Aztec Noir framework * `token` -- the standard Token contract * `token_bridge` -- the standard TokenBridge contract * `keccak256` -- for computing Solidity-compatible function selectors **TypeScript**: * `@aztec/aztec.js`, `@aztec/accounts`, `@aztec/wallets`, `@aztec/stdlib` * `@aztec/ethereum`, `@aztec/noir-contracts.js`, `@aztec/foundation` note The TypeScript script imports compiled Solidity artifacts and a generated Noir artifact (`ExampleUniswapContract`). You will compile both before running the script. ## What You'll Build[​](#what-youll-build "Direct link to What You'll Build") Each swap generates **two L2-to-L1 messages**, both of which must be consumed on L1 before the swap executes: 1. **Token bridge exit** - Authorizes releasing input tokens from the token portal to the uniswap portal 2. **Swap intent** - Proves the user authorized *this specific swap* with *these exact parameters* Neither message alone is sufficient. If only the token exit existed, anyone observing it could potentially redirect the swap. If only the swap intent existed, there would be no proof that tokens were actually withdrawn. Together, they create a cryptographic chain of authorization. This two-message pattern is common in Aztec cross-chain applications where multiple independent systems must coordinate. ## Part 1: Token Portal (Solidity)[​](#part-1-token-portal-solidity "Direct link to Part 1: Token Portal (Solidity)") The token portal handles depositing tokens from L1 to L2 and withdrawing from L2 to L1. This is a simplified version for tutorial purposes -- for a deeper look at how portals work, see the [token bridge tutorial](/developers/testnet/docs/tutorials/js_tutorials/token_bridge.md). example\_token\_portal ``` import {IERC20} from "@oz/token/ERC20/IERC20.sol"; import {SafeERC20} from "@oz/token/ERC20/utils/SafeERC20.sol"; import {IRegistry} from "@aztec/governance/interfaces/IRegistry.sol"; import {IInbox} from "@aztec/core/interfaces/messagebridge/IInbox.sol"; import {IOutbox} from "@aztec/core/interfaces/messagebridge/IOutbox.sol"; import {IRollup} from "@aztec/core/interfaces/IRollup.sol"; import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; import {Hash} from "@aztec/core/libraries/crypto/Hash.sol"; /// @title ExampleTokenPortal /// @notice Example token portal for tutorial. contract ExampleTokenPortal { using SafeERC20 for IERC20; IRegistry public registry; IERC20 public underlying; bytes32 public l2Bridge; IRollup public rollup; IOutbox public outbox; IInbox public inbox; uint256 public rollupVersion; /// @dev No access control for simplicity. A production contract should restrict this to the deployer/owner. function initialize(address _registry, address _underlying, bytes32 _l2Bridge) external { registry = IRegistry(_registry); underlying = IERC20(_underlying); l2Bridge = _l2Bridge; rollup = IRollup(address(registry.getCanonicalRollup())); outbox = rollup.getOutbox(); inbox = rollup.getInbox(); rollupVersion = rollup.getVersion(); } ``` > [Source code: docs/examples/solidity/example\_swap/ExampleTokenPortal.sol#L4-L41](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/solidity/example_swap/ExampleTokenPortal.sol#L4-L41) Key functions: * `depositToAztecPublic` - Locks ERC20 tokens and sends an L1→L2 message for public minting * `depositToAztecPrivate` - Same but for private minting * `withdraw` - Consumes an L2→L1 message and releases tokens The [registry](/developers/testnet/docs/aztec-nr/framework-description/ethereum_aztec_messaging.md) provides governance-updateable addresses for core Aztec contracts. Rather than hardcoding rollup addresses, portals query the registry, allowing the protocol to upgrade without redeploying all portals. Each cross-chain message includes a **content hash** -- a `sha256` digest of the function selector and its parameters that uniquely identifies the message. The content hash is computed with `Hash.sha256ToField(abi.encodeWithSignature(...))`, where `abi.encodeWithSignature` prepends a 4-byte function selector (keccak256 of the function signature, per Solidity convention). This makes each message type unique, preventing a deposit message from being confused with a withdrawal message. deposit\_to\_aztec\_public ``` /// @notice Deposit tokens and send L1->L2 message for public minting on Aztec function depositToAztecPublic(bytes32 _to, uint256 _amount, bytes32 _secretHash) external returns (bytes32, uint256) { DataStructures.L2Actor memory actor = DataStructures.L2Actor(l2Bridge, rollupVersion); bytes32 contentHash = Hash.sha256ToField(abi.encodeWithSignature("mint_to_public(bytes32,uint256)", _to, _amount)); underlying.safeTransferFrom(msg.sender, address(this), _amount); return inbox.sendL2Message(actor, contentHash, _secretHash); } ``` > [Source code: docs/examples/solidity/example\_swap/ExampleTokenPortal.sol#L43-L59](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/solidity/example_swap/ExampleTokenPortal.sol#L43-L59) withdraw ``` /// @notice Withdraw tokens after consuming an L2->L1 message. /// @param _numCheckpointsInEpoch The partial-proof depth (1-indexed) the witness was built against. function withdraw( address _recipient, uint256 _amount, Epoch _epoch, uint256 _numCheckpointsInEpoch, uint256 _leafIndex, bytes32[] calldata _path ) external { DataStructures.L2ToL1Msg memory message = DataStructures.L2ToL1Msg({ sender: DataStructures.L2Actor(l2Bridge, rollupVersion), recipient: DataStructures.L1Actor(address(this), block.chainid), content: Hash.sha256ToField( abi.encodeWithSignature("withdraw(address,uint256,address)", _recipient, _amount, msg.sender) ) }); outbox.consume(message, _epoch, _numCheckpointsInEpoch, _leafIndex, _path); underlying.safeTransfer(_recipient, _amount); } ``` > [Source code: docs/examples/solidity/example\_swap/ExampleTokenPortal.sol#L72-L95](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/solidity/example_swap/ExampleTokenPortal.sol#L72-L95) ## Part 2: Uniswap Portal (Solidity)[​](#part-2-uniswap-portal-solidity "Direct link to Part 2: Uniswap Portal (Solidity)") The uniswap portal orchestrates the swap on L1. It consumes two L2→L1 messages, performs the swap, and deposits the output back to L2. Mock Swap This tutorial uses a mock 1:1 swap instead of a real Uniswap V3 router. The portal must be pre-funded with output tokens. The important part is the **message-passing pattern**, not the swap itself. example\_uniswap\_portal ``` import {IERC20} from "@oz/token/ERC20/IERC20.sol"; import {SafeERC20} from "@oz/token/ERC20/utils/SafeERC20.sol"; import {IRegistry} from "@aztec/governance/interfaces/IRegistry.sol"; import {IOutbox} from "@aztec/core/interfaces/messagebridge/IOutbox.sol"; import {IRollup} from "@aztec/core/interfaces/IRollup.sol"; import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; import {Hash} from "@aztec/core/libraries/crypto/Hash.sol"; import {ExampleTokenPortal} from "./ExampleTokenPortal.sol"; /// @title ExampleUniswapPortal /// @notice Example swap portal for tutorial. Instead of using a real Uniswap V3 router, /// performs a mock 1:1 swap by transferring pre-funded output tokens. /// Still demonstrates the core pattern: consuming 2 L2->L1 messages per swap. contract ExampleUniswapPortal { using SafeERC20 for IERC20; IRegistry public registry; bytes32 public l2UniswapAddress; IRollup public rollup; IOutbox public outbox; uint256 public rollupVersion; function initialize(address _registry, bytes32 _l2UniswapAddress) external { registry = IRegistry(_registry); l2UniswapAddress = _l2UniswapAddress; rollup = IRollup(address(registry.getCanonicalRollup())); outbox = rollup.getOutbox(); rollupVersion = rollup.getVersion(); } ``` > [Source code: docs/examples/solidity/example\_swap/ExampleUniswapPortal.sol#L4-L37](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/solidity/example_swap/ExampleUniswapPortal.sol#L4-L37) The public swap function consumes two messages and deposits the output: swap\_public ``` /// @notice Execute a public swap: consume 2 L2->L1 messages, mock-swap, deposit output to L2 /// @dev Message 1: TokenBridge exit (withdraw input tokens to this contract) /// Message 2: Uniswap swap intent (proves the user authorized this exact swap) function swapPublic( address _inputTokenPortal, uint256 _inAmount, uint24 _uniswapFeeTier, address _outputTokenPortal, uint256 _amountOutMinimum, bytes32 _aztecRecipient, bytes32 _secretHashForL1ToL2Message, // Outbox message metadata for the two L2->L1 messages Epoch[2] calldata _epochs, uint256[2] calldata _numCheckpointsInEpochs, uint256[2] calldata _leafIndices, bytes32[][2] calldata _paths ) external returns (bytes32, uint256) { IERC20 outputAsset = ExampleTokenPortal(_outputTokenPortal).underlying(); // Message 1: Consume the token bridge exit message (withdraw input tokens) ExampleTokenPortal(_inputTokenPortal) .withdraw(address(this), _inAmount, _epochs[0], _numCheckpointsInEpochs[0], _leafIndices[0], _paths[0]); // Message 2: Consume the uniswap swap intent message bytes32 contentHash = Hash.sha256ToField( abi.encodeWithSignature( "swap_public(address,uint256,uint24,address,uint256,bytes32,bytes32)", _inputTokenPortal, _inAmount, _uniswapFeeTier, _outputTokenPortal, _amountOutMinimum, _aztecRecipient, _secretHashForL1ToL2Message ) ); outbox.consume( DataStructures.L2ToL1Msg({ sender: DataStructures.L2Actor(l2UniswapAddress, rollupVersion), recipient: DataStructures.L1Actor(address(this), block.chainid), content: contentHash }), _epochs[1], _numCheckpointsInEpochs[1], _leafIndices[1], _paths[1] ); // Mock swap: 1:1 transfer (this contract must be pre-funded with output tokens) uint256 amountOut = _inAmount; require(amountOut >= _amountOutMinimum, "Insufficient output amount"); // Approve output token portal and deposit back to Aztec outputAsset.approve(_outputTokenPortal, amountOut); return ExampleTokenPortal(_outputTokenPortal) .depositToAztecPublic(_aztecRecipient, amountOut, _secretHashForL1ToL2Message); } ``` > [Source code: docs/examples/solidity/example\_swap/ExampleUniswapPortal.sol#L39-L99](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/solidity/example_swap/ExampleUniswapPortal.sol#L39-L99) The private swap follows the same pattern but deposits output tokens privately: swap\_private ``` /// @notice Execute a private swap: same pattern but deposits output privately function swapPrivate( address _inputTokenPortal, uint256 _inAmount, uint24 _uniswapFeeTier, address _outputTokenPortal, uint256 _amountOutMinimum, bytes32 _secretHashForL1ToL2Message, // Outbox message metadata for the two L2->L1 messages Epoch[2] calldata _epochs, uint256[2] calldata _numCheckpointsInEpochs, uint256[2] calldata _leafIndices, bytes32[][2] calldata _paths ) external returns (bytes32, uint256) { IERC20 outputAsset = ExampleTokenPortal(_outputTokenPortal).underlying(); // Message 1: Consume the token bridge exit message (withdraw input tokens) ExampleTokenPortal(_inputTokenPortal) .withdraw(address(this), _inAmount, _epochs[0], _numCheckpointsInEpochs[0], _leafIndices[0], _paths[0]); // Message 2: Consume the uniswap swap intent message bytes32 contentHash = Hash.sha256ToField( abi.encodeWithSignature( "swap_private(address,uint256,uint24,address,uint256,bytes32)", _inputTokenPortal, _inAmount, _uniswapFeeTier, _outputTokenPortal, _amountOutMinimum, _secretHashForL1ToL2Message ) ); outbox.consume( DataStructures.L2ToL1Msg({ sender: DataStructures.L2Actor(l2UniswapAddress, rollupVersion), recipient: DataStructures.L1Actor(address(this), block.chainid), content: contentHash }), _epochs[1], _numCheckpointsInEpochs[1], _leafIndices[1], _paths[1] ); // Mock swap: 1:1 transfer uint256 amountOut = _inAmount; require(amountOut >= _amountOutMinimum, "Insufficient output amount"); // Approve output token portal and deposit back to Aztec privately outputAsset.approve(_outputTokenPortal, amountOut); return ExampleTokenPortal(_outputTokenPortal).depositToAztecPrivate(amountOut, _secretHashForL1ToL2Message); } ``` > [Source code: docs/examples/solidity/example\_swap/ExampleUniswapPortal.sol#L101-L155](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/solidity/example_swap/ExampleUniswapPortal.sol#L101-L155) ### Compile Solidity Contracts[​](#compile-solidity-contracts "Direct link to Compile Solidity Contracts") With all Solidity contracts from Part 1 and Part 2 ready, compile them using Foundry. From the `examples/solidity` directory: ``` cd solidity forge build ``` This produces JSON artifacts containing the ABI and bytecode. The TypeScript script imports these artifacts to deploy contracts on L1. ## Part 3: Uniswap Contract (Noir)[​](#part-3-uniswap-contract-noir "Direct link to Part 3: Uniswap Contract (Noir)") The L2 contract handles the user-facing logic: transferring input tokens, calling the bridge to exit to L1, and creating the swap intent message. ### Setup[​](#setup "Direct link to Setup") The contract stores the portal address and imports the `Token` and `TokenBridge` contracts: example\_uniswap\_setup ``` mod util; // Example Uniswap L2 contract for tutorial purposes. // Demonstrates how to use portal contracts to swap on L1 with funds on L2. // Has two separate flows for public and private swaps. use aztec::macros::aztec; #[aztec] pub contract ExampleUniswap { use aztec::{ authwit::auth::{ assert_current_call_valid_authwit_public, compute_authwit_message_hash_from_call, set_authorized, }, macros::{functions::{external, initializer, only_self}, storage::storage}, protocol::{ abis::function_selector::FunctionSelector, address::{AztecAddress, EthAddress}, traits::ToField, }, state_vars::PublicImmutable, }; use crate::util::{compute_swap_private_content_hash, compute_swap_public_content_hash}; use token::Token; use token_bridge::TokenBridge; #[storage] struct Storage { portal_address: PublicImmutable, } #[external("public")] #[initializer] fn constructor(portal_address: EthAddress) { self.storage.portal_address.initialize(portal_address); } ``` > [Source code: docs/examples/contracts/example\_uniswap/src/main.nr#L1-L39](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/example_uniswap/src/main.nr#L1-L39) ### Public Swap[​](#public-swap "Direct link to Public Swap") The public swap transfers tokens from the sender to the contract, exits them to L1 via the bridge, and sends a swap intent message: Authorization Witnesses Aztec uses [**authorization witnesses** (authwit)](/developers/testnet/docs/aztec-nr/framework-description/authentication_witnesses.md) instead of the ERC20 approve/transferFrom pattern. The contract computes the hash of the exact action it wants to perform, sets that hash as authorized, then immediately performs the action. This gives fine-grained control - the authorization is for a specific action, not a blanket approval. Since we authorize and spend in the same transaction, replay attacks are impossible. swap\_public ``` #[external("public")] fn swap_public( sender: AztecAddress, input_asset_bridge: AztecAddress, input_amount: u128, output_asset_bridge: AztecAddress, // params for the swap uniswap_fee_tier: Field, minimum_output_amount: u128, // params for depositing output_asset back to Aztec recipient: AztecAddress, secret_hash_for_L1_to_l2_message: Field, ) { // If caller is not the sender, check they have approval if (!sender.eq(self.msg_sender())) { assert_current_call_valid_authwit_public(self.context, sender); } let input_asset_bridge_config = self.view(TokenBridge::at(input_asset_bridge).get_config_public()); let input_asset = input_asset_bridge_config.token; let input_asset_bridge_portal_address = input_asset_bridge_config.portal; // Transfer funds from sender to this contract // We use a fixed nonce since we authorize and spend in the same public call let nonce_for_transfer = 0xdeadbeef; let transfer_selector = FunctionSelector::from_signature("transfer_in_public((Field),(Field),u128,Field)"); let transfer_msg_hash = compute_authwit_message_hash_from_call( self.address, input_asset, self.context.chain_id(), self.context.version(), transfer_selector, [sender.to_field(), self.address.to_field(), input_amount as Field, nonce_for_transfer], ); set_authorized(self.context, transfer_msg_hash, true); self.call(Token::at(input_asset).transfer_in_public( sender, self.address, input_amount, nonce_for_transfer, )); // Approve bridge to burn this contract's funds and exit to L1 Uniswap Portal. // `let _ =` explicitly discards the return value (Noir requires handling all return values). let _ = self.call_self._approve_bridge_and_exit_input_asset_to_L1( input_asset, input_asset_bridge, input_amount, ); // Create swap message and send to Outbox for Uniswap Portal let output_asset_bridge_portal_address = self.view(TokenBridge::at(output_asset_bridge).get_config_public()).portal; // Ensure portals exist - else funds might be lost assert( !input_asset_bridge_portal_address.is_zero(), "L1 portal address of input_asset's bridge is 0", ); assert( !output_asset_bridge_portal_address.is_zero(), "L1 portal address of output_asset's bridge is 0", ); let content_hash = compute_swap_public_content_hash( input_asset_bridge_portal_address, input_amount, uniswap_fee_tier, output_asset_bridge_portal_address, minimum_output_amount, recipient, secret_hash_for_L1_to_l2_message, ); self.context.message_portal(self.storage.portal_address.read(), content_hash); } ``` > [Source code: docs/examples/contracts/example\_uniswap/src/main.nr#L41-L121](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/example_uniswap/src/main.nr#L41-L121) ### Private Swap[​](#private-swap "Direct link to Private Swap") The private swap is similar but uses `transfer_to_public` (private to public transfer) and [`enqueue_self`](/developers/testnet/docs/aztec-nr/framework-description/calling_contracts.md) instead of `call_self`. Because `swap_private` is a private function and `_approve_bridge_and_exit_input_asset_to_L1` is public, it cannot be called synchronously -- private functions execute before public functions in a transaction. `enqueue_self` schedules the public call to run in the public phase of the same transaction: swap\_private ``` #[external("private")] fn swap_private( input_asset: AztecAddress, input_asset_bridge: AztecAddress, input_amount: u128, output_asset_bridge: AztecAddress, // params for the swap uniswap_fee_tier: Field, minimum_output_amount: u128, // params for depositing output_asset back to Aztec secret_hash_for_L1_to_l2_message: Field, ) { let input_asset_bridge_config = self.view(TokenBridge::at(input_asset_bridge).get_config()); let output_asset_bridge_config = self.view(TokenBridge::at(output_asset_bridge).get_config()); // Verify the token address matches the bridge's config assert( input_asset.eq(input_asset_bridge_config.token), "input_asset address is not the same as seen in the bridge contract", ); // Transfer funds from sender to this contract (private -> public) // We use a fixed nonce since we authorize and spend in the same tx let nonce_for_transfer = 0xdeadbeef; self.call(Token::at(input_asset).transfer_to_public( self.msg_sender(), self.address, input_amount, nonce_for_transfer, )); // Approve bridge to burn this contract's funds and exit to L1 Uniswap Portal self.enqueue_self._approve_bridge_and_exit_input_asset_to_L1( input_asset, input_asset_bridge, input_amount, ); // Ensure portals exist assert( !input_asset_bridge_config.portal.is_zero(), "L1 portal address of input_asset's bridge is 0", ); assert( !output_asset_bridge_config.portal.is_zero(), "L1 portal address of output_asset's bridge is 0", ); let content_hash = compute_swap_private_content_hash( input_asset_bridge_config.portal, input_amount, uniswap_fee_tier, output_asset_bridge_config.portal, minimum_output_amount, secret_hash_for_L1_to_l2_message, ); self.context.message_portal(self.storage.portal_address.read(), content_hash); } ``` > [Source code: docs/examples/contracts/example\_uniswap/src/main.nr#L123-L184](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/example_uniswap/src/main.nr#L123-L184) Why no recipient parameter? In `swap_private`, the recipient is the person that provides the secret used to generate the hash for the L1 to L2 message. This preserves privacy: revealing a recipient address to L1 would compromise the caller's identity. The output tokens are deposited privately to L2, where only the secret holder can claim them. ### Bridge Helper[​](#bridge-helper "Direct link to Bridge Helper") Both flows share this internal function that approves the bridge to burn tokens and exits them to L1: approve\_bridge\_and\_exit ``` // Internal helper: approves the bridge to burn this contract's funds and exits to L1. // Used by both public and private swap flows. #[external("public")] #[only_self] fn _approve_bridge_and_exit_input_asset_to_L1( token: AztecAddress, token_bridge: AztecAddress, amount: u128, ) { // Since we authorize and instantly spend in the same public call, reuse a fixed nonce. let authwit_nonce = 0xdeadbeef; let selector = FunctionSelector::from_signature("burn_public((Field),u128,Field)"); let message_hash = compute_authwit_message_hash_from_call( token_bridge, token, self.context.chain_id(), self.context.version(), selector, [self.address.to_field(), amount as Field, authwit_nonce], ); set_authorized(self.context, message_hash, true); let this_portal_address = self.storage.portal_address.read(); // Exit to L1 Uniswap Portal self.call(TokenBridge::at(token_bridge).exit_to_l1_public( this_portal_address, amount, this_portal_address, authwit_nonce, )); } ``` > [Source code: docs/examples/contracts/example\_uniswap/src/main.nr#L186-L220](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/example_uniswap/src/main.nr#L186-L220) Portal Address Validation The portal address checks are a **safety mechanism**. If either portal is zero (not configured), the funds would be permanently lost. Always validate external addresses before sending irreversible messages. Fixed nonce safety The fixed nonce `0xdeadbeef` used throughout this contract is safe because authorization and token spending occur in the same transaction. There's no opportunity for replay attacks since the authorization is set and consumed atomically. ### Content Hash Helpers[​](#content-hash-helpers "Direct link to Content Hash Helpers") These content hashes form the **cross-chain contract interface**. The L2 contract computes a hash of all swap parameters, and the L1 portal reconstructs the same hash from the parameters it receives. If they don't match exactly, the message consumption fails. This is how L1 verifies that L2 actually authorized the swap - not by trusting a signature, but by independently computing what the message should contain. The hashes must match exactly between L2 (Noir) and L1 (Solidity): swap\_public\_content\_hash ``` use aztec::protocol::{ address::{AztecAddress, EthAddress}, hash::sha256_to_field, traits::ToField, }; // Hash byte sizes: number of fields x 32 bytes + 4 byte selector // Public: 7 fields (input portal, amount, fee, output portal, min output, recipient, secret hash) global PUBLIC_SWAP_HASH_SIZE: u32 = 228; // Private: 6 fields (no recipient - implicit from sender for privacy) global PRIVATE_SWAP_HASH_SIZE: u32 = 196; // Computes the L2 to L1 message content hash for the public swap flow. // Must match the hash computed in ExampleUniswapPortal.sol on L1. pub fn compute_swap_public_content_hash( input_asset_bridge_portal_address: EthAddress, input_amount: u128, uniswap_fee_tier: Field, output_asset_bridge_portal_address: EthAddress, minimum_output_amount: u128, aztec_recipient: AztecAddress, secret_hash_for_L1_to_l2_message: Field, ) -> Field { let mut hash_bytes = [0; PUBLIC_SWAP_HASH_SIZE]; let input_token_portal_bytes: [u8; 32] = input_asset_bridge_portal_address.to_field().to_be_bytes(); let in_amount_bytes: [u8; 32] = input_amount.to_field().to_be_bytes(); let uniswap_fee_tier_bytes: [u8; 32] = uniswap_fee_tier.to_be_bytes(); let output_token_portal_bytes: [u8; 32] = output_asset_bridge_portal_address.to_field().to_be_bytes(); let amount_out_min_bytes: [u8; 32] = minimum_output_amount.to_field().to_be_bytes(); let aztec_recipient_bytes: [u8; 32] = aztec_recipient.to_field().to_be_bytes(); let secret_hash_for_L1_to_l2_message_bytes: [u8; 32] = secret_hash_for_L1_to_l2_message.to_be_bytes(); // The function selector makes the message unique to this specific call type. let selector = comptime { keccak256::keccak256( "swap_public(address,uint256,uint24,address,uint256,bytes32,bytes32)".as_bytes(), 67, ) }; hash_bytes[0] = selector[0]; hash_bytes[1] = selector[1]; hash_bytes[2] = selector[2]; hash_bytes[3] = selector[3]; for i in 0..32 { hash_bytes[i + 4] = input_token_portal_bytes[i]; hash_bytes[i + 36] = in_amount_bytes[i]; hash_bytes[i + 68] = uniswap_fee_tier_bytes[i]; hash_bytes[i + 100] = output_token_portal_bytes[i]; hash_bytes[i + 132] = amount_out_min_bytes[i]; hash_bytes[i + 164] = aztec_recipient_bytes[i]; hash_bytes[i + 196] = secret_hash_for_L1_to_l2_message_bytes[i]; } sha256_to_field(hash_bytes) } ``` > [Source code: docs/examples/contracts/example\_uniswap/src/util.nr#L1-L62](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/example_uniswap/src/util.nr#L1-L62) swap\_private\_content\_hash ``` // Computes the L2 to L1 message content hash for the private swap flow. // Must match the hash computed in ExampleUniswapPortal.sol on L1. pub fn compute_swap_private_content_hash( input_asset_bridge_portal_address: EthAddress, input_amount: u128, uniswap_fee_tier: Field, output_asset_bridge_portal_address: EthAddress, minimum_output_amount: u128, secret_hash_for_L1_to_l2_message: Field, ) -> Field { let mut hash_bytes = [0; PRIVATE_SWAP_HASH_SIZE]; let input_token_portal_bytes: [u8; 32] = input_asset_bridge_portal_address.to_field().to_be_bytes(); let in_amount_bytes: [u8; 32] = input_amount.to_field().to_be_bytes(); let uniswap_fee_tier_bytes: [u8; 32] = uniswap_fee_tier.to_be_bytes(); let output_token_portal_bytes: [u8; 32] = output_asset_bridge_portal_address.to_field().to_be_bytes(); let amount_out_min_bytes: [u8; 32] = minimum_output_amount.to_field().to_be_bytes(); let secret_hash_for_L1_to_l2_message_bytes: [u8; 32] = secret_hash_for_L1_to_l2_message.to_be_bytes(); // The function selector makes the message unique to this specific call type. let selector = comptime { keccak256::keccak256( "swap_private(address,uint256,uint24,address,uint256,bytes32)".as_bytes(), 60, ) }; hash_bytes[0] = selector[0]; hash_bytes[1] = selector[1]; hash_bytes[2] = selector[2]; hash_bytes[3] = selector[3]; for i in 0..32 { hash_bytes[i + 4] = input_token_portal_bytes[i]; hash_bytes[i + 36] = in_amount_bytes[i]; hash_bytes[i + 68] = uniswap_fee_tier_bytes[i]; hash_bytes[i + 100] = output_token_portal_bytes[i]; hash_bytes[i + 132] = amount_out_min_bytes[i]; hash_bytes[i + 164] = secret_hash_for_L1_to_l2_message_bytes[i]; } sha256_to_field(hash_bytes) } ``` > [Source code: docs/examples/contracts/example\_uniswap/src/util.nr#L64-L110](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/contracts/example_uniswap/src/util.nr#L64-L110) ### Compile and Generate Bindings[​](#compile-and-generate-bindings "Direct link to Compile and Generate Bindings") From the `examples/contracts/example_uniswap` directory, compile the Noir contract and generate TypeScript bindings: ``` cd ../contracts/example_uniswap aztec compile aztec codegen target -o ../../ts/example_swap/artifacts ``` The `aztec compile` command compiles the Noir contract. The `aztec codegen` command generates a TypeScript class (`ExampleUniswapContract`) from the compiled artifact, which you will use in the deployment script. note Before proceeding, make sure you have compiled both the Solidity contracts (Part 1-2) and the Noir contract (Part 3). The TypeScript script below imports compiled artifacts from both. ## Install TypeScript Dependencies[​](#install-typescript-dependencies "Direct link to Install TypeScript Dependencies") From the `examples/ts/example_swap` directory, initialize a project and install the required packages: ``` cd ../../ts/example_swap npm init -y npm install \ @aztec/aztec.js@v5.0.0-rc.2 \ @aztec/accounts@v5.0.0-rc.2 \ @aztec/wallets@v5.0.0-rc.2 \ @aztec/stdlib@v5.0.0-rc.2 \ @aztec/ethereum@v5.0.0-rc.2 \ @aztec/noir-contracts.js@v5.0.0-rc.2 \ @aztec/foundation@v5.0.0-rc.2 \ npm:@aztec/viem@2.38.2 \ tsx ``` ## Part 4: Public Swap Flow (TypeScript)[​](#part-4-public-swap-flow-typescript "Direct link to Part 4: Public Swap Flow (TypeScript)") Now you can tie everything together in a TypeScript script. Start by setting up clients and deploying all contracts: setup ``` import { getInitialTestAccountsData } from "@aztec/accounts/testing"; import { AztecAddress, EthAddress } from "@aztec/aztec.js/addresses"; import { SetPublicAuthwitContractInteraction } from "@aztec/aztec.js/authorization"; import { Fr } from "@aztec/aztec.js/fields"; import { createAztecNodeClient, waitForNode } from "@aztec/aztec.js/node"; import { createExtendedL1Client } from "@aztec/ethereum/client"; import { deployL1Contract } from "@aztec/ethereum/deploy-l1-contract"; import { sha256ToField } from "@aztec/foundation/crypto/sha256"; import { TokenContract } from "@aztec/noir-contracts.js/Token"; import { TokenBridgeContract } from "@aztec/noir-contracts.js/TokenBridge"; import { computeL2ToL1MessageHash, computeSecretHash, } from "@aztec/stdlib/hash"; import { createAztecNodeDebugClient } from "@aztec/stdlib/interfaces/client"; import { decodeEventLog, encodeFunctionData, pad } from "@aztec/viem"; import { EmbeddedWallet } from "@aztec/wallets/embedded"; import { foundry } from "@aztec/viem/chains"; import ExampleERC20 from "../../../target/solidity/example_swap/ExampleERC20.sol/ExampleERC20.json" with { type: "json" }; import ExampleTokenPortal from "../../../target/solidity/example_swap/ExampleTokenPortal.sol/ExampleTokenPortal.json" with { type: "json" }; import ExampleUniswapPortal from "../../../target/solidity/example_swap/ExampleUniswapPortal.sol/ExampleUniswapPortal.json" with { type: "json" }; import { ExampleUniswapContract } from "./artifacts/ExampleUniswap.js"; // Setup L1 client const MNEMONIC = "test test test test test test test test test test test junk"; const l1RpcUrl = process.env.ETHEREUM_HOST ?? "http://localhost:8545"; const l1Client = createExtendedL1Client([l1RpcUrl], MNEMONIC); const ownerEthAddress = l1Client.account.address; // Setup L2 client console.log("Setting up L2...\n"); const nodeUrl = process.env.AZTEC_NODE_URL ?? "http://localhost:8080"; const node = createAztecNodeClient(nodeUrl); await waitForNode(node); const wallet = await EmbeddedWallet.create(node, { ephemeral: true }); const [accData] = await getInitialTestAccountsData(); const account = await wallet.createSchnorrInitializerlessAccount( accData.secret, accData.salt, accData.signingKey, ); console.log(`Account: ${account.address.toString()}\n`); const nodeInfo = await node.getNodeInfo(); const registryAddress = nodeInfo.l1ContractAddresses.registryAddress.toString(); const inboxAddress = nodeInfo.l1ContractAddresses.inboxAddress.toString(); ``` > [Source code: docs/examples/ts/example\_swap/index.ts#L1-L48](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/example_swap/index.ts#L1-L48) ### Deploy L1 Contracts[​](#deploy-l1-contracts "Direct link to Deploy L1 Contracts") Deploy two ERC20 tokens, two token portals, and the uniswap portal: deploy\_l1 ``` console.log("Deploying L1 contracts...\n"); // Deploy two ERC20 tokens: WETH (input) and DAI (output) const { address: wethAddress } = await deployL1Contract( l1Client, ExampleERC20.abi, ExampleERC20.bytecode.object as `0x${string}`, ["Wrapped Ether", "WETH"], ); const { address: daiAddress } = await deployL1Contract( l1Client, ExampleERC20.abi, ExampleERC20.bytecode.object as `0x${string}`, ["Dai Stablecoin", "DAI"], ); // Deploy two token portals (one per token) const { address: wethPortalAddress } = await deployL1Contract( l1Client, ExampleTokenPortal.abi, ExampleTokenPortal.bytecode.object as `0x${string}`, ); const { address: daiPortalAddress } = await deployL1Contract( l1Client, ExampleTokenPortal.abi, ExampleTokenPortal.bytecode.object as `0x${string}`, ); // Deploy the uniswap portal const { address: uniswapPortalAddress } = await deployL1Contract( l1Client, ExampleUniswapPortal.abi, ExampleUniswapPortal.bytecode.object as `0x${string}`, ); console.log(`WETH: ${wethAddress}`); console.log(`DAI: ${daiAddress}`); console.log(`WETH Portal: ${wethPortalAddress}`); console.log(`DAI Portal: ${daiPortalAddress}`); console.log(`Uniswap Portal: ${uniswapPortalAddress}\n`); ``` > [Source code: docs/examples/ts/example\_swap/index.ts#L50-L93](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/example_swap/index.ts#L50-L93) ### Deploy L2 Contracts[​](#deploy-l2-contracts "Direct link to Deploy L2 Contracts") Deploy L2 tokens (using `TokenContract` from `@aztec/noir-contracts.js`), bridges, and the uniswap contract: deploy\_l2 ``` console.log("Deploying L2 contracts...\n"); // Deploy L2 tokens (using the standard TokenContract from @aztec/noir-contracts.js) const { contract: l2Weth } = await TokenContract.deploy( wallet, account.address, "Wrapped Ether", "WETH", 18, ).send({ from: account.address }); const { contract: l2Dai } = await TokenContract.deploy( wallet, account.address, "Dai Stablecoin", "DAI", 18, ).send({ from: account.address }); // Deploy L2 token bridges const { contract: l2WethBridge } = await TokenBridgeContract.deploy( wallet, l2Weth.address, wethPortalAddress, ).send({ from: account.address }); const { contract: l2DaiBridge } = await TokenBridgeContract.deploy( wallet, l2Dai.address, daiPortalAddress, ).send({ from: account.address }); // Deploy L2 uniswap contract const { contract: l2Uniswap } = await ExampleUniswapContract.deploy( wallet, EthAddress.fromString(uniswapPortalAddress.toString()), ).send({ from: account.address }); console.log(`L2 WETH: ${l2Weth.address}`); console.log(`L2 DAI: ${l2Dai.address}`); console.log(`L2 WETH Bridge: ${l2WethBridge.address}`); console.log(`L2 DAI Bridge: ${l2DaiBridge.address}`); console.log(`L2 Uniswap: ${l2Uniswap.address}\n`); ``` > [Source code: docs/examples/ts/example\_swap/index.ts#L95-L139](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/example_swap/index.ts#L95-L139) ### Initialize and Fund[​](#initialize-and-fund "Direct link to Initialize and Fund") Initialize all portals and mint tokens: initialize ``` console.log("Initializing contracts...\n"); // Make bridges minters on their respective tokens await l2Weth.methods .set_minter(l2WethBridge.address, true) .send({ from: account.address }); await l2Dai.methods .set_minter(l2DaiBridge.address, true) .send({ from: account.address }); // Initialize L1 portals with registry, underlying token, and L2 bridge addresses // @ts-expect-error - viem type inference doesn't work with JSON-imported ABIs const initWethPortal = await l1Client.writeContract({ address: wethPortalAddress.toString() as `0x${string}`, abi: ExampleTokenPortal.abi, functionName: "initialize", args: [ registryAddress, wethAddress.toString(), l2WethBridge.address.toString(), ], }); await l1Client.waitForTransactionReceipt({ hash: initWethPortal }); // @ts-expect-error - viem type inference doesn't work with JSON-imported ABIs const initDaiPortal = await l1Client.writeContract({ address: daiPortalAddress.toString() as `0x${string}`, abi: ExampleTokenPortal.abi, functionName: "initialize", args: [ registryAddress, daiAddress.toString(), l2DaiBridge.address.toString(), ], }); await l1Client.waitForTransactionReceipt({ hash: initDaiPortal }); // Initialize uniswap portal // @ts-expect-error - viem type inference doesn't work with JSON-imported ABIs const initUniswapPortal = await l1Client.writeContract({ address: uniswapPortalAddress.toString() as `0x${string}`, abi: ExampleUniswapPortal.abi, functionName: "initialize", args: [registryAddress, l2Uniswap.address.toString()], }); await l1Client.waitForTransactionReceipt({ hash: initUniswapPortal }); console.log("All contracts initialized\n"); ``` > [Source code: docs/examples/ts/example\_swap/index.ts#L141-L190](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/example_swap/index.ts#L141-L190) Fund the user with input tokens and pre-fund the uniswap portal with output tokens: fund ``` console.log("Funding accounts...\n"); const SWAP_AMOUNT = 100n * 10n ** 18n; // 100 tokens // Mint WETH on L1 for the user // @ts-expect-error - viem type inference doesn't work with JSON-imported ABIs const mintWethHash = await l1Client.writeContract({ address: wethAddress.toString() as `0x${string}`, abi: ExampleERC20.abi, functionName: "mint", args: [ownerEthAddress, SWAP_AMOUNT], }); await l1Client.waitForTransactionReceipt({ hash: mintWethHash }); // Pre-fund the uniswap portal with DAI (for the mock 1:1 swap) // @ts-expect-error - viem type inference doesn't work with JSON-imported ABIs const mintDaiHash = await l1Client.writeContract({ address: daiAddress.toString() as `0x${string}`, abi: ExampleERC20.abi, functionName: "mint", args: [uniswapPortalAddress.toString(), SWAP_AMOUNT * 2n], }); await l1Client.waitForTransactionReceipt({ hash: mintDaiHash }); console.log(`Minted ${SWAP_AMOUNT} WETH to user`); console.log(`Pre-funded uniswap portal with ${SWAP_AMOUNT * 2n} DAI\n`); ``` > [Source code: docs/examples/ts/example\_swap/index.ts#L192-L219](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/example_swap/index.ts#L192-L219) ### Deposit to L2[​](#deposit-to-l2 "Direct link to Deposit to L2") Bridge WETH from L1 to L2: deposit\_to\_l2 ``` console.log("Depositing WETH to Aztec (L1 -> L2)...\n"); const depositSecret = Fr.random(); const depositSecretHash = await computeSecretHash(depositSecret); // Approve WETH portal to take tokens // @ts-expect-error - viem type inference doesn't work with JSON-imported ABIs const approveHash = await l1Client.writeContract({ address: wethAddress.toString() as `0x${string}`, abi: ExampleERC20.abi, functionName: "approve", args: [wethPortalAddress.toString(), SWAP_AMOUNT], }); await l1Client.waitForTransactionReceipt({ hash: approveHash }); // Deposit to Aztec publicly // @ts-expect-error - viem type inference doesn't work with JSON-imported ABIs const depositHash = await l1Client.writeContract({ address: wethPortalAddress.toString() as `0x${string}`, abi: ExampleTokenPortal.abi, functionName: "depositToAztecPublic", args: [ account.address.toString(), SWAP_AMOUNT, pad(depositSecretHash.toString() as `0x${string}`, { dir: "left", size: 32, }), ], }); const depositReceipt = await l1Client.waitForTransactionReceipt({ hash: depositHash, }); // Extract message leaf index from Inbox event const INBOX_ABI = [ { type: "event", name: "MessageSent", inputs: [ { name: "checkpointNumber", type: "uint256", indexed: true }, { name: "index", type: "uint256", indexed: false }, { name: "hash", type: "bytes32", indexed: true }, { name: "rollingHash", type: "bytes16", indexed: false }, ], }, ] as const; const messageSentLogs = depositReceipt.logs .filter((log) => log.address.toLowerCase() === inboxAddress.toLowerCase()) .map((log: any) => { try { const decoded = decodeEventLog({ abi: INBOX_ABI, data: log.data, topics: log.topics, }); return { log, decoded }; } catch { return null; } }) .filter( (item): item is { log: any; decoded: any } => item !== null && (item.decoded as any).eventName === "MessageSent", ); if (messageSentLogs.length === 0) { throw new Error("No MessageSent events found in deposit transaction"); } const depositLeafIndex = new Fr(messageSentLogs[0].decoded.args.index); console.log(`Deposit message leaf index: ${depositLeafIndex}\n`); ``` > [Source code: docs/examples/ts/example\_swap/index.ts#L221-L294](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/example_swap/index.ts#L221-L294) Why use a secret hash? When depositing from L1 to L2, we use a secret/secret-hash pattern: generate a random secret on the client, send only the hash to L1 (in the deposit transaction), then later reveal the secret on L2 to claim the tokens. This prevents **front-running attacks**: a malicious sequencer (the node that orders and processes L2 transactions) cannot observe the L1 deposit and claim the tokens themselves because they don't know the secret. Only someone who knows the preimage can claim. Before claiming, we need to mine 2 L2 blocks. L1-to-L2 messages are not available in the same block they are sent -- the rollup must first include them in an L2 block, and then one more block must pass before the message can be consumed. We use a helper that deploys throwaway contracts to force these blocks: mine\_blocks ``` // Utility: mine 2 blocks (required before L1->L2 messages can be consumed) async function mine2Blocks( wallet: EmbeddedWallet, accountAddress: AztecAddress, ) { await TokenContract.deploy(wallet, accountAddress, "T", "T", 18).send({ from: accountAddress, }); await TokenContract.deploy(wallet, accountAddress, "T", "T", 18).send({ from: accountAddress, }); } ``` > [Source code: docs/examples/ts/example\_swap/index.ts#L296-L309](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/example_swap/index.ts#L296-L309) Claim the deposited tokens on L2: claim\_on\_l2 ``` console.log("Claiming WETH on L2...\n"); await mine2Blocks(wallet, account.address); await l2WethBridge.methods .claim_public(account.address, SWAP_AMOUNT, depositSecret, depositLeafIndex) .send({ from: account.address }); const { result: wethBalanceBefore } = await l2Weth.methods .balance_of_public(account.address) .simulate({ from: account.address }); console.log(`L2 WETH balance after claim: ${wethBalanceBefore}\n`); if (wethBalanceBefore !== SWAP_AMOUNT) { throw new Error( `Expected WETH balance ${SWAP_AMOUNT}, got ${wethBalanceBefore}`, ); } console.log("✓ WETH claimed successfully on L2\n"); ``` > [Source code: docs/examples/ts/example\_swap/index.ts#L311-L330](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/example_swap/index.ts#L311-L330) ### Execute the Swap[​](#execute-the-swap "Direct link to Execute the Swap") Initiate the swap on L2: public\_swap ``` console.log("=== PUBLIC SWAP FLOW ===\n"); console.log("Initiating public swap on L2 (WETH -> DAI)...\n"); // Force L2 block production so the claim message is included in a block before the swap await mine2Blocks(wallet, account.address); const swapSecret = Fr.random(); const swapSecretHash = await computeSecretHash(swapSecret); // Create authwit for the uniswap contract to transfer WETH on our behalf const transferAction = l2Weth.methods.transfer_in_public( account.address, l2Uniswap.address, SWAP_AMOUNT, 0xdeadbeefn, ); const authwit = await SetPublicAuthwitContractInteraction.create( wallet, account.address, { caller: l2Uniswap.address, action: transferAction }, true, ); await authwit.send(); // Call swap_public on the L2 uniswap contract const { receipt: swapReceipt } = await l2Uniswap.methods .swap_public( account.address, l2WethBridge.address, SWAP_AMOUNT, l2DaiBridge.address, 3000n, // fee tier 0n, // minimum output account.address, // recipient swapSecretHash, ) .send({ from: account.address }); console.log(`Swap tx sent (block: ${swapReceipt.blockNumber})\n`); // Verify WETH was spent (balance should be 0 after swap) const { result: wethAfterSwap } = await l2Weth.methods .balance_of_public(account.address) .simulate({ from: account.address }); if (wethAfterSwap !== 0n) { throw new Error(`Expected WETH balance 0 after swap, got ${wethAfterSwap}`); } console.log("✓ WETH transferred to bridge for swap\n"); ``` > [Source code: docs/examples/ts/example\_swap/index.ts#L332-L381](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/example_swap/index.ts#L332-L381) ### Waiting for Block Proofs[​](#waiting-for-block-proofs "Direct link to Waiting for Block Proofs") L2→L1 messages can only be consumed on L1 after the L2 block containing them has been **proven**. Aztec batches blocks into [**epochs**](/developers/testnet/docs/aztec-nr/framework-description/ethereum_aztec_messaging.md) and generates ZK proofs for each epoch. The proof confirms that the L2 state transition (including our swap messages) actually happened according to the protocol rules. Until the proof is submitted to L1, the messages exist but cannot be trusted. wait\_for\_proof ``` const isLocalNetwork = nodeUrl.includes("localhost") || nodeUrl.includes("127.0.0.1") || nodeUrl.includes("local-network"); const nodeDebug = isLocalNetwork ? createAztecNodeDebugClient(nodeUrl) : undefined; console.log("Waiting for block to be proven...\n"); let provenBlockNumber = await node.getBlockNumber("proven"); while (provenBlockNumber < swapReceipt.blockNumber!) { console.log( ` Waiting... (proven: ${provenBlockNumber}, needed: ${swapReceipt.blockNumber})`, ); if (nodeDebug) { await nodeDebug.mineBlock(); } await new Promise((resolve) => setTimeout(resolve, 10000)); provenBlockNumber = await node.getBlockNumber("proven"); } console.log("Block proven!\n"); ``` > [Source code: docs/examples/ts/example\_swap/index.ts#L383-L407](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/example_swap/index.ts#L383-L407) The [outbox](/developers/testnet/docs/aztec-nr/framework-description/ethereum_aztec_messaging.md) stores L2→L1 messages in a Merkle tree. To consume a message, you must provide the **epoch** (which proof batch contains the message), the **leaf index** (position in the message tree), and the **sibling path** (Merkle proof showing the message is in the tree). These parameters are computed offchain by observing L2 blocks. First, read the rollup version from the portal and compute the content hash and message leaf for the token bridge exit message: consume\_l1\_messages\_setup ``` console.log("Consuming L2->L1 messages on L1...\n"); // The swap generates 2 L2->L1 messages: // 1. Token bridge exit (withdraw WETH to uniswap portal) // 2. Uniswap swap intent // @ts-expect-error - viem type inference doesn't work with JSON-imported ABIs const portalRollupVersion = (await l1Client.readContract({ address: wethPortalAddress.toString() as `0x${string}`, abi: ExampleTokenPortal.abi, functionName: "rollupVersion", })) as bigint; // Compute message 1: token bridge exit // Encode using the same approach as Solidity's abi.encodeWithSignature("withdraw(address,uint256,address)", ...) const withdrawContentEncoded = encodeFunctionData({ abi: [ { name: "withdraw", type: "function", inputs: [ { name: "", type: "address" }, { name: "", type: "uint256" }, { name: "", type: "address" }, ], outputs: [], }, ], args: [ uniswapPortalAddress.toString() as `0x${string}`, SWAP_AMOUNT, uniswapPortalAddress.toString() as `0x${string}`, ], }); const withdrawContentHash = sha256ToField([ Buffer.from(withdrawContentEncoded.slice(2), "hex"), ]); // Message 1: Token bridge exit message const exitMsgLeaf = computeL2ToL1MessageHash({ l2Sender: l2WethBridge.address, l1Recipient: wethPortalAddress, content: withdrawContentHash, rollupVersion: new Fr(portalRollupVersion), chainId: new Fr(foundry.id), }); ``` > [Source code: docs/examples/ts/example\_swap/index.ts#L409-L456](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/example_swap/index.ts#L409-L456) Next, compute Merkle membership witnesses for both L2→L1 messages -- the sibling path (proof of inclusion) for each. We also compute the swap intent message leaf using the same encoding as the Solidity portal: consume\_l1\_messages\_witnesses ``` // The node picks the smallest partial-proof root that covers each tx's checkpoint. const waitForL2ToL1MembershipWitness = async ( messageName: string, messageLeaf: Fr, ) => { const maxAttempts = 30; for (let attempt = 1; attempt <= maxAttempts; attempt++) { const witness = await node.getL2ToL1MembershipWitness( swapReceipt.txHash, messageLeaf, ); if (witness) { return witness; } console.log( ` Waiting for ${messageName} L2->L1 witness (${attempt}/${maxAttempts})...`, ); await new Promise((resolve) => setTimeout(resolve, 10000)); } throw new Error(`Timed out waiting for ${messageName} L2->L1 witness`); }; const exitWitness = await waitForL2ToL1MembershipWitness( "token bridge exit", exitMsgLeaf, ); const exitSiblingPath = exitWitness.siblingPath .toBufferArray() .map((buf: Buffer) => `0x${buf.toString("hex")}` as `0x${string}`); // Message 2: Uniswap swap intent message // Compute using the same encoding as ExampleUniswapPortal.sol const swapContentEncoded = encodeFunctionData({ abi: [ { name: "swap_public", type: "function", inputs: [ { name: "", type: "address" }, { name: "", type: "uint256" }, { name: "", type: "uint24" }, { name: "", type: "address" }, { name: "", type: "uint256" }, { name: "", type: "bytes32" }, { name: "", type: "bytes32" }, ], outputs: [], }, ], args: [ wethPortalAddress.toString() as `0x${string}`, SWAP_AMOUNT, 3000, daiPortalAddress.toString() as `0x${string}`, 0n, account.address.toString() as `0x${string}`, pad(swapSecretHash.toString() as `0x${string}`, { dir: "left", size: 32, }), ], }); const swapContentHash = sha256ToField([ Buffer.from(swapContentEncoded.slice(2), "hex"), ]); const swapMsgLeaf = computeL2ToL1MessageHash({ l2Sender: l2Uniswap.address, l1Recipient: uniswapPortalAddress, content: swapContentHash, rollupVersion: new Fr(portalRollupVersion), chainId: new Fr(foundry.id), }); const swapWitness = await waitForL2ToL1MembershipWitness( "swap intent", swapMsgLeaf, ); const swapSiblingPath = swapWitness.siblingPath .toBufferArray() .map((buf: Buffer) => `0x${buf.toString("hex")}` as `0x${string}`); ``` > [Source code: docs/examples/ts/example\_swap/index.ts#L458-L543](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/example_swap/index.ts#L458-L543) Next, call `swapPublic` on the L1 uniswap portal, passing both message proofs. The portal verifies both messages against the outbox, performs the mock swap, and deposits the output tokens back to L2: consume\_l1\_messages\_execute ``` // Execute the swap on L1 (consumes both messages) // @ts-expect-error - viem type inference doesn't work with JSON-imported ABIs const l1SwapHash = await l1Client.writeContract({ address: uniswapPortalAddress.toString() as `0x${string}`, abi: ExampleUniswapPortal.abi, functionName: "swapPublic", args: [ wethPortalAddress.toString(), SWAP_AMOUNT, 3000, daiPortalAddress.toString(), 0n, account.address.toString(), pad(swapSecretHash.toString() as `0x${string}`, { dir: "left", size: 32, }), [BigInt(exitWitness.epochNumber), BigInt(swapWitness.epochNumber)], [ BigInt(exitWitness.numCheckpointsInEpoch), BigInt(swapWitness.numCheckpointsInEpoch), ], [BigInt(exitWitness.leafIndex), BigInt(swapWitness.leafIndex)], [exitSiblingPath, swapSiblingPath], ], }); const l1SwapReceipt = await l1Client.waitForTransactionReceipt({ hash: l1SwapHash, }); console.log(`L1 swap executed! Tx: ${l1SwapHash}\n`); ``` > [Source code: docs/examples/ts/example\_swap/index.ts#L545-L576](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/example_swap/index.ts#L545-L576) Finally, claim the output DAI on L2: claim\_output ``` console.log("Claiming DAI output on L2...\n"); // Extract the deposit message leaf index from the L1 swap receipt const daiDepositLogs = l1SwapReceipt.logs .filter((log) => log.address.toLowerCase() === inboxAddress.toLowerCase()) .map((log: any) => { try { const decoded = decodeEventLog({ abi: INBOX_ABI, data: log.data, topics: log.topics, }); return { log, decoded }; } catch { return null; } }) .filter( (item): item is { log: any; decoded: any } => item !== null && (item.decoded as any).eventName === "MessageSent", ); if (daiDepositLogs.length === 0) { throw new Error("No MessageSent events found in L1 swap transaction"); } const daiDepositLeafIndex = new Fr(daiDepositLogs[0].decoded.args.index); // Mine blocks and claim await mine2Blocks(wallet, account.address); await l2DaiBridge.methods .claim_public(account.address, SWAP_AMOUNT, swapSecret, daiDepositLeafIndex) .send({ from: account.address }); const { result: daiBalance } = await l2Dai.methods .balance_of_public(account.address) .simulate({ from: account.address }); const { result: wethBalanceAfter } = await l2Weth.methods .balance_of_public(account.address) .simulate({ from: account.address }); console.log(`L2 WETH balance: ${wethBalanceAfter}`); console.log(`L2 DAI balance: ${daiBalance}`); if (wethBalanceAfter !== 0n) { throw new Error(`Expected final WETH balance 0, got ${wethBalanceAfter}`); } if (daiBalance !== SWAP_AMOUNT) { throw new Error(`Expected DAI balance ${SWAP_AMOUNT}, got ${daiBalance}`); } console.log("\n✓ All checks passed — public swap complete!\n"); ``` > [Source code: docs/examples/ts/example\_swap/index.ts#L578-L631](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/ts/example_swap/index.ts#L578-L631) ### Run It[​](#run-it "Direct link to Run It") Start a local Aztec network in one terminal, then execute the script in another. Make sure you are in the `examples/ts/example_swap` directory: ``` # Terminal 1: Start a local network aztec start --local-network # Terminal 2: From examples/ts/example_swap, run the swap script npx tsx index.ts ``` You should see console output tracing each step: deploying contracts, depositing to L2, initiating the swap, waiting for the proof, consuming messages on L1, and claiming the output DAI on L2. ## Public vs Private Comparison[​](#public-vs-private-comparison "Direct link to Public vs Private Comparison") | Aspect | Public Swap | Private Swap | | ------------------ | ------------------------------------ | ------------------------------------- | | **L2 function** | `swap_public()` | `swap_private()` | | **Token transfer** | `transfer_in_public` (public→public) | `transfer_to_public` (private→public) | | **Bridge call** | `call_self` (immediate) | `enqueue_self` (deferred) | | **L1 deposit** | `depositToAztecPublic` | `depositToAztecPrivate` | | **L2 claim** | `claim_public` | `claim_private` | | **Visibility** | Swap amount and recipient visible | Swap amount visible, recipient hidden | The private flow hides *who* is swapping, but the amounts are visible on L1 because the token bridge exit is a public operation. The output deposit can be claimed privately, so the final recipient is hidden. ## What You Built[​](#what-you-built "Direct link to What You Built") A complete cross-chain token swap system with: 1. **L1 Contracts** (Solidity) * `ExampleERC20`: Minimal ERC20 tokens for testing * `ExampleTokenPortal`: Handles L1↔L2 token deposits and withdrawals * `ExampleUniswapPortal`: Orchestrates the swap by consuming two L2→L1 messages 2. **L2 Contract** (Noir) * `ExampleUniswap`: User-facing contract that initiates the swap, exits tokens to L1, and sends the swap intent message 3. **Message Flow** * User calls `swap_public` on L2 * Two L2→L1 messages are created (bridge exit + swap intent) * L1 portal consumes both messages, swaps, and deposits output back to L2 * User claims output tokens on L2 ## Next Steps[​](#next-steps "Direct link to Next Steps") * Extend with a real Uniswap V3 integration instead of the mock swap * Add slippage protection with meaningful `minimum_output_amount` values * Implement the private swap flow end-to-end in the TypeScript script * Explore [cross-chain messaging](/developers/testnet/docs/aztec-nr/framework-description/ethereum_aztec_messaging.md) in depth Learn More * [Token bridge tutorial](/developers/testnet/docs/tutorials/js_tutorials/token_bridge.md) - NFT bridge example * [Cross-chain messaging reference](/developers/testnet/docs/aztec-nr/framework-description/ethereum_aztec_messaging.md) --- # Building a Wallet Extension for Aztec In this tutorial, you'll build a fully functional Chrome extension wallet that can: * Create and store encrypted accounts * Deploy account contracts using SponsoredFPC (no fee tokens needed) * Connect to dApps using the Aztec wallet SDK protocol * Approve and sign transactions with a popup UI This is a **standalone tutorial** that complements the [Webapp Tutorial](/developers/testnet/docs/tutorials/js_tutorials/webapp.md). While the webapp tutorial uses an embedded wallet for simplicity, this tutorial shows how to build a real browser extension wallet like MetaMask or Rabby. ## What You'll Learn[​](#what-youll-learn "Direct link to What You'll Learn") 1. **Extension Architecture** - Service workers, offscreen documents, and message passing 2. **Wallet SDK Protocol** - Discovery, ECDH key exchange, and secure messaging 3. **PXE Integration** - Running a Private eXecution Environment in a browser extension 4. **Account Management** - Key derivation, encrypted storage, and Schnorr signatures 5. **Transaction Handling** - Signing, proofs, and SponsoredFPC fee payment 6. **Approval UIs** - React popups for connection and transaction approval ## Architecture Overview[​](#architecture-overview "Direct link to Architecture Overview") ``` ┌─────────────────────────────────────────────────────────────┐ │ Content Script │ │ - Injected into every page │ │ - Relays messages between page and background │ └──────────────────────┬──────────────────────────────────────┘ │ chrome.runtime messages ┌──────────────────────▼──────────────────────────────────────┐ │ Service Worker (Background) │ │ - Handles wallet SDK protocol │ │ - Routes wallet method calls to offscreen document │ │ - Manages popup for user approvals │ └──────────────────────┬──────────────────────────────────────┘ │ chrome.runtime messages ┌──────────────────────▼──────────────────────────────────────┐ │ Offscreen Document │ │ - Runs PXE instance (long-lived, supports WASM) │ │ - Implements wallet methods with SponsoredFPC │ │ - Manages account creation and signing │ └──────────────────────────────────────────────────────────────┘ ``` ### Why This Architecture?[​](#why-this-architecture "Direct link to Why This Architecture?") **Service workers** in Manifest V3 have a 5-minute inactivity timeout and limited WASM support. Since the PXE needs persistent state and long-running proof generation, the extension uses an **offscreen document** that: * Runs longer than service workers * Supports IndexedDB for PXE storage * Handles WASM-based proof generation * Maintains state across requests The **service worker** handles the lightweight protocol layer (discovery, key exchange) and routes heavier operations to the offscreen document. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before starting, you should be familiar with: * TypeScript and React basics * Chrome extension development (Manifest V3) * The Aztec concepts (accounts, transactions, PXE) You'll also need: * Node.js 22+ * Chrome browser * A local Aztec network running (`aztec start --local-network`) * The [webapp-tutorial project set up](/developers/testnet/docs/tutorials/js_tutorials/webapp.md#clone-the-example) (for testing) ## Project Structure[​](#project-structure "Direct link to Project Structure") We'll build on the existing `test-extension/` in the webapp tutorial: ``` test-extension/ ├── manifest.json # Chrome extension manifest ├── popup/ │ ├── popup.html # Popup UI HTML │ └── popup.css # Popup styles ├── src/ │ ├── background.ts # Service worker - protocol + routing │ ├── content-script.ts # Page <-> background relay │ ├── config.ts # Constants and configuration │ ├── account-utils.ts # Shared account instantiation logic │ ├── aztec-imports.ts # Lazy import caching for Aztec modules │ ├── utils.ts # Chrome runtime helpers and utilities │ ├── offscreen/ │ │ ├── offscreen.html # Offscreen document HTML │ │ └── offscreen.ts # PXE host + OffscreenWallet (BaseWallet subclass) │ ├── popup/ │ │ └── popup.tsx # React popup component │ └── wallet/ │ ├── wallet-impl.ts # ExtensionWalletManager - secret generation and encrypted storage │ └── storage.ts # Encrypted key storage with CryptoKey pattern └── dist/ # Compiled output ``` ## Tutorial Sections[​](#tutorial-sections "Direct link to Tutorial Sections") 1. [**Architecture**](/developers/testnet/docs/tutorials/js_tutorials/wallet-extension/architecture.md) - Understanding service worker limitations and offscreen documents 2. [**Wallet Protocol**](/developers/testnet/docs/tutorials/js_tutorials/wallet-extension/wallet-protocol.md) - Implementing discovery, key exchange, and secure messaging 3. [**PXE Integration**](/developers/testnet/docs/tutorials/js_tutorials/wallet-extension/pxe-integration.md) - Running PXE in an extension and extending BaseWallet 4. [**Account Management**](/developers/testnet/docs/tutorials/js_tutorials/wallet-extension/accounts.md) - Key derivation, encrypted storage, and SchnorrAccountContract 5. [**Transaction Handling**](/developers/testnet/docs/tutorials/js_tutorials/wallet-extension/transactions.md) - The sendTx flow, proofs, and SponsoredFPC 6. [**Approval UI**](/developers/testnet/docs/tutorials/js_tutorials/wallet-extension/approval-ui.md) - Building React popups for user confirmations 7. [**Testing**](/developers/testnet/docs/tutorials/js_tutorials/wallet-extension/testing.md) - Loading the extension and testing with the Pod Racing dApp ## Quick Start[​](#quick-start "Direct link to Quick Start") If you want to try the completed wallet before reading the tutorial: ``` git clone https://github.com/AztecProtocol/aztec-packages.git cd aztec-packages git checkout v5.0.0-rc.2 cd docs/examples/webapp-tutorial ./setup.sh node esbuild.extension.mjs ``` Then load it in Chrome: 1. Make sure your local Aztec network is running (`aztec start --local-network`) 2. Open `chrome://extensions/` 3. Enable **Developer mode** (toggle in top-right corner) 4. Click **Load unpacked** and select the `test-extension` folder 5. Start the dApp with `yarn dev` and open `http://localhost:5173` 6. Select "Browser Wallet" to connect ## Key Differences from Embedded Wallet[​](#key-differences-from-embedded-wallet "Direct link to Key Differences from Embedded Wallet") | Feature | Embedded Wallet | Extension Wallet | | ------------ | --------------------------------- | ------------------------------ | | **Location** | Runs in dApp's page | Runs in extension context | | **Storage** | localStorage (accessible to dApp) | chrome.storage (isolated) | | **Security** | Keys visible to dApp | Keys encrypted, never exposed | | **UX** | No approval needed | Popup for approvals | | **Setup** | Just import library | User installs extension | | **Network** | Local development | Local network (or any network) | ## Next Steps[​](#next-steps "Direct link to Next Steps") Start with [Architecture](/developers/testnet/docs/tutorials/js_tutorials/wallet-extension/architecture.md) to understand why this multi-component design is needed, then work through each section to build your wallet. Related Resources * [Webapp Tutorial](/developers/testnet/docs/tutorials/js_tutorials/webapp.md) - Build a dApp that connects to this wallet * [BaseWallet Source](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/yarn-project/wallet-sdk/src/base-wallet/base_wallet.ts) - The class you extend --- # Account Management Aztec accounts are smart contracts with associated keys. This section covers how the wallet creates, stores, and manages accounts securely. ## Aztec Account Model[​](#aztec-account-model "Direct link to Aztec Account Model") Unlike Ethereum's EOA model, Aztec accounts are: 1. **Smart contracts** - Each account is a deployed contract 2. **Abstracted** - Custom authentication logic (Schnorr, ECDSA, multisig, etc.) 3. **Key-based** - Derived from a secret key using standardized derivation An Aztec account has: * **Secret key** (`Fr`) - The root secret, never exposed * **Signing key** (`GrumpkinScalar`) - Derived from secret, used for signatures * **Public keys** - For viewing, tagging, and nullifying * **Contract address** - Computed from keys and contract salt ## Key Derivation[​](#key-derivation "Direct link to Key Derivation") The wallet uses the standard derivation from `@aztec/stdlib/keys`: ``` import { deriveSigningKey } from '@aztec/stdlib/keys'; // Generate a random secret const secret = Fr.random(); // Derive the signing key const signingKey = deriveSigningKey(secret); ``` The derivation uses SHA-512 with domain separators to derive different keys: ``` // From stdlib/src/keys/derivation.ts export function deriveSigningKey(secretKey: Fr): GrumpkinScalar { return sha512ToGrumpkinScalar([secretKey, GeneratorIndex.IVSK_M]); } export function deriveMasterNullifierHidingKey(secretKey: Fr): GrumpkinScalar { return sha512ToGrumpkinScalar([secretKey, GeneratorIndex.NHK_M]); } ``` ## SchnorrAccountContract[​](#schnorraccountcontract "Direct link to SchnorrAccountContract") The wallet uses `SchnorrAccountContract` for authentication: ``` import { SchnorrAccountContract } from '@aztec/accounts/schnorr/lazy'; const accountContract = new SchnorrAccountContract(signingKey); ``` This account contract: * Verifies Schnorr signatures on transactions * Is battle-tested and widely used * Supports standard Aztec authorization patterns The "lazy" import defers loading the Noir artifact until needed. ## Creating an Account[​](#creating-an-account "Direct link to Creating an Account") Account creation is handled by the `ExtensionWalletManager` utility class: create-new-account ``` export async function createAccount( masterKey: CryptoKey, alias: string = '' ): Promise<{ address: string; secret: string; salt: string }> { log.debug('[wallet-manager] Creating account...'); const { secret, salt } = generateSecret(); const address = await computeAddress(secret, salt); await storeAccount(address, secret, salt, masterKey, alias); // Auto-set as active if this is the first account const currentActive = await getActiveAccount(); if (!currentActive) { await setActiveAccount(address); log.debug('[wallet-manager] Set as active account (first account)'); } log.debug('[wallet-manager] Account created:', address); return { address, secret, salt }; } ``` > [Source code: docs/examples/webapp-tutorial/test-extension/src/wallet/wallet-impl.ts#L57-L78](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/wallet/wallet-impl.ts#L57-L78) Breaking this down: 1. **Generate secrets** - Random `Fr` for secret and salt via `Fr.random()` 2. **Compute address** - Uses the shared `instantiateAccount()` utility to derive keys and compute the contract address deterministically 3. **Encrypt and store** - Secret is encrypted with the master `CryptoKey` before storage 4. **Auto-activate** - The first account is automatically set as active Note that PXE (Private eXecution Environment) registration happens separately - either when the user unlocks the wallet or when they deploy the account. This separation means account creation is fast (no PXE or network interaction required). ## Encrypted Key Storage[​](#encrypted-key-storage "Direct link to Encrypted Key Storage") Keys are encrypted using PBKDF2 + AES-GCM with a non-extractable `CryptoKey`: derive-master-key ``` /** * Derives a non-extractable AES-GCM CryptoKey from a password and salt using PBKDF2. * * The key is non-extractable: once created, the raw key material cannot be read * from JavaScript. This means even if an attacker has a reference to the CryptoKey * object, they cannot extract the underlying bytes. */ export async function deriveMasterKey( password: string, salt: Uint8Array ): Promise { const encoder = new TextEncoder(); const passwordKey = await crypto.subtle.importKey( 'raw', encoder.encode(password), 'PBKDF2', false, ['deriveKey'] ); return crypto.subtle.deriveKey( { name: 'PBKDF2', salt, iterations: PBKDF2_ITERATIONS, hash: 'SHA-256', }, passwordKey, { name: 'AES-GCM', length: 256 }, false, // non-extractable ['encrypt', 'decrypt'] ); } ``` > [Source code: docs/examples/webapp-tutorial/test-extension/src/wallet/storage.ts#L58-L92](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/wallet/storage.ts#L58-L92) Key derivation parameters: * **600,000 iterations** - OWASP 2023 recommendation for SHA-256, slows brute-force attacks * **SHA-256** - Hash function for PBKDF2 * **AES-256-GCM** - Authenticated encryption * **Non-extractable** - The derived `CryptoKey` cannot be read from JavaScript, even if an attacker has a reference to the object Encryption and decryption use the derived `CryptoKey` directly - the raw password string is never stored or passed around: encrypt-decrypt ``` /** * Encrypts a secret using a CryptoKey. * Each call generates a fresh random IV for AES-GCM. */ export async function encryptWithKey( secret: string, key: CryptoKey ): Promise<{ encrypted: string; iv: string }> { const iv = crypto.getRandomValues(new Uint8Array(12)); const encoder = new TextEncoder(); const ciphertext = await crypto.subtle.encrypt( { name: 'AES-GCM', iv }, key, encoder.encode(secret) ); return { encrypted: bytesToBase64(new Uint8Array(ciphertext)), iv: bytesToBase64(iv), }; } /** * Decrypts a secret using a CryptoKey. */ export async function decryptWithKey( encrypted: string, iv: string, key: CryptoKey ): Promise { const ivBytes = base64ToBytes(iv); const ciphertextBytes = base64ToBytes(encrypted); const decrypted = await crypto.subtle.decrypt( { name: 'AES-GCM', iv: ivBytes }, key, ciphertextBytes ); return new TextDecoder().decode(decrypted); } ``` > [Source code: docs/examples/webapp-tutorial/test-extension/src/wallet/storage.ts#L94-L137](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/wallet/storage.ts#L94-L137) Each account gets a unique random IV for AES-GCM. The stored data includes: * `encrypted` - The encrypted secret (base64) * `iv` - AES-GCM IV, unique per account (base64) ## Storage Operations[​](#storage-operations "Direct link to Storage Operations") The offscreen document cannot access `chrome.storage` directly, so all storage operations are proxied through the background script via Chrome messaging: account-operations ``` /** * Saves an account to storage. */ export async function saveAccount(account: StoredAccount): Promise { const accounts = await getStoredAccounts(); const existingIndex = accounts.findIndex((a) => a.address === account.address); if (existingIndex >= 0) { accounts[existingIndex] = account; } else { accounts.push(account); } await storageSet({ [STORAGE_KEYS.ACCOUNTS]: accounts }); } /** * Retrieves all stored accounts. */ export async function getStoredAccounts(): Promise { const result = await storageGet(STORAGE_KEYS.ACCOUNTS); return result || []; } /** * Gets a specific account by address. */ export async function getStoredAccount( address: string ): Promise { const accounts = await getStoredAccounts(); return accounts.find((a) => a.address === address); } /** * Updates an account's deployment status. */ export async function markAccountDeployed(address: string): Promise { const accounts = await getStoredAccounts(); const account = accounts.find((a) => a.address === address); if (account) { account.isDeployed = true; await storageSet({ [STORAGE_KEYS.ACCOUNTS]: accounts }); } } /** * Removes an account from storage. */ export async function removeAccount(address: string): Promise { const accounts = await getStoredAccounts(); const filtered = accounts.filter((a) => a.address !== address); await storageSet({ [STORAGE_KEYS.ACCOUNTS]: filtered }); } /** * Gets the active account address. */ export async function getActiveAccount(): Promise { const result = await storageGet(STORAGE_KEYS.ACTIVE_ACCOUNT); return result || null; } /** * Sets the active account address. */ export async function setActiveAccount(address: string): Promise { await storageSet({ [STORAGE_KEYS.ACTIVE_ACCOUNT]: address }); } ``` > [Source code: docs/examples/webapp-tutorial/test-extension/src/wallet/storage.ts#L263-L333](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/wallet/storage.ts#L263-L333) The stored account structure: ``` interface StoredAccount { address: string; // Aztec address (hex) encryptedSecret: string; // Encrypted Fr (base64) iv: string; // AES-GCM IV (base64) alias: string; // User-friendly name isDeployed: boolean; // Whether contract is deployed contractSalt: string; // Contract deployment salt (hex) } ``` ## Password Management[​](#password-management "Direct link to Password Management") Instead of storing a password hash (vulnerable to rainbow tables), the wallet uses an encrypt-then-verify approach: derive a `CryptoKey` via PBKDF2 with a random salt, encrypt a known plaintext, and verify by attempting to decrypt it: password-management ``` /** * Sets the master password for the first time. (#1) * * Instead of storing an unsalted SHA-256 hash (vulnerable to rainbow tables), * we derive a CryptoKey via PBKDF2 with a random salt, then encrypt a known * plaintext. Verification = re-derive key + try to decrypt. * * Returns the derived master CryptoKey so the caller can cache it immediately. */ export async function setupPassword(password: string): Promise { const salt = crypto.getRandomValues(new Uint8Array(32)); const iv = crypto.getRandomValues(new Uint8Array(12)); const masterKey = await deriveMasterKey(password, salt); const encoder = new TextEncoder(); const ciphertext = await crypto.subtle.encrypt( { name: 'AES-GCM', iv }, masterKey, encoder.encode(VERIFICATION_PLAINTEXT) ); await storageSet({ [STORAGE_KEYS.PASSWORD_DATA]: { salt: bytesToBase64(salt), iv: bytesToBase64(iv), verifier: bytesToBase64(new Uint8Array(ciphertext)), }, }); return masterKey; } /** * Verifies the password and returns the derived master CryptoKey. (#1, #2) * * If the password is correct, returns the non-extractable CryptoKey. * If wrong, returns null (AES-GCM decryption fails with wrong key). * The caller should cache the CryptoKey and discard the password string. */ export async function verifyAndDeriveMasterKey(password: string): Promise { const data = await storageGet(STORAGE_KEYS.PASSWORD_DATA); if (!data) return null; const salt = base64ToBytes(data.salt); const iv = base64ToBytes(data.iv); const verifier = base64ToBytes(data.verifier); const masterKey = await deriveMasterKey(password, salt); try { const decrypted = await crypto.subtle.decrypt( { name: 'AES-GCM', iv }, masterKey, verifier ); const decoded = new TextDecoder().decode(decrypted); if (decoded === VERIFICATION_PLAINTEXT) { return masterKey; } return null; } catch { // AES-GCM decryption throws on wrong key (authentication tag mismatch) return null; } } /** * Checks if a master password has been set. */ export async function hasPassword(): Promise { const data = await storageGet(STORAGE_KEYS.PASSWORD_DATA); return !!data; } ``` > [Source code: docs/examples/webapp-tutorial/test-extension/src/wallet/storage.ts#L187-L261](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/wallet/storage.ts#L187-L261) This provides: * `setupPassword()` - Derives a master key, encrypts a known plaintext for future verification, returns the `CryptoKey` * `verifyAndDeriveMasterKey()` - Re-derives the key, tries to decrypt the known plaintext; returns the `CryptoKey` on success or `null` on failure * `hasPassword()` - Checks if a master password has been set The caller caches the `CryptoKey` in memory and discards the password string immediately. ## Loading Accounts[​](#loading-accounts "Direct link to Loading Accounts") When the user unlocks the wallet, the extension verifies the password, derives the master `CryptoKey`, initializes PXE, and registers all stored accounts: load-accounts ``` /** * Unlocks the wallet: verifies password, caches CryptoKey, initializes PXE, * registers all stored accounts. (#1, #2) * * After unlock: * - cachedMasterKey holds the non-extractable CryptoKey * - The password string is discarded (goes out of scope) * - All accounts are registered with PXE and the BaseWallet */ async function handleUnlockWallet(password: string) { log.debug('[offscreen] Unlocking wallet...'); reportProgress('Verifying password...'); const { verifyAndDeriveMasterKey, hasPassword: checkHasPassword } = await import('../wallet/storage'); if (await checkHasPassword()) { const masterKey = await verifyAndDeriveMasterKey(password); if (!masterKey) { throw new Error('Incorrect password'); } cachedMasterKey = masterKey; // password string goes out of scope — only the CryptoKey survives } else { throw new Error('No password set. Please set up your wallet first.'); } // Initialize PXE reportProgress('Initializing PXE (loading WASM)...'); await ensurePXE(); log.debug('[offscreen] PXE initialized for unlock'); // Register all stored accounts const storedAccounts = await getAccounts(); reportProgress(`Registering ${storedAccounts.length} account(s)...`); log.debug('[offscreen] Registering', storedAccounts.length, 'accounts with PXE'); const failedAccounts: string[] = []; for (const account of storedAccounts) { try { const secretData = await getAccountSecret(account.address, cachedMasterKey); if (secretData) { await registerAccountInWallet(account.address, secretData.secret, secretData.salt); log.debug('[offscreen] Registered account:', account.address); } } catch (err: any) { log.error('[offscreen] Failed to register account:', account.address, err.message); failedAccounts.push(account.address); // Continue with remaining accounts — partial unlock is better than full lockout } } if (failedAccounts.length === storedAccounts.length && storedAccounts.length > 0) { // ALL accounts failed — password is likely wrong cachedMasterKey = null; throw new Error('Failed to unlock: wrong password or corrupted data'); } if (failedAccounts.length > 0) { log.warn('[offscreen] Partial unlock:', failedAccounts.length, 'account(s) failed to register'); } log.debug('[offscreen] Wallet unlocked,', storedAccounts.length - failedAccounts.length, 'of', storedAccounts.length, 'accounts registered'); return { success: true }; } ``` > [Source code: docs/examples/webapp-tutorial/test-extension/src/offscreen/offscreen.ts#L601-L665](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/offscreen/offscreen.ts#L601-L665) Each account: 1. Has its secret decrypted using the master `CryptoKey` 2. Gets its keys derived and contract instantiated via `instantiateAccount()` 3. Is registered with PXE (contract instance, artifact, and secret key) 4. Gets an `AccountManager` created and account registered with the `OffscreenWallet` ## Deploying Accounts[​](#deploying-accounts "Direct link to Deploying Accounts") Accounts must be deployed before they can receive notes or initiate transactions: deploy-account ``` /** * Deploys an account contract onchain using SponsoredFPC for fee payment. * Uses the cached master CryptoKey to decrypt the account secret. (#2, #4) */ async function handleDeployAccount(address: string) { const masterKey = getCachedMasterKey(); log.debug('[offscreen] Deploying account:', address); // 1. Decrypt the account secret reportProgress('Decrypting account secret...'); const secretData = await getAccountSecret(address, masterKey); if (!secretData) { throw new Error(`Account not found: ${address}`); } // 2. Ensure PXE is initialized (needed by the wallet) reportProgress('Connecting to PXE...'); await ensurePXE(); // 3. Register account with PXE and wallet (shared with unlock flow) reportProgress('Registering account contract...'); const { accountManager } = await registerAccountInWallet(address, secretData.secret, secretData.salt); // 4. Register SponsoredFPC contract with PXE (shared helper) reportProgress('Registering fee payment contract...'); const { AztecAddress, SponsoredFeePaymentMethod, SponsoredFPCContract } = await getAztecDeploy(); const sponsoredFPCInstance = await getSponsoredFPCInstance(); const wallet = await getWallet(); await wallet.registerContract(sponsoredFPCInstance, SponsoredFPCContract.artifact); // 5. Deploy with SponsoredFPC fee payment. // PXE log matchers (PXE_STAGE_MATCHERS) provide granular progress updates // (simulating → proving → proof generated → sending → awaiting confirmation). reportProgress('Starting deploy tx...'); const paymentMethod = new SponsoredFeePaymentMethod(sponsoredFPCInstance.address); const deployMethod = await accountManager.getDeployMethod(); const receipt = await deployMethod.send({ from: AztecAddress.ZERO, fee: { paymentMethod }, wait: { timeout: 2400 }, }); // 6. Mark deployed in storage await markDeployed(address); reportProgress('Deploy complete!'); log.debug('[offscreen] Account deployed:', address, 'txHash:', receipt.txHash?.toString()); return { success: true, txHash: receipt.txHash?.toString() }; } ``` > [Source code: docs/examples/webapp-tutorial/test-extension/src/offscreen/offscreen.ts#L548-L599](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/offscreen/offscreen.ts#L548-L599) The deployment: 1. Gets the cached master `CryptoKey` (wallet must be unlocked) 2. Decrypts the account secret 3. Instantiates the account contract with derived keys 4. Initializes PXE and registers the account 5. Registers SponsoredFPC for fee payment 6. Deploys via `AccountManager.getDeployMethod()` with `SponsoredFeePaymentMethod` 7. Uses a heartbeat interval to keep the service worker alive during the long-running proof generation 8. Marks the account as deployed in storage ## Account Registration with PXE[​](#account-registration-with-pxe "Direct link to Account Registration with PXE") For PXE to track notes for an account, the extension registers the contract instance, artifact, and secret key. This is handled by the shared `registerAccountInWallet()` function: ``` async function registerAccountInWallet(address, secret, salt) { const { secretFr, saltFr, accountContract, artifact, instance } = await instantiateAccount(secret, salt); const wallet = await getWallet(); await wallet.registerContract(instance, artifact, secretFr); const accountManager = await AccountManager.create(wallet, secretFr, accountContract, saltFr); const account = await accountManager.getAccount(); wallet.registerAccount(address, account); } ``` Registration includes: * **Contract instance** - Address, class, initialization args * **Artifact** - The Noir contract artifact for simulation * **Secret key** - For note decryption ## Account Recovery[​](#account-recovery "Direct link to Account Recovery") Since the wallet stores: * Encrypted secret * Contract salt Users can recover accounts by: 1. Entering their password 2. Decrypting the secret 3. Recomputing the account address (deterministic) The account address is derived from: * Public keys (from secret) * Contract class ID (SchnorrAccountContract) * Contract salt ## Security Considerations[​](#security-considerations "Direct link to Security Considerations") The tutorial wallet implements several security best practices: 1. **Non-extractable CryptoKey** - The master key cannot be read from JavaScript 2. **600,000 PBKDF2 iterations** - Follows OWASP 2023 recommendations 3. **Per-account random IV** - Each account uses a unique AES-GCM initialization vector 4. **Auto-lock** - The wallet clears the cached key after 15 minutes of inactivity 5. **Secure random** - Uses `crypto.getRandomValues()` and `Fr.random()` (CSPRNG) For a production wallet, also consider: * Hardware security modules or platform keychain APIs * Seed phrase support for backup and restore * Memory protection (zeroing secrets after use) ## Next Steps[​](#next-steps "Direct link to Next Steps") With accounts created and stored, let's handle [Transaction Handling](/developers/testnet/docs/tutorials/js_tutorials/wallet-extension/transactions.md) - the signing and proof generation flow. --- # Approval UI The popup is the user-facing part of the wallet. It displays accounts, pending approvals, and handles user interactions. This section covers building the React-based popup. ## Popup Structure[​](#popup-structure "Direct link to Popup Structure") The popup is a small React app rendered when clicking the extension icon: ``` popup/ ├── popup.html # HTML entry point ├── popup.css # Styles └── src/popup/ ├── popup.tsx # Top-level orchestrator (state machine + routing) ├── helpers.ts # sendToBackground, waitForTask, truncateAddress ├── types.ts # Shared TypeScript interfaces ├── Header.tsx # Header + SubHeader components ├── SetupScreen.tsx # First-time password setup ├── LockScreen.tsx # Unlock with password ├── MainScreen.tsx # Active account detail + deploy ├── AccountSwitcher.tsx # Account list overlay ├── CreateAccountView.tsx # New account creation ├── ApprovalView.tsx # Connection + transaction approvals └── SettingsPage.tsx # Export/import wallet ``` The HTML loads the compiled JavaScript: ```
``` ## Main App Component[​](#main-app-component "Direct link to Main App Component") The popup is split into focused components. The top-level `popup.tsx` acts as an orchestrator with a state machine that routes between views: setup, lock, main, create-account, approvals, session-verification, and settings. main-app ``` function App() { const [view, setView] = useState('loading'); const [accounts, setAccounts] = useState([]); const [activeAccount, setActiveAccount] = useState(null); const [discoveries, setDiscoveries] = useState([]); const [transactions, setTransactions] = useState([]); const [connectedSites, setConnectedSites] = useState([]); const [sessionVerifications, setSessionVerifications] = useState([]); const [pendingCapabilities, setPendingCapabilities] = useState([]); const [runningTasks, setRunningTasks] = useState([]); const [error, setError] = useState(null); const [elapsed, setElapsed] = useState(0); const [pendingImportData, setPendingImportData] = useState(null); const portRef = useRef(null); const reconnectTimerRef = useRef | null>(null); const pendingCount = discoveries.length + transactions.length + sessionVerifications.length + pendingCapabilities.length; /** * Applies state pushed from the background via the port. (#10) * This is the single source of truth for pending items, tasks, and connected sites. */ const applyBackgroundState = useCallback((data: any) => { if (data.discoveries) setDiscoveries(data.discoveries); if (data.transactions) setTransactions(data.transactions); if (data.pendingSessionVerifications) setSessionVerifications(data.pendingSessionVerifications); if (data.pendingCapabilities) setPendingCapabilities(data.pendingCapabilities); if (data.connectedSites) setConnectedSites(data.connectedSites); if (data.tasks) { setRunningTasks(data.tasks.filter((t: BackgroundTask) => t.status === 'running')); // Resolve any waitForTask promises for completed tasks (#12) for (const task of data.tasks) { handleTaskUpdate(task); } } }, []); /** * Loads account data and determines the initial view. * Pending items come from the port push, NOT from a separate fetch. (#10) */ const loadData = useCallback(async () => { try { setError(null); const [accountsResult, activeAccountResult, statusResult] = await Promise.all([ sendToBackground({ type: MessageTypes.GET_ACCOUNTS }), sendToBackground({ type: MessageTypes.GET_ACTIVE_ACCOUNT }), sendToBackground({ type: MessageTypes.GET_WALLET_STATUS }), ]); setAccounts(accountsResult || []); setActiveAccount(activeAccountResult || null); const unlocked = statusResult?.unlocked || false; const hasPassword = statusResult?.hasPassword || false; const hasAccounts = (accountsResult || []).length > 0; if (!hasPassword && !hasAccounts) { setView('setup'); } else if (!unlocked) { setView('lock'); } else { setView((prev) => prev === 'loading' ? 'main' : prev); } } catch (err: any) { console.error('Failed to load data:', err); setError(err.message); setView('setup'); } }, []); /** * Connect persistent port to background. (#9) * Reconnects automatically if the background disconnects (e.g., SW restart). */ const connectPort = useCallback(() => { if (reconnectTimerRef.current) { clearTimeout(reconnectTimerRef.current); reconnectTimerRef.current = null; } try { const port = chrome.runtime.connect({ name: 'popup' }); portRef.current = port; port.onMessage.addListener((message: any) => { if (message.type === 'state') { applyBackgroundState(message.data); // Auto-navigate to approvals/verification if there are pending items const d = message.data.discoveries?.length || 0; const t = message.data.transactions?.length || 0; const sv = message.data.pendingSessionVerifications?.length || 0; const c = message.data.pendingCapabilities?.length || 0; if (sv > 0) { setView((prev) => (prev === 'main' || prev === 'loading' || prev === 'approvals') ? 'verifySession' : prev); } else if (d > 0 || t > 0 || c > 0) { setView((prev) => (prev === 'main' || prev === 'loading') ? 'approvals' : prev); } } else if (message.type === 'task-update') { const task: BackgroundTask = message.task; handleTaskUpdate(task); setRunningTasks((prev) => { if (task.status === 'running') { const existing = prev.findIndex((t) => t.id === task.id); if (existing >= 0) { const updated = [...prev]; updated[existing] = task; return updated; } return [...prev, task]; } return prev.filter((t) => t.id !== task.id); }); // Refresh account data if a state-changing task completed if (task.status === 'success') { const refreshTypes = ['create-account', 'deploy-account', 'unlock', 'setup-password', 'import-wallet-accounts']; if (refreshTypes.includes(task.type)) { loadData(); } } } }); port.onDisconnect.addListener(() => { console.log('[popup] Port disconnected, will reconnect...'); portRef.current = null; // Reconnect after a short delay (SW may be restarting) (#9) reconnectTimerRef.current = setTimeout(connectPort, 1000); }); } catch (err) { console.error('[popup] Failed to connect port:', err); // Retry connection (#9) reconnectTimerRef.current = setTimeout(connectPort, 2000); } }, [applyBackgroundState, loadData]); useEffect(() => { connectPort(); loadData(); return () => { if (reconnectTimerRef.current) { clearTimeout(reconnectTimerRef.current); } if (portRef.current) { portRef.current.disconnect(); portRef.current = null; } }; }, [connectPort, loadData]); // Reactive auto-navigation: ensures the popup shows the right view whenever // pending items exist, even if the port message handler's auto-nav fired while // the popup was on a non-target view (e.g. 'setup' or 'lock'). useEffect(() => { if (sessionVerifications.length > 0 && (view === 'main' || view === 'loading' || view === 'approvals')) { setView('verifySession'); } else if ((discoveries.length > 0 || transactions.length > 0 || pendingCapabilities.length > 0) && (view === 'main' || view === 'loading')) { setView('approvals'); } }, [sessionVerifications, discoveries, transactions, pendingCapabilities, view]); // Tick elapsed time while tasks are running useEffect(() => { if (runningTasks.length === 0) { setElapsed(0); return; } const oldest = Math.min(...runningTasks.map((t) => t.startedAt)); setElapsed(Math.round((Date.now() - oldest) / 1000)); const timer = setInterval(() => { setElapsed(Math.round((Date.now() - oldest) / 1000)); }, 1000); return () => clearInterval(timer); }, [runningTasks]); const handleUnlocked = () => { setView('main'); loadData(); }; const handleSetupComplete = async () => { if (pendingImportData) { try { const { taskId } = await sendToBackground({ type: MessageTypes.IMPORT_WALLET_ACCOUNTS, accounts: pendingImportData.accounts, activeAccount: pendingImportData.activeAccount, }); await waitForTask(taskId); setPendingImportData(null); } catch (err: any) { console.error('Failed to import accounts:', err); setError(err.message); setPendingImportData(null); } } setView('main'); loadData(); }; const handleImportStart = (data: WalletExportData) => { setPendingImportData(data); sendToBackground({ type: MessageTypes.IMPORT_WALLET }).then(() => { setView('setup'); }).catch((err) => { console.error('Failed to wipe wallet:', err); setError(err.message); setPendingImportData(null); }); }; const handleDisconnectSite = async (sessionId: string) => { try { await sendToBackground({ type: MessageTypes.DISCONNECT_SESSION, sessionId }); } catch (err) { console.error('Failed to disconnect session:', err); } }; const handleConfirmSession = async (sessionId: string) => { try { await sendToBackground({ type: MessageTypes.CONFIRM_SESSION, sessionId }); setView('main'); } catch (err) { console.error('Failed to confirm session:', err); } }; const handleRejectSession = async (sessionId: string) => { try { await sendToBackground({ type: MessageTypes.REJECT_SESSION, sessionId }); setView('main'); } catch (err) { console.error('Failed to reject session:', err); } }; const activeAccountData = accounts.find((a) => a.address === activeAccount) || accounts[0] || null; const handleApprovalClick = () => { if (sessionVerifications.length > 0) { setView('verifySession'); } else { setView('approvals'); } }; const noopDisconnect = () => {}; if (view === 'loading') { return (
{}} connectedSites={[]} onDisconnect={noopDisconnect} onSettingsClick={() => {}} />
Loading...
); } if (view === 'setup') { return (
{}} connectedSites={[]} onDisconnect={noopDisconnect} onSettingsClick={() => {}} />
); } if (view === 'lock') { return (
{}} connectedSites={[]} onDisconnect={noopDisconnect} onSettingsClick={() => {}} />
); } if (view === 'approvals') { return (
{}} connectedSites={connectedSites} onDisconnect={handleDisconnectSite} onSettingsClick={() => setView('settings')} /> setView('main')} />
); } if (view === 'verifySession') { const currentVerification = sessionVerifications[0]; return (
{}} connectedSites={connectedSites} onDisconnect={handleDisconnectSite} onSettingsClick={() => setView('settings')} /> setView('main')} /> {currentVerification ? ( handleConfirmSession(currentVerification.sessionId)} onReject={() => handleRejectSession(currentVerification.sessionId)} /> ) : (
No pending verifications
)}
); } if (view === 'switcher') { return (
setView('settings')} /> setView('main')} /> { sendToBackground({ type: MessageTypes.SET_ACTIVE_ACCOUNT, address }) .then(() => { setActiveAccount(address); setView('main'); }) .catch((err) => console.error('Failed to switch account:', err)); }} onCreateNew={() => setView('createAccount')} />
); } if (view === 'createAccount') { return (
setView('settings')} /> setView('switcher')} /> { setView('main'); loadData(); }} />
); } if (view === 'settings') { return (
{}} /> setView('main')} />
); } // Main view return (
setView('settings')} /> {error &&
{error}
} {runningTasks.length > 0 && (
{runningTasks.map((t) => { const labels: Record = { 'deploy-account': 'Deploying account...', 'create-account': 'Creating account...', 'unlock': 'Unlocking wallet...', 'setup-password': 'Setting up password...', 'export-wallet': 'Exporting wallet...', 'import-wallet-accounts': 'Importing accounts...', }; const genericLabel = t.type.startsWith('wallet:') ? `Processing ${t.type.replace('wallet:', '')}...` : t.type.startsWith('tx:') ? `Executing ${t.type.replace('tx:', '')}...` : labels[t.type] || 'Processing...'; return (
{t.progress || genericLabel}
{t.progress && (
{genericLabel}
)}
); })}
Elapsed: {Math.floor(elapsed / 60)}:{String(elapsed % 60).padStart(2, '0')}
)} {activeAccountData ? ( 0} onSwitcherOpen={() => setView('switcher')} onRefresh={loadData} /> ) : (
👛
No accounts yet
)}
); } ``` > [Source code: docs/examples/webapp-tutorial/test-extension/src/popup/popup.tsx#L34-L463](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/popup/popup.tsx#L34-L463) Key features: * **Persistent port** to background for real-time push updates (no polling) * Auto-reconnects if the background service worker restarts * Auto-switches to Approvals if there are pending items * Loads accounts and pending items on mount ## Communication with Background[​](#communication-with-background "Direct link to Communication with Background") The popup sends messages to the background script: send-message ``` /** * Sends a message to the background script via chrome.runtime.sendMessage. * Used for simple request/response calls (accounts, status, approvals). */ export function sendToBackground(message: any): Promise { return new Promise((resolve, reject) => { chrome.runtime.sendMessage( { ...message, target: MessageTarget.BACKGROUND }, (response) => { if (chrome.runtime.lastError) { reject(new Error(chrome.runtime.lastError.message)); return; } if (response?.success) { resolve(response.result); } else { reject(new Error(response?.error || 'Unknown error')); } } ); }); } ``` > [Source code: docs/examples/webapp-tutorial/test-extension/src/popup/helpers.ts#L4-L27](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/popup/helpers.ts#L4-L27) The popup targets the background explicitly with `target: MessageTarget.BACKGROUND` to distinguish from content script messages. ## Main Page[​](#main-page "Direct link to Main Page") The main page shows the active account and provides key actions: main-page ``` interface MainPageProps { account: StoredAccount; busy: boolean; onSwitcherOpen: () => void; onRefresh: () => void; } export function MainPage({ account, busy, onSwitcherOpen, onRefresh }: MainPageProps) { const [deployError, setDeployError] = useState(null); const [copied, setCopied] = useState(false); const handleDeploy = async () => { setDeployError(null); try { const { taskId } = await sendToBackground({ type: MessageTypes.DEPLOY_ACCOUNT, address: account.address, }); await waitForTask(taskId); onRefresh(); } catch (err: any) { setDeployError(err.message); } }; const copyAddress = () => { navigator.clipboard.writeText(account.address); setCopied(true); setTimeout(() => setCopied(false), 2000); }; return (
{/* Account selector pill */} {/* Account detail card */}
{account.isDeployed ? 'Deployed' : 'Not Deployed'}
{account.address}
{/* Deploy button */} {!account.isDeployed && (
{deployError &&
{deployError}
}
)}
); } ``` > [Source code: docs/examples/webapp-tutorial/test-extension/src/popup/MainScreen.tsx#L7-L81](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/popup/MainScreen.tsx#L7-L81) Features: * Active account with alias and truncated address * Deployment status indicator with deploy button for undeployed accounts * Account switcher for selecting between multiple accounts * Navigation to create-account and settings views ## Connection Approval[​](#connection-approval "Direct link to Connection Approval") When a dApp requests connection, the wallet shows the approval UI: connection-approval ``` interface ConnectionApprovalProps { discovery: PendingDiscovery; onApprove: () => void; onReject: () => void; processing: boolean; } function ConnectionApproval({ discovery, onApprove, onReject, processing, }: ConnectionApprovalProps) { return (
🔗
{getOriginHost(discovery.origin)}
Connection Request
Origin {discovery.origin}
{discovery.appId && (
App ID {discovery.appId}
)}
); } ``` > [Source code: docs/examples/webapp-tutorial/test-extension/src/popup/ApprovalView.tsx#L142-L198](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/popup/ApprovalView.tsx#L142-L198) The approval shows: * Origin URL (the dApp's domain) * App ID if provided * Connect/Reject buttons The wallet also includes emoji verification for secure channel confirmation — see the `session-verification` marker in the popup source. ## Transaction Approval[​](#transaction-approval "Direct link to Transaction Approval") Transaction approvals show more detail: transaction-approval ``` interface TransactionApprovalProps { transaction: PendingTransaction; onApprove: () => void; onReject: () => void; processing: boolean; } function TransactionApproval({ transaction, onApprove, onReject, processing, }: TransactionApprovalProps) { const methodLabels: Record = { sendTx: 'Send Transaction', simulateTx: 'Simulate Transaction', createAuthWit: 'Create Authorization', profileTx: 'Profile Transaction', batch: 'Batch Transaction', }; return (
📝
{getOriginHost(transaction.origin)}
{methodLabels[transaction.method] || transaction.method}
From {truncateAddress(transaction.from)}
Method {transaction.method}
{/* sendTx: show function calls from the execution payload (args[0]) */} {transaction.method === 'sendTx' && transaction.args?.[0]?.calls && (
Function Calls:
{transaction.args[0].calls.map((call: any, i: number) => (
{call.name || 'Unknown Function'}
Contract: {truncateAddress(call.to?.toString?.() || '')}
))}
)} {/* batch: show list of batched operations and their function calls */} {transaction.method === 'batch' && Array.isArray(transaction.args?.[0]) && (
Batched Operations:
{transaction.args[0].map((method: any, i: number) => (
{methodLabels[method.name] || method.name}
{method.name === 'sendTx' && method.args?.[0]?.calls?.map((call: any, j: number) => (
{call.name || 'Unknown'} → {truncateAddress(call.to?.toString?.() || '')}
))}
))}
)}
); } ``` > [Source code: docs/examples/webapp-tutorial/test-extension/src/popup/ApprovalView.tsx#L200-L296](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/popup/ApprovalView.tsx#L200-L296) The popup displays: * Origin and method type (sendTx, simulateTx, etc.) * From address (the signing account) * Function calls being made (if available) * Approve/Reject buttons ## Helper Functions[​](#helper-functions "Direct link to Helper Functions") Utility for address truncation: helpers ``` export function truncateAddress(address: string): string { if (!address) return ''; if (address.length <= 16) return address; return `${address.slice(0, 8)}...${address.slice(-6)}`; } /** * Creates an account and sets it as active. * Shared between SetupScreen (first account) and CreateAccountView (additional accounts). */ export async function createAndActivateAccount(alias: string): Promise { const { taskId } = await sendToBackground({ type: MessageTypes.CREATE_ACCOUNT, alias, }); const result = await waitForTask(taskId); if (result?.address) { await sendToBackground({ type: MessageTypes.SET_ACTIVE_ACCOUNT, address: result.address, }); } } ``` > [Source code: docs/examples/webapp-tutorial/test-extension/src/popup/helpers.ts#L75-L100](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/popup/helpers.ts#L75-L100) ## Styling[​](#styling "Direct link to Styling") The CSS provides a dark theme suited for wallet UIs: See the full stylesheet at [`popup/popup.css`](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/popup/popup.css). Key design choices: * Dark background (`#1a1a2e`) for modern look * Orange accent color (`#ff6b00`) for Aztec branding * Compact cards for account and approval display * Clear visual hierarchy with section titles ## State Management[​](#state-management "Direct link to State Management") The popup uses React's `useState` for local state: ``` const [view, setView] = useState('loading'); const [accounts, setAccounts] = useState([]); const [activeAccount, setActiveAccount] = useState(null); const [discoveries, setDiscoveries] = useState([]); const [transactions, setTransactions] = useState([]); const [connectedSites, setConnectedSites] = useState([]); const [sessionVerifications, setSessionVerifications] = useState([]); const [error, setError] = useState(null); ``` For a production wallet, consider: * Redux or Zustand for complex state * React Query for async data fetching * Local storage for UI preferences ## Error Handling[​](#error-handling "Direct link to Error Handling") Errors are displayed inline: ``` {error &&
{error}
} {success &&
{success}
} ``` Common errors: * Wrong password (decryption fails) * Network errors (node unreachable) * Transaction failures (contract reverts) ## Loading States[​](#loading-states "Direct link to Loading States") The popup shows spinners during async operations: ``` {loading ? (
Loading...
) : ( // Content )} ``` And disable buttons during processing: ``` ``` ## Building the Popup[​](#building-the-popup "Direct link to Building the Popup") The popup is built by **Vite** (not esbuild) as part of the main extension build. Vite handles JSX transformation, React support, and bundling: ``` node esbuild.extension.mjs # Step 1: Vite builds background, offscreen, and popup (with React JSX support) # Step 2: esbuild builds the content script separately as IIFE # Step 3-4: Copy static files (offscreen HTML, WASM binaries) ``` ## Popup Dimensions[​](#popup-dimensions "Direct link to Popup Dimensions") The popup size is controlled by CSS: ``` body { width: 360px; min-height: 400px; } ``` Chrome allows popups up to 800x600, but 360x500 is typical for wallets. ## Security Considerations[​](#security-considerations "Direct link to Security Considerations") For production popups: 1. **Input validation** - Sanitize all displayed data 2. **Origin verification** - Always show full origin for approvals 3. **Confirmation dialogs** - For destructive actions 4. **Rate limiting** - Prevent rapid-fire approvals 5. **Session timeout** - Auto-lock after inactivity ## Accessibility[​](#accessibility "Direct link to Accessibility") The current UI is minimal. Production improvements: * Keyboard navigation * ARIA labels * Screen reader support * High contrast mode * Focus management ## Testing the Popup[​](#testing-the-popup "Direct link to Testing the Popup") To test popup changes: 1. Rebuild: `node esbuild.extension.mjs` 2. Go to `chrome://extensions/` 3. Click refresh on the extension 4. Click the extension icon DevTools for popup: 1. Right-click the popup 2. Select "Inspect" 3. Use Console and Elements tabs ## Next Steps[​](#next-steps "Direct link to Next Steps") With the UI complete, let's put it all together in [Testing](/developers/testnet/docs/tutorials/js_tutorials/wallet-extension/testing.md) - loading the extension and testing with the Pod Racing dApp. --- # Extension Architecture Browser extension wallets face unique challenges that don't exist in embedded wallets. This section explains why you need a multi-component architecture and how the pieces fit together. ## The Service Worker Problem[​](#the-service-worker-problem "Direct link to The Service Worker Problem") Chrome's Manifest V3 requires extensions to use **service workers** instead of persistent background pages. Service workers have limitations that affect wallet development: 1. **5-minute timeout** - Service workers terminate after 5 minutes of inactivity 2. **No DOM access** - Can't use DOM APIs or run WASM directly 3. **Limited storage** - Must use async storage APIs like `chrome.storage` 4. **Cold starts** - Must reinitialize state when waking up For a wallet, these limitations are problematic because: * **PXE needs persistence** - The Private eXecution Environment maintains Merkle tree state * **Proof generation takes time** - Can exceed the 5-minute timeout * **WASM is essential** - Aztec's cryptographic operations use WASM ## The Solution: Offscreen Documents[​](#the-solution-offscreen-documents "Direct link to The Solution: Offscreen Documents") Manifest V3 introduced **offscreen documents** as a way to handle operations that service workers can't: ``` // In background.ts (service worker) await chrome.offscreen.createDocument({ url: 'offscreen.html', reasons: [chrome.offscreen.Reason.WORKERS], justification: 'Aztec PXE requires long-running WASM operations', }); ``` Offscreen documents: * Run longer than service workers * Support WASM and IndexedDB * Can maintain state across requests * Are invisible to users (no UI) ## Component Responsibilities[​](#component-responsibilities "Direct link to Component Responsibilities") ### Content Script (`content-script.ts`)[​](#content-script-content-scriptts "Direct link to content-script-content-scriptts") The content script runs in the context of every web page. Its only job is to relay messages: content-script ``` import { ContentScriptConnectionHandler, type ContentScriptTransport, } from '@aztec/wallet-sdk/extension/handlers'; const transport: ContentScriptTransport = { sendToBackground: (message) => { chrome.runtime.sendMessage(message); }, addBackgroundListener: (handler) => { chrome.runtime.onMessage.addListener((message) => { handler(message); }); }, }; const handler = new ContentScriptConnectionHandler(transport); handler.start(); console.log('[content-script] Wallet SDK handler started'); ``` > [Source code: docs/examples/webapp-tutorial/test-extension/src/content-script.ts#L1-L21](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/content-script.ts#L1-L21) It uses the `ContentScriptConnectionHandler` from the wallet SDK, which: * Listens for messages from the page (dApp) * Forwards them to the background service worker * Relays responses back to the page ### Service Worker (`background.ts`)[​](#service-worker-backgroundts "Direct link to service-worker-backgroundts") The service worker handles the wallet SDK protocol and coordinates between components: offscreen-management ``` let offscreenCreating: Promise | null = null; /** * Ensures the offscreen document exists. Creates it if needed. * The offscreen document hosts the PXE and wallet implementation. */ async function ensureOffscreenDocument(): Promise { const existingContexts = await chrome.runtime.getContexts({ contextTypes: [chrome.runtime.ContextType.OFFSCREEN_DOCUMENT], }); if (existingContexts.length > 0) { return; } if (offscreenCreating) { await offscreenCreating; return; } const offscreenUrl = chrome.runtime.getURL("dist/offscreen.html"); log.debug("[background] Creating offscreen document:", offscreenUrl); offscreenCreating = chrome.offscreen.createDocument({ url: offscreenUrl, reasons: [chrome.offscreen.Reason.WORKERS], justification: "Aztec PXE requires long-running WASM operations", }); await offscreenCreating; offscreenCreating = null; log.debug("[background] Offscreen document created"); } ``` > [Source code: docs/examples/webapp-tutorial/test-extension/src/background.ts#L36-L70](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/background.ts#L36-L70) Key responsibilities: * **Protocol handling** - Discovery, key exchange, session management * **Offscreen lifecycle** - Creating/checking the offscreen document * **Message routing** - Forwarding wallet calls to offscreen * **User approvals** - Triggering popups for connection/transaction approval ### Offscreen Document (`offscreen.ts`)[​](#offscreen-document-offscreents "Direct link to offscreen-document-offscreents") The offscreen document is where the heavy lifting happens: pxe-instance ``` /** * PXE + node — lazily initialized as a pair, with dedup on the inflight promise. */ let pxeState: { pxe: PXE; node: AztecNode } | null = null; let pxeInitializing: Promise<{ pxe: PXE; node: AztecNode }> | null = null; async function ensurePXE(nodeUrl: string = NODE_URL): Promise<{ pxe: PXE; node: AztecNode }> { if (pxeState) return pxeState; if (pxeInitializing) return pxeInitializing; log.debug('[offscreen] Initializing PXE with node:', nodeUrl); pxeInitializing = (async () => { try { const node = createAztecNodeClient(nodeUrl); const config = getPXEConfig(); config.l1Contracts = await node.getL1ContractAddresses(); const isLocal = nodeUrl.includes('localhost') || nodeUrl.includes('127.0.0.1'); config.proverEnabled = !isLocal; const pxe = await createPXE(node, config, {}); log.debug('[offscreen] PXE initialized, connected to node at:', nodeUrl); pxeState = { pxe, node }; return pxeState; } finally { pxeInitializing = null; // Always clear so a retry can re-attempt } })(); return pxeInitializing; } ``` > [Source code: docs/examples/webapp-tutorial/test-extension/src/offscreen/offscreen.ts#L123-L155](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/offscreen/offscreen.ts#L123-L155) It hosts: * **PXE instance** - The Private eXecution Environment * **Wallet implementation** - The `OffscreenWallet` that extends `BaseWallet` * **Method handlers** - All wallet operations (send, simulate, sign) ### Popup (`popup.tsx`)[​](#popup-popuptsx "Direct link to popup-popuptsx") The popup provides the user interface for: * Viewing accounts * Creating new accounts * Deploying account contracts * Approving connections * Approving transactions ## Message Flow[​](#message-flow "Direct link to Message Flow") Here's how a transaction flows through the system: ``` 1. dApp calls wallet.sendTx(...) ↓ 2. Page postMessage to content script ↓ 3. Content script → chrome.runtime.sendMessage → Background ↓ 4. Background checks: requires approval? ├─ No: Forward directly to offscreen └─ Yes: Store pending, update badge, wait for popup approval ↓ 5. User clicks extension icon, sees pending tx ↓ 6. User clicks "Approve" ↓ 7. Popup → chrome.runtime.sendMessage → Background ↓ 8. Background → persistent port → Offscreen ↓ 9. Offscreen executes: wallet.sendTx(...) - Creates execution request - Generates proof (WASM, can take time) - Submits to node ↓ 10. Response flows back: Offscreen → port → Background → Content → Page ``` The extension uses two messaging strategies: * **Persistent ports** (`chrome.runtime.connect`) for background ↔ offscreen and background ↔ popup. Ports provide point-to-point channels with automatic disconnect detection. * **One-shot messages** (`chrome.runtime.sendMessage`) for content script → background and popup → background requests. These are broadcast messages filtered by a `target` field. ## The Manifest[​](#the-manifest "Direct link to The Manifest") The `manifest.json` declares all components: ``` { "manifest_version": 3, "name": "Aztec Tutorial Wallet", "version": "1.0.0", "permissions": ["storage", "offscreen"], "background": { "service_worker": "dist/background.js", "type": "module" }, "content_scripts": [{ "matches": [""], "js": ["dist/content-script.js"], "run_at": "document_start" }], "action": { "default_popup": "popup/popup.html" } } ``` note This is a simplified manifest. The full version in the example project includes additional permissions (`alarms`, `notifications`, `windows`), `host_permissions`, `content_security_policy` for WASM, and `web_accessible_resources` for WASM files. Key points: * `"offscreen"` permission enables offscreen document creation * `"storage"` permission for encrypted key storage * `"type": "module"` enables ES modules in the service worker * `"run_at": "document_start"` ensures content script loads early ## Configuration[​](#configuration "Direct link to Configuration") Constants are centralized in `config.ts`: wallet-config ``` /** * Configuration for the Aztec Tutorial Wallet extension. * Uses SponsoredFPC for fee payment. */ /** Aztec node URL — defaults to a local sandbox. */ export const NODE_URL = 'http://localhost:8080'; /** Current @aztec/* package version, injected at build time by Vite. */ declare const __AZTEC_PACKAGES_VERSION__: string; export const AZTEC_PACKAGES_VERSION: string = typeof __AZTEC_PACKAGES_VERSION__ !== 'undefined' ? __AZTEC_PACKAGES_VERSION__ : 'unknown'; /** Wallet identification for the SDK protocol */ export const WALLET_CONFIG = { walletId: 'aztec-tutorial-wallet', walletName: 'Aztec Tutorial Wallet', walletVersion: '1.0.0', walletIcon: 'data:image/svg+xml,🔮', }; /** Auto-lock timeout in minutes. The wallet locks after this period of inactivity. (#28) */ export const AUTO_LOCK_MINUTES = 15; /** Message types for internal extension communication */ export const MessageTypes = { // Account management GET_ACCOUNTS: 'get-accounts', MARK_DEPLOYED: 'mark-deployed', // Full account creation in extension (uses Barretenberg) CREATE_ACCOUNT: 'create-account', DEPLOY_ACCOUNT: 'deploy-account', // Master password + wallet unlock SETUP_PASSWORD: 'setup-password', UNLOCK_WALLET: 'unlock-wallet', GET_WALLET_STATUS: 'get-wallet-status', // PXE operations INIT_PXE: 'init-pxe', REGISTER_ACCOUNT: 'register-account', // Active account management GET_ACTIVE_ACCOUNT: 'get-active-account', SET_ACTIVE_ACCOUNT: 'set-active-account', // Wallet export/import EXPORT_WALLET: 'export-wallet', IMPORT_WALLET: 'import-wallet', IMPORT_WALLET_ACCOUNTS: 'import-wallet-accounts', // Wallet SDK protocol — dispatches to BaseWallet WALLET_METHOD: 'wallet-method', // Auto-lock LOCK_WALLET: 'lock-wallet', // Popup -> Background APPROVE_CONNECTION: 'approve-connection', REJECT_CONNECTION: 'reject-connection', APPROVE_TRANSACTION: 'approve-transaction', REJECT_TRANSACTION: 'reject-transaction', CONFIRM_SESSION: 'confirm-session', REJECT_SESSION: 'reject-session', DISCONNECT_SESSION: 'disconnect-session', APPROVE_CAPABILITIES: 'approve-capabilities', REJECT_CAPABILITIES: 'reject-capabilities', } as const; /** Union type of all message type values — use for exhaustive checking. */ export type MessageType = (typeof MessageTypes)[keyof typeof MessageTypes]; /** Targets for chrome.runtime messages */ export const MessageTarget = { OFFSCREEN: 'offscreen', POPUP: 'popup', BACKGROUND: 'background', } as const; /** * Conditional logging. (#26) * Strips verbose logs in production while keeping errors visible. * Set DEBUG=true in the build to enable verbose logging. */ const DEBUG = process.env.NODE_ENV !== 'production'; // Toggle via build environment export const log = { debug: (...args: unknown[]) => { if (DEBUG) console.log(...args); }, info: (...args: unknown[]) => { if (DEBUG) console.info(...args); }, warn: (...args: unknown[]) => console.warn(...args), error: (...args: unknown[]) => console.error(...args), }; ``` > [Source code: docs/examples/webapp-tutorial/test-extension/src/config.ts#L1-L96](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/config.ts#L1-L96) This keeps configuration in one place and provides typed message constants. ## Building the Extension[​](#building-the-extension "Direct link to Building the Extension") The build uses a two-step process orchestrated by `esbuild.extension.mjs`: 1. **Vite** bundles the background script, offscreen document, and popup — with React support, node polyfills, and a custom plugin that patches Barretenberg worker files for `crossOriginIsolated` (see `vite.extension.config.ts`) 2. **esbuild** bundles the content script separately as IIFE (Chrome content scripts don't support ES modules) ``` node esbuild.extension.mjs ``` The script also copies static files (offscreen HTML, WASM binaries) to the correct locations. ## Next Steps[​](#next-steps "Direct link to Next Steps") Now that you understand the architecture, let's implement the [Wallet Protocol](/developers/testnet/docs/tutorials/js_tutorials/wallet-extension/wallet-protocol.md) - the discovery and key exchange that establishes secure connections between dApps and the wallet. --- # PXE Integration The Private eXecution Environment (PXE) is the core of any Aztec wallet. It handles private state, note management, and proof generation. This section shows how to run a PXE in the extension's offscreen document. ## Why PXE in Extensions?[​](#why-pxe-in-extensions "Direct link to Why PXE in Extensions?") Every Aztec wallet needs a PXE to: * **Sync private state** - Download and decrypt notes belonging to the user * **Generate proofs** - Create zero-knowledge proofs for private functions * **Manage contracts** - Register and track contract artifacts * **Compute witnesses** - Provide private inputs for transaction execution The PXE is stateful and long-running, which is why the extension uses an offscreen document instead of the service worker. ## Initializing PXE[​](#initializing-pxe "Direct link to Initializing PXE") The offscreen document initializes PXE lazily on first use with deduplication to prevent multiple initializations: pxe-instance ``` /** * PXE + node — lazily initialized as a pair, with dedup on the inflight promise. */ let pxeState: { pxe: PXE; node: AztecNode } | null = null; let pxeInitializing: Promise<{ pxe: PXE; node: AztecNode }> | null = null; async function ensurePXE(nodeUrl: string = NODE_URL): Promise<{ pxe: PXE; node: AztecNode }> { if (pxeState) return pxeState; if (pxeInitializing) return pxeInitializing; log.debug('[offscreen] Initializing PXE with node:', nodeUrl); pxeInitializing = (async () => { try { const node = createAztecNodeClient(nodeUrl); const config = getPXEConfig(); config.l1Contracts = await node.getL1ContractAddresses(); const isLocal = nodeUrl.includes('localhost') || nodeUrl.includes('127.0.0.1'); config.proverEnabled = !isLocal; const pxe = await createPXE(node, config, {}); log.debug('[offscreen] PXE initialized, connected to node at:', nodeUrl); pxeState = { pxe, node }; return pxeState; } finally { pxeInitializing = null; // Always clear so a retry can re-attempt } })(); return pxeInitializing; } ``` > [Source code: docs/examples/webapp-tutorial/test-extension/src/offscreen/offscreen.ts#L123-L155](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/offscreen/offscreen.ts#L123-L155) Key configuration: * `l1Contracts` - Required for the PXE to verify L1 state * `proverEnabled` - Enables client-side proof generation SponsoredFPC is registered lazily when the wallet's `completeFeeOptions()` is first called, rather than at PXE initialization time. ## The Wallet Implementation[​](#the-wallet-implementation "Direct link to The Wallet Implementation") The extension has two wallet-related classes with distinct responsibilities: 1. **`ExtensionWalletManager`** (in `wallet-impl.ts`) - A static utility class that handles secret generation, address computation, and encrypted storage. It does **not** extend `BaseWallet`. 2. **`OffscreenWallet`** (in `offscreen.ts`) - A `BaseWallet` subclass defined inline inside the `getWallet()` function that handles actual wallet operations. The `OffscreenWallet` extends `BaseWallet` from the wallet SDK: wallet-instance ``` /** Single wallet class used for all operations. (#18, #20) */ import type { BaseWallet } from '@aztec/wallet-sdk/base-wallet'; /** * The wallet instance holds a BaseWallet subclass with an additional * registerAccount method for tracking which accounts we can sign for. * BaseWallet is dynamically imported at runtime; using `import type` gives * us the type without a runtime dependency. (#20) */ type OffscreenWalletType = BaseWallet & { registerAccount(address: string, account: Account): void }; let walletInstance: OffscreenWalletType | null = null; /** * Creates a SponsoredFPC contract instance from its artifact and well-known salt. * Shared between OffscreenWallet.ensureSponsoredFPC() and handleDeployAccount(). */ async function getSponsoredFPCInstance() { const { Fr, SponsoredFPCContract, SPONSORED_FPC_SALT, getContractInstanceFromInstantiationParams } = await getAztecDeploy(); return getContractInstanceFromInstantiationParams( SponsoredFPCContract.artifact, { salt: new Fr(SPONSORED_FPC_SALT) }, ); } async function getWallet() { if (walletInstance) return walletInstance; const { BaseWallet, AztecAddress, SignerlessAccount } = await getAztecWallet(); const { pxe, node } = await ensurePXE(); // AccountFeePaymentMethodOptions.EXTERNAL = 0 — fee is paid by an external FPC const EXTERNAL_FEE_PAYMENT = 0; class OffscreenWallet extends BaseWallet { protected minFeePadding = 1.0; // 100% padding for fee estimation variance private accounts: Map = new Map(); private sponsoredFPCAddress: any | null = null; constructor(pxeInstance: PXE, aztecNode: AztecNode) { super(pxeInstance, aztecNode); } registerAccount(address: string, account: Account) { this.accounts.set(address, account); } protected async getAccountFromAddress(address: any): Promise { if (address.equals(AztecAddress.ZERO)) { return new SignerlessAccount(); } const key = address.toString(); const account = this.accounts.get(key); if (!account) { throw new Error(`Account not found for address: ${key}`); } return account; } async getAccounts() { return Array.from(this.accounts.entries()).map(([, acc]) => ({ alias: '', item: acc.getAddress(), })); } /** Lazily registers the SponsoredFPC contract and caches its address. */ private async ensureSponsoredFPC() { if (this.sponsoredFPCAddress) return this.sponsoredFPCAddress; const { SponsoredFPCContract } = await getAztecDeploy(); const sponsoredFPCInstance = await getSponsoredFPCInstance(); await this.registerContract(sponsoredFPCInstance, SponsoredFPCContract.artifact); this.sponsoredFPCAddress = sponsoredFPCInstance.address; return this.sponsoredFPCAddress; } /** * Always uses SponsoredFPC for fee payment, mirroring the deployment flow. * The tutorial wallet doesn't hold fee juice, so every tx is sponsor-paid. * * If the execution payload already has a feePayer (e.g. DeployAccountMethod * embeds SponsoredFPC in its own payload), we skip injecting a wallet-level * payment method to avoid calling sponsor_unconditionally() twice, which * would trigger "Cannot enter the revertible phase twice". */ protected async completeFeeOptions(from: any, feePayer?: any, gasSettings?: any) { const base = await super.completeFeeOptions(from, feePayer, gasSettings); // If the payload already includes a fee payer, don't inject another one if (feePayer) { return { ...base, accountFeePaymentMethodOptions: EXTERNAL_FEE_PAYMENT, }; } const address = await this.ensureSponsoredFPC(); const { SponsoredFeePaymentMethod } = await getAztecDeploy(); return { ...base, walletFeePaymentMethod: new SponsoredFeePaymentMethod(address), accountFeePaymentMethodOptions: EXTERNAL_FEE_PAYMENT, }; } /** * Overrides sendTx to auto-extract auth witnesses from offchain effects. * * dApps like gregoswap don't explicitly create auth witnesses. Instead, they * expect the wallet to handle it: simulate with a stub account (which passes * all auth checks), extract the authorization requests emitted by * `#[authorize_once]` in Noir contracts, sign them, and include them in the * real transaction. */ async sendTx(executionPayload: any, opts: any): Promise { if (executionPayload.authWitnesses.length === 0 && opts.from && !opts.from.equals(AztecAddress.ZERO)) { try { await this.extractAndInjectAuthWitnesses(executionPayload, opts.from, opts.fee?.gasSettings); } catch (err: any) { log.error('[offscreen] Auth witness extraction failed, proceeding without:', err.message, err.stack); } } return super.sendTx(executionPayload, opts); } /** * Simulates the tx with a stub account to collect offchain effects, * parses CallAuthorizationRequest objects, and creates real auth witnesses. */ private async extractAndInjectAuthWitnesses(executionPayload: any, from: any, feeGasSettings?: any) { const { Fr, getContractInstanceFromInstantiationParams } = await getAztecCore(); // Step 1: Create a stub account that passes all auth checks unconditionally log.info('[offscreen] Step 1: Loading stub account module...'); const realAccount = await this.getAccountFromAddress(from); const originalAddress = realAccount.getCompleteAddress(); log.info('[offscreen] Got complete address:', originalAddress.address.toString()); const { createStubAccount, getStubAccountContractArtifact } = await import('@aztec/accounts/stub/lazy'); log.info('[offscreen] Loaded @aztec/accounts/stub/lazy'); const stubArtifact = await getStubAccountContractArtifact(); log.info('[offscreen] Loaded stub artifact:', stubArtifact.name); const stubAccount = createStubAccount(originalAddress); const stubInstance = await getContractInstanceFromInstantiationParams(stubArtifact, { salt: Fr.random() }); log.info('[offscreen] Created stub account and instance'); // Step 2: Simulate with the stub account swapped in via PXE overrides log.info('[offscreen] Step 2: Simulating tx with stub account...'); const feeOptions = await this.completeFeeOptions(from, executionPayload.feePayer, feeGasSettings); const chainInfo = await this.getChainInfo(); const txRequest = await stubAccount.createTxExecutionRequest( executionPayload, feeOptions.gasSettings, chainInfo, { txNonce: Fr.random(), cancellable: false, feePaymentMethodOptions: feeOptions.accountFeePaymentMethodOptions }, ); log.info('[offscreen] Created tx execution request, simulating...'); const simResult = await this.pxe.simulateTx(txRequest, { simulatePublic: true, skipTxValidation: true, skipFeeEnforcement: true, overrides: { contracts: { [from.toString()]: { instance: stubInstance, artifact: stubArtifact } } }, scopes: [from], }); log.info('[offscreen] Simulation succeeded'); // Step 3: Extract auth witness requests from offchain effects log.info('[offscreen] Step 3: Extracting offchain effects...'); const { collectOffchainEffects } = await import('@aztec/stdlib/tx'); const { CallAuthorizationRequest } = await import('@aztec/aztec.js/authorization'); if (!simResult.privateExecutionResult) { log.warn('[offscreen] No privateExecutionResult in simulation result'); return; } const effects = collectOffchainEffects(simResult.privateExecutionResult); log.info(`[offscreen] Found ${effects.length} offchain effect(s)`); // Pre-filter by CallAuthorizationRequest selector (matching e2e test pattern) const callAuthSelector = await CallAuthorizationRequest.getSelector(); const authEffects = effects.filter((e: any) => e.data.length > 0 && e.data[0].equals(callAuthSelector.toField()), ); log.info(`[offscreen] ${authEffects.length} are CallAuthorizationRequest(s)`); // Step 4: Create auth witnesses from parsed authorization requests let count = 0; for (const effect of authEffects) { const authRequest = await CallAuthorizationRequest.fromFields(effect.data); log.info(`[offscreen] Auth request: consumer=${effect.contractAddress.toString()}, innerHash=${authRequest.innerHash.toString()}`); const wit = await this.createAuthWit(from, { consumer: effect.contractAddress, innerHash: authRequest.innerHash, }); executionPayload.authWitnesses.push(wit); count++; log.info(`[offscreen] Created auth witness #${count}: messageHash=${wit.requestHash.toString()}`); } log.info(`[offscreen] Auth witness extraction complete: ${count} witness(es) from ${effects.length} effect(s)`); } } walletInstance = new OffscreenWallet(pxe, node); return walletInstance; } ``` > [Source code: docs/examples/webapp-tutorial/test-extension/src/offscreen/offscreen.ts#L157-L369](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/offscreen/offscreen.ts#L157-L369) By extending `BaseWallet`, you inherit: * `sendTx()` - Transaction submission with proof generation * `simulateTx()` - Transaction simulation * `createAuthWit()` - Authorization witness creation * `registerContract()` - Contract registration * `getChainInfo()` - Network information * And more... You implement: * `getAccountFromAddress()` - Return the Account object for signing * `getAccounts()` - List available accounts * `completeFeeOptions()` - Configure fee payment (the SponsoredFPC override) * `sendTx()` - Override with automatic auth witness extraction ## SponsoredFPC Fee Payment[​](#sponsoredfpc-fee-payment "Direct link to SponsoredFPC Fee Payment") The key override is `completeFeeOptions()`, which lazily registers the SponsoredFPC contract on first use: complete-fee-options ``` /** * Always uses SponsoredFPC for fee payment, mirroring the deployment flow. * The tutorial wallet doesn't hold fee juice, so every tx is sponsor-paid. * * If the execution payload already has a feePayer (e.g. DeployAccountMethod * embeds SponsoredFPC in its own payload), we skip injecting a wallet-level * payment method to avoid calling sponsor_unconditionally() twice, which * would trigger "Cannot enter the revertible phase twice". */ protected async completeFeeOptions(from: any, feePayer?: any, gasSettings?: any) { const base = await super.completeFeeOptions(from, feePayer, gasSettings); // If the payload already includes a fee payer, don't inject another one if (feePayer) { return { ...base, accountFeePaymentMethodOptions: EXTERNAL_FEE_PAYMENT, }; } const address = await this.ensureSponsoredFPC(); const { SponsoredFeePaymentMethod } = await getAztecDeploy(); return { ...base, walletFeePaymentMethod: new SponsoredFeePaymentMethod(address), accountFeePaymentMethodOptions: EXTERNAL_FEE_PAYMENT, }; } ``` > [Source code: docs/examples/webapp-tutorial/test-extension/src/offscreen/offscreen.ts#L235-L262](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/offscreen/offscreen.ts#L235-L262) This ensures that by default: 1. If the payload already has a `feePayer` (e.g., during account deployment), the wallet respects it 2. Otherwise, the wallet injects `SponsoredFeePaymentMethod` so users don't need fee tokens to transact The `SponsoredFeePaymentMethod` creates an execution payload that: * Calls the SponsoredFPC contract's fee payment function * Gets merged with the user's transaction payload * Results in SponsoredFPC paying the fee ## Message Handling[​](#message-handling "Direct link to Message Handling") The offscreen document receives messages from the background via a persistent port. When the background connects with `chrome.runtime.connect({ name: 'offscreen' })`, the offscreen stores the port and listens for messages. Each message includes a `messageId` for request/response correlation: message-handler ``` /** * Handles messages from the background script via a persistent port. * The background connects with chrome.runtime.connect({ name: 'offscreen' }). * Each message includes a messageId for request/response correlation. */ chromeRuntime.runtime.onConnect.addListener((port: chrome.runtime.Port) => { if (port.name !== 'offscreen') return; log.debug('[offscreen] Background port connected'); backgroundPort = port; port.onMessage.addListener((message: any) => { log.debug('[offscreen] Received message:', message.type); handleMessage(message) .then((result) => { log.debug('[offscreen] Sending response for:', message.type); port.postMessage({ messageId: message.messageId, success: true, result }); }) .catch((error: unknown) => { const msg = getErrorMessage(error); log.error('[offscreen] Error:', msg, error); port.postMessage({ messageId: message.messageId, success: false, error: msg }); }); }); port.onDisconnect.addListener(() => { log.debug('[offscreen] Background port disconnected'); backgroundPort = null; }); }); async function handleMessage(message: any): Promise { switch (message.type) { case MessageTypes.GET_ACCOUNTS: return handleGetAccounts(); case MessageTypes.MARK_DEPLOYED: return handleMarkDeployed(message.address); case MessageTypes.WALLET_METHOD: return handleWalletMethod(message.method, message.args); case MessageTypes.SETUP_PASSWORD: return handleSetupPassword(message.password); case MessageTypes.CREATE_ACCOUNT: return handleCreateAccount(message.alias); case MessageTypes.DEPLOY_ACCOUNT: return handleDeployAccount(message.address); case MessageTypes.UNLOCK_WALLET: return handleUnlockWallet(message.password); case MessageTypes.INIT_PXE: return handleInitPXE(message.nodeUrl); case MessageTypes.REGISTER_ACCOUNT: return handleRegisterAccount(message.address, message.secret, message.salt); case MessageTypes.EXPORT_WALLET: return handleExportWallet(); case MessageTypes.IMPORT_WALLET_ACCOUNTS: return handleImportWalletAccounts(message.accounts, message.activeAccount); // Lock the wallet (clear cached key) — used by auto-lock (#28) case MessageTypes.LOCK_WALLET: cachedMasterKey = null; walletInstance = null; return { success: true }; default: throw new Error(`Unknown message type: ${message.type}`); } } ``` > [Source code: docs/examples/webapp-tutorial/test-extension/src/offscreen/offscreen.ts#L371-L449](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/offscreen/offscreen.ts#L371-L449) Each message type maps to a handler: * `INIT_PXE` - Ensures PXE is ready * `GET_ACCOUNTS` - Lists stored accounts * `CREATE_ACCOUNT` - Creates a new account * `DEPLOY_ACCOUNT` - Deploys an account contract * `WALLET_METHOD` - Handles dApp wallet calls * `SETUP_PASSWORD` - Sets master password on first use * `UNLOCK_WALLET` - Verifies password and registers accounts * `EXPORT_WALLET` / `IMPORT_WALLET_ACCOUNTS` - Wallet backup and restore ## Wallet Method Dispatch[​](#wallet-method-dispatch "Direct link to Wallet Method Dispatch") For wallet methods from dApps, the handler dispatches based on method name. The handler uses `WalletSchema` to parse incoming JSON arguments back into proper Aztec types (`AztecAddress`, `Fr`, `ExecutionPayload`, etc.), and `jsonStringify` to serialize results before returning through Chrome messaging: wallet-method-handler ``` /** * Handles wallet method calls from the ExtensionWallet proxy via the SDK protocol. * * Serialization notes: * 1. ARGS: Arrive as plain JSON. We use WalletSchema to parse them back into * proper Aztec types (AztecAddress, Fr, ExecutionPayload, etc.). * 2. RESULT: Contains class instances that lose prototypes through Chrome messaging. * We serialize with jsonStringify before returning. */ async function handleWalletMethod(method: string, args: any[]): Promise { log.debug('[offscreen] Handling wallet method:', method); const wallet = await getWallet(); // Dynamic dispatch: the wallet protocol sends method names as strings. // Cast to Record for dynamic access since TypeScript can't know the method at compile time. const walletObj = wallet as unknown as Record any>; if (typeof walletObj[method] !== 'function') { throw new Error(`Unknown wallet method: ${method}`); } const { WalletSchema, jsonStringify, schemaHasMethod } = await getAztecWallet(); // Parse args through WalletSchema to reconstruct proper Aztec types (Buffer, Fr, etc.) // from their JSON representations. The schema's .parameters() returns a zod tuple that // requires all positional elements even if some are optional. Pad with undefined so the // tuple length matches and the parse succeeds. let parsedArgs: any[] = args || []; if (schemaHasMethod(WalletSchema, method)) { const schema = WalletSchema[method as keyof typeof WalletSchema]; const paramSchema = schema.parameters(); const expectedLength = (paramSchema as any)?._def?.items?.length ?? 0; const paddedArgs = [...(args || [])]; while (paddedArgs.length < expectedLength) { paddedArgs.push(undefined); } try { parsedArgs = await paramSchema.parseAsync(paddedArgs); } catch (parseErr: any) { log.warn('[offscreen] Args parse warning for', method, ':', parseErr.message); parsedArgs = args || []; } } // Report initial progress for long-running methods so the popup shows something // before the PXE log matchers kick in const longRunningMethods = ['sendTx', 'simulateTx', 'profileTx']; if (longRunningMethods.includes(method)) { reportProgress(`Starting ${method}...`); } const result = await walletObj[method](...parsedArgs); // Serialize to JSON-safe format before returning through Chrome messaging const jsonSafe = JSON.parse(jsonStringify(result)); log.debug('[offscreen] Wallet method completed:', method); return jsonSafe; } ``` > [Source code: docs/examples/webapp-tutorial/test-extension/src/offscreen/offscreen.ts#L460-L519](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/offscreen/offscreen.ts#L460-L519) This generic dispatch means any method on the `BaseWallet` interface (e.g., `getAccounts`, `sendTx`, `simulateTx`, `createAuthWit`, `getChainInfo`) is automatically available to dApps through the wallet SDK protocol. ## Error Handling[​](#error-handling "Direct link to Error Handling") The port message handler wraps each operation in try/catch and posts the result back with the same `messageId` for correlation: ``` port.onMessage.addListener((message) => { handleMessage(message) .then((result) => { port.postMessage({ messageId: message.messageId, success: true, result }); }) .catch((error) => { port.postMessage({ messageId: message.messageId, success: false, error: error.message }); }); }); ``` Errors are serialized and sent back to the background, which can: * Show them in the popup * Return them to the dApp as wallet errors ## PXE State Persistence[​](#pxe-state-persistence "Direct link to PXE State Persistence") The PXE uses IndexedDB for persistence (via `@aztec/kv-store`). This means: * Synced notes persist across extension restarts * Registered contracts are remembered * Account registrations survive restarts However, the `OffscreenWallet.accounts` Map is in-memory and needs reloading when the user unlocks the wallet. This is handled by `handleUnlockWallet()`, which verifies the password, derives a `CryptoKey`, initializes PXE, and registers all stored accounts. See [Account Management](/developers/testnet/docs/tutorials/js_tutorials/wallet-extension/accounts.md) for details. ## Integration with BaseWallet[​](#integration-with-basewallet "Direct link to Integration with BaseWallet") The `BaseWallet` base class does the heavy lifting for transactions: ``` // In BaseWallet (inherited) async sendTx(executionPayload, opts) { // 1. Complete fee options (our override uses SponsoredFPC) const feeOptions = await this.completeFeeOptions(...); // 2. Create execution request const txRequest = await this.createTxExecutionRequestFromPayloadAndFee(...); // 3. Generate proof (WASM, can take time) const provenTx = await this.pxe.proveTx(txRequest); // 4. Submit to node const tx = await provenTx.toTx(); await this.aztecNode.sendTx(tx); // 5. Optionally wait for confirmation if (opts.wait !== NO_WAIT) { return await waitForTx(this.aztecNode, txHash, waitOpts); } return txHash; } ``` Our `OffscreenWallet` also overrides `sendTx()` to automatically extract authorization witnesses from offchain effects. This means dApps don't need to explicitly create auth witnesses - the wallet handles it by simulating with a stub account, collecting `CallAuthorizationRequest` objects, and signing them before the real transaction. ## Next Steps[​](#next-steps "Direct link to Next Steps") Now that PXE is running, let's implement [Account Management](/developers/testnet/docs/tutorials/js_tutorials/wallet-extension/accounts.md) - creating, storing, and managing user accounts with encrypted keys. --- # Testing the Wallet Extension This final section covers loading your wallet extension in Chrome and testing it with the Pod Racing dApp from the webapp tutorial. ## Building the Extension[​](#building-the-extension "Direct link to Building the Extension") From the `webapp-tutorial` directory: ``` # Install dependencies if needed yarn install # Build the extension node esbuild.extension.mjs ``` You should see: ``` Extension build complete! ``` The build creates: * `test-extension/dist/background.js` * `test-extension/dist/content-script.js` * `test-extension/dist/offscreen.js` * `test-extension/dist/popup.js` ## Loading in Chrome[​](#loading-in-chrome "Direct link to Loading in Chrome") 1. Open Chrome and navigate to `chrome://extensions/` 2. Enable **Developer mode** (toggle in top-right corner) 3. Click **Load unpacked** 4. Select the `test-extension` folder (not `dist/`) 5. The extension "Aztec Tutorial Wallet" should appear You should see: * Extension icon in the toolbar * "Service Worker" link under "Inspect views" ## First Launch[​](#first-launch "Direct link to First Launch") Click the extension icon to open the popup: 1. You'll see the setup screen prompting you to create a master password 2. Enter and confirm your password, then click "Create Wallet" 3. Once the password is set, you'll be prompted to create your first account 4. Enter an account alias (optional) and click "Create Account" The account appears with status "Pending" (not deployed yet). ## Deploying an Account[​](#deploying-an-account "Direct link to Deploying an Account") To deploy the account contract: 1. The wallet should already be unlocked from the setup step above 2. Click "Deploy" next to the account 3. Wait for the transaction (uses SponsoredFPC, no tokens needed) 4. Status changes to "Deployed" Check the console (Service Worker inspector) for logs: ``` [offscreen] Initializing wallet... [offscreen] Wallet initialized [offscreen] Received message: deploy-account ``` ## Running the dApp[​](#running-the-dapp "Direct link to Running the dApp") In another terminal, start the webapp tutorial: ``` cd docs/examples/webapp-tutorial # Start the dev server yarn dev ``` Open `http://localhost:5173` in Chrome. ## Connecting the Wallet[​](#connecting-the-wallet "Direct link to Connecting the Wallet") 1. In the dApp, select network "Browser Wallet" 2. Click "Connect Wallet" 3. Choose "Browser Wallet" The extension should: 1. Show a badge with "1" (pending connection) 2. When you click the icon, show the connection request To approve: 1. Click the extension icon 2. Go to "Approvals" tab 3. Review the origin (localhost:5173) 4. Click "Connect" The dApp should now show your account address. ## Deploying a Contract[​](#deploying-a-contract "Direct link to Deploying a Contract") In the dApp: 1. Click "Deploy Pod Racing Contract" 2. The extension shows a pending transaction To approve: 1. Click the extension icon 2. Go to "Approvals" tab 3. Review the transaction: * From: your account address * Method: sendTx * Calls: contract deployment 4. Click "Approve" Wait for the transaction to complete. This takes 30-60 seconds due to: * Proof generation (WASM) * Block confirmation ## Playing the Game[​](#playing-the-game "Direct link to Playing the Game") Once deployed: 1. Click "Boost" in the dApp 2. Approve the transaction in the extension 3. Watch your pod accelerate! Each boost is a private transaction that: * Updates your pod's speed (private state) * Generates a ZK proof * Gets included in a block ## Debugging[​](#debugging "Direct link to Debugging") ### Background Script[​](#background-script "Direct link to Background Script") 1. Go to `chrome://extensions/` 2. Find "Aztec Tutorial Wallet" 3. Click "Service Worker" under "Inspect views" 4. Check Console for `[background]` logs ### Offscreen Document[​](#offscreen-document "Direct link to Offscreen Document") 1. On the extensions page, look for "Offscreen document" 2. Click to open DevTools 3. Check Console for `[offscreen]` logs ### Content Script[​](#content-script "Direct link to Content Script") 1. Open DevTools on the dApp page (F12) 2. Check Console for `[content-script]` logs 3. May need to filter by extension ### Common Issues[​](#common-issues "Direct link to Common Issues") **"Cannot read properties of undefined"** * PXE (Private eXecution Environment) hasn't initialized yet * Check offscreen console for initialization errors **"Account not found"** * Account isn't loaded in memory * Try entering password to unlock **"Transaction rejected"** * Check if the Aztec node is reachable * Verify SponsoredFPC is registered **Popup doesn't show pending items** * Refresh the popup (close and reopen) * Check background console for errors ## Verifying on Explorer[​](#verifying-on-explorer "Direct link to Verifying on Explorer") If your network has a block explorer, you can verify transactions: 1. Copy your transaction hash from logs 2. Visit the explorer 3. Search for the transaction 4. Verify it's included in a block ## Reloading After Changes[​](#reloading-after-changes "Direct link to Reloading After Changes") When you modify the extension: 1. Rebuild: `node esbuild.extension.mjs` 2. Go to `chrome://extensions/` 3. Click the refresh icon on the extension 4. Reload any open dApp pages ## End-to-End Test Flow[​](#end-to-end-test-flow "Direct link to End-to-End Test Flow") Complete test checklist: 1. Build extension 2. Load in Chrome 3. Create account in popup 4. Deploy account (SponsoredFPC) 5. Start dApp 6. Connect wallet (approve in popup) 7. Verify account shows in dApp 8. Deploy Pod Racing contract (approve in popup) 9. Play game (approve boost transactions) 10. Check transactions in explorer ## Production Considerations[​](#production-considerations "Direct link to Production Considerations") Before releasing a wallet extension: 1. **Security audit** - Professional review of crypto code 2. **Key management** - Consider hardware wallet support 3. **Network switching** - Support testnet, mainnet 4. **Error recovery** - Graceful handling of failures 5. **Backup/restore** - Seed phrase support 6. **Multi-account** - Better account management UI 7. **Transaction history** - Show past transactions 8. **Note management** - Display synced notes ## Summary[​](#summary "Direct link to Summary") You've built a functional wallet extension that: * Creates and stores encrypted accounts * Deploys contracts using SponsoredFPC * Connects to dApps via the wallet SDK protocol * Approves transactions with a popup UI * Generates ZK proofs for private transactions This is the foundation for a production Aztec wallet. The architecture patterns - offscreen documents, message routing, BaseWallet extension - apply to any browser wallet. ## What's Next?[​](#whats-next "Direct link to What's Next?") * Add more account types (ECDSA, multisig) * Implement transaction history * Add network switching * Build a note browser * Support hardware wallets Happy building! --- # Transaction Handling Transaction handling is the core purpose of a wallet. This section explains how transactions flow through the extension, from dApp request to onchain settlement. ## Transaction Flow Overview[​](#transaction-flow-overview "Direct link to Transaction Flow Overview") ``` 1. dApp calls wallet.sendTx(payload, options) ↓ 2. Content script encrypts and forwards to background ↓ 3. Background stores as pending, shows in popup ↓ 4. User reviews and clicks "Approve" ↓ 5. Background forwards to offscreen ↓ 6. Offscreen: a. Deserializes ExecutionPayload b. Gets fee options (SponsoredFPC) c. Creates TxExecutionRequest d. Generates proof (WASM, 10-60 seconds) e. Submits to node ↓ 7. Response flows back to dApp ``` ## ExecutionPayload[​](#executionpayload "Direct link to ExecutionPayload") The dApp sends an `ExecutionPayload` containing: ``` interface ExecutionPayload { calls: FunctionCall[]; // Contract calls to execute authWitnesses: AuthWitness[]; // Pre-signed authorizations capsules: any[]; // Encrypted data capsules feePayer?: AztecAddress; // Optional explicit fee payer } interface FunctionCall { to: AztecAddress; // Target contract functionSelector: FunctionSelector; args: Fr[]; // Function arguments isStatic: boolean; // View call? } ``` Example from a Pod Racing dApp: ``` const payload = new ExecutionPayload([ { to: gameContractAddress, functionSelector: FunctionSelector.fromSignature('boost()'), args: [], isStatic: false, }, ]); const receipt = await wallet.sendTx(payload, { from: playerAddress, }); ``` ## Approval Flow[​](#approval-flow "Direct link to Approval Flow") When a wallet message arrives, the background checks whether it needs approval: ``` // sendTx always requires approval — it's a state-changing operation. // batch requires approval only if it contains a sendTx. // Read-only calls (simulateTx, getAccounts, etc.) auto-execute. const needsApproval = message.type === 'sendTx' || (message.type === 'batch' && Array.isArray(message.args?.[0]) && message.args[0].some((m: any) => m.name === 'sendTx')); if (needsApproval) { const pending = { sessionId: session.sessionId, messageId: message.messageId, method: message.type, args: message.args, from, origin: session.origin, timestamp: Date.now(), }; pendingTransactions.push(pending); updateBadge(); // User must approve in popup } ``` The badge shows the pending count, alerting the user. ## Approval UI[​](#approval-ui "Direct link to Approval UI") The popup displays transaction details: ``` function TransactionApproval({ transaction, onApprove, onReject }) { return (
{new URL(transaction.origin).host}
Send Transaction
From {truncateAddress(transaction.from)}
{/* Show function calls */} {transaction.args?.executionPayload?.calls?.map((call, i) => (
{call.functionSelector?.name || 'Unknown'}
To: {truncateAddress(call.to)}
))}
); } ``` ## Processing Approved Transactions[​](#processing-approved-transactions "Direct link to Processing Approved Transactions") When the user approves: ``` async function handleTransactionApproval(pending) { // Forward to offscreen for execution const result = await sendToOffscreen({ type: MessageTypes.WALLET_METHOD, method: pending.method, args: pending.args, from: pending.from, }); // Send response back to dApp await handler.sendResponse(pending.sessionId, { messageId: pending.messageId, result, walletId: WALLET_CONFIG.walletId, }); return result; } ``` ## Offscreen Execution[​](#offscreen-execution "Direct link to Offscreen Execution") The offscreen document handles the actual transaction: ``` case 'sendTx': { const { executionPayload, options } = args; // 1. Deserialize the payload const payload = deserializeExecutionPayload(executionPayload); const fromAddress = AztecAddress.fromStringUnsafe(from || options.from); // 2. Call wallet.sendTx (inherited from BaseWallet) const result = await wallet.sendTx(payload, { ...options, from: fromAddress, }); // 3. Serialize the result if (typeof result === 'object' && 'txHash' in result) { return { txHash: result.txHash.toString(), status: result.status, blockNumber: result.blockNumber?.toString(), }; } return { txHash: result.toString() }; } ``` note The actual implementation uses `WalletSchema` to parse arguments in a type-safe way, as described in [PXE Integration](/developers/testnet/docs/tutorials/js_tutorials/wallet-extension/pxe-integration.md). The above is a simplified view of the logic. ## BaseWallet.sendTx[​](#basewalletsendtx "Direct link to BaseWallet.sendTx") For details on how `BaseWallet.sendTx` works with `completeFeeOptions`, see [PXE Integration](/developers/testnet/docs/tutorials/js_tutorials/wallet-extension/pxe-integration.md#sponsoredfpc-fee-payment). The inherited `sendTx` method does the heavy lifting: ``` // In BaseWallet (inherited by OffscreenWallet) async sendTx(executionPayload, opts) { // 1. Get fee options (our override uses SponsoredFPC!) const feeOptions = await this.completeFeeOptions( opts.from, executionPayload.feePayer, opts.fee?.gasSettings ); // 2. Create execution request const txRequest = await this.createTxExecutionRequestFromPayloadAndFee( executionPayload, opts.from, feeOptions ); // 3. Generate proof (this is the slow part) const provenTx = await this.pxe.proveTx(txRequest); // 4. Convert to onchain transaction const tx = await provenTx.toTx(); const txHash = tx.getTxHash(); // 5. Submit to node await this.aztecNode.sendTx(tx); // 6. Optionally wait for confirmation if (opts.wait !== NO_WAIT) { return await waitForTx(this.aztecNode, txHash, opts.wait); } return txHash; } ``` ## SponsoredFPC Integration[​](#sponsoredfpc-integration "Direct link to SponsoredFPC Integration") Our `completeFeeOptions` override in `OffscreenWallet` ensures SponsoredFPC is used: ``` protected async completeFeeOptions(from, feePayer, gasSettings) { const base = await super.completeFeeOptions(from, feePayer, gasSettings); // If the payload already includes a fee payer, don't inject another one if (feePayer) { return { ...base, accountFeePaymentMethodOptions: 0, // EXTERNAL }; } // Otherwise, lazily register and use SponsoredFPC const address = await this.ensureSponsoredFPC(); return { ...base, walletFeePaymentMethod: new SponsoredFeePaymentMethod(address), accountFeePaymentMethodOptions: 0, // EXTERNAL: sponsored FPC pays }; } ``` The `SponsoredFeePaymentMethod`: 1. Creates a fee payment execution payload 2. Gets merged with the user's transaction 3. Calls the SponsoredFPC contract to pay fees 4. User transaction executes with paid fees ## Proof Generation[​](#proof-generation "Direct link to Proof Generation") Proof generation is the slowest part (10-60 seconds): ``` const provenTx = await this.pxe.proveTx(txRequest); ``` This: 1. Executes private functions locally 2. Generates WASM-based zero-knowledge proofs 3. Creates the kernel proofs 4. Packages everything for submission The offscreen document handles this well because it: * Doesn't have service worker timeouts * Supports WASM * Can use IndexedDB for intermediate state ## Simulation[​](#simulation "Direct link to Simulation") For gas estimation or validation, dApps use `simulateTx`: ``` case 'simulateTx': { const { executionPayload, options } = args; const payload = deserializeExecutionPayload(executionPayload); const fromAddress = AztecAddress.fromStringUnsafe(from || options.from); const result = await wallet.simulateTx(payload, { ...options, from: fromAddress, }); return serializeTxSimulationResult(result); } ``` Simulation: * Executes the transaction locally * Estimates gas usage * Detects revert conditions * Doesn't generate full proofs * Much faster than `sendTx` ## Error Handling[​](#error-handling "Direct link to Error Handling") Transactions can fail at several points: ``` try { const result = await wallet.sendTx(payload, options); return { success: true, result }; } catch (error) { // Could be: // - Simulation failure (contract revert) // - Proof generation failure // - Node rejection (already settled, insufficient gas) // - Network error return { success: false, error: error.message }; } ``` Errors are serialized and returned to the dApp, which should handle them gracefully. ## Authorization Witnesses[​](#authorization-witnesses "Direct link to Authorization Witnesses") For delegated actions (like approving token spending), the wallet creates auth witnesses: ``` case 'createAuthWit': { const { from: authFrom, messageHashOrIntent } = args; const fromAddress = AztecAddress.fromStringUnsafe(authFrom); const authWit = await wallet.createAuthWit(fromAddress, messageHashOrIntent); return { requestHash: authWit.requestHash.toString(), witness: Array.from(authWit.witness), }; } ``` The account signs the authorization, which can be used by other contracts to verify permission. ## Transaction Status[​](#transaction-status "Direct link to Transaction Status") After submission, transactions go through states: 1. **Pending** - Submitted, waiting for sequencer 2. **Included** - In a block, but not proven 3. **Proven** - Epoch proof submitted 4. **Finalized** - L1 finality achieved The wallet can track status: ``` const receipt = await waitForTx(this.aztecNode, txHash, { timeout: 60_000, // 60 seconds interval: 1_000, // Check every second }); console.log(receipt.status); // 'success' | 'reverted' console.log(receipt.blockNumber); ``` ## Next Steps[​](#next-steps "Direct link to Next Steps") With transactions flowing, let's build the [Approval UI](/developers/testnet/docs/tutorials/js_tutorials/wallet-extension/approval-ui.md) - the React popup that makes all this user-friendly. --- # Wallet Protocol The Aztec wallet SDK defines a protocol for dApps to discover and communicate with wallets securely. This section covers how the extension implements this protocol. ## Protocol Overview[​](#protocol-overview "Direct link to Protocol Overview") The wallet SDK protocol has three phases: 1. **Discovery** - dApp broadcasts a request, wallets respond with their info 2. **Key Exchange** - ECDH establishes a shared secret for encrypted messaging 3. **Secure Messaging** - All subsequent messages are encrypted This design: * Works with any number of wallets * Prevents eavesdropping on wallet calls * Allows user verification (emoji codes) ## Discovery Phase[​](#discovery-phase "Direct link to Discovery Phase") When a dApp calls `aztec.connect()`, it broadcasts a discovery request: ``` // From dApp (simplified) const wallet = await aztec.connect({ appId: 'my-dapp', chainInfo: { chainId: 31337, version: 1 }, }); ``` The content script receives this and forwards it to the background: ``` // Content script forwards to background chrome.runtime.sendMessage({ type: 'DISCOVERY_REQUEST', content: { appId, chainInfo, requestId }, }); ``` The background service worker handles it: callbacks ``` const callbacks: BackgroundConnectionCallbacks = { onPendingDiscovery: async (discovery) => { log.debug( "[background] Pending discovery:", discovery.requestId, "from", discovery.origin, ); // Clean up stale sessions from this tab (e.g. page refresh creates a new // discovery while the old session is still in activeSessions). for (const session of handler.getActiveSessions()) { if (session.tabId === discovery.tabId) { log.debug( "[background] Terminating stale session for tab:", discovery.tabId, session.sessionId, ); capabilitiesApprovedSessions.delete(session.sessionId); queuedMessages.delete(session.sessionId); handler.terminateSession(session.sessionId); } } // Deduplicate: reject any existing discovery from the same tab const existing = handler .getPendingDiscoveries() .find( (d) => d.tabId === discovery.tabId && d.requestId !== discovery.requestId, ); if (existing) { handler.rejectDiscovery(existing.requestId); } // Auto-approve if origin is already trusted (reconnection after page refresh) if (await isTrustedOrigin(discovery.origin, discovery.appId)) { log.debug( "[background] Auto-approving trusted origin:", discovery.origin, ); handler.approveDiscovery(discovery.requestId); return; } updateBadge(); openPopupWithFallback(); }, onSessionEstablished: async (session: ActiveSession) => { log.debug("[background] Session established:", session.sessionId); // Auto-confirm if origin is already trusted (skip emoji verification) if (await isTrustedOrigin(session.origin, session.appId)) { log.debug( "[background] Auto-confirming trusted session:", session.sessionId, ); // Pre-approve capabilities if previously granted (enables seamless reconnect) const savedCaps = await getStoredCapabilities( session.origin, session.appId, ); if (savedCaps) { capabilitiesApprovedSessions.add(session.sessionId); } // Flush any queued messages immediately (same logic as CONFIRM_SESSION handler) const queued = queuedMessages.get(session.sessionId) ?? []; queuedMessages.delete(session.sessionId); for (const { session: s, message: msg } of queued) { processWalletMessage(s, msg); } pushStateToPopup(); return; } // New origin — require emoji verification log.debug( "[background] Awaiting emoji verification for:", session.sessionId, ); // SDK automatically removes the discovery when key exchange completes. // Show emojis in approvals so user can compare with the webapp pendingSessionVerifications.push({ sessionId: session.sessionId, origin: session.origin, appId: session.appId, verificationHash: session.verificationHash, timestamp: Date.now(), }); updateBadge(); // Only open popup if not already connected — calling openPopup() on an // already-open popup rejects, and the fallback creates a second window // that steals the popupPort from the original. if (!popupPort) { openPopupWithFallback(); } pushStateToPopup(); }, /** * Handles wallet method calls from the ExtensionWallet proxy. * Messages are queued while emoji verification is pending — the extension * user must confirm before any dApp calls are processed. */ onWalletMessage: async (session: ActiveSession, message: any) => { log.debug( "[background] Wallet message:", message.type, "from session:", session.sessionId, ); // Block wallet messages until the user confirms emoji verification in the extension. // The dApp's calls (e.g. getAccounts) will wait until the extension user approves. const awaitingVerification = pendingSessionVerifications.some( (v) => v.sessionId === session.sessionId, ); if (awaitingVerification) { log.debug( "[background] Session awaiting verification, queuing message:", message.type, ); const queue = queuedMessages.get(session.sessionId) ?? []; queue.push({ session, message }); queuedMessages.set(session.sessionId, queue); return; } await processWalletMessage(session, message); }, }; ``` > [Source code: docs/examples/webapp-tutorial/test-extension/src/background.ts#L744-L884](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/background.ts#L744-L884) Key points: * Each discovery becomes a "pending discovery" awaiting user approval * The `BackgroundConnectionHandler` from wallet SDK manages the protocol state * `onPendingDiscovery` callback lets us show the connection request to users ## Connection Approval[​](#connection-approval "Direct link to Connection Approval") When the user clicks "Connect" in the popup: popup-messages ``` /** * Handle messages from popup and offscreen for approvals and account management. */ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { /** * Storage proxy for the offscreen document. (#7) * Validates that the request comes from the extension itself (not content scripts or external). */ if (message.type === "storage-get" || message.type === "storage-set") { // Security: only allow storage proxy from extension pages (offscreen, popup) (#7) // Content scripts have sender.tab set; extension pages (offscreen, popup) do not. if (sender.tab) { log.warn( "[background] Rejected storage proxy from content script, tab:", sender.tab.id, ); sendResponse({ success: false, error: "Storage proxy not allowed from content scripts", }); return false; } if (message.type === "storage-get") { chrome.storage.local .get(message.key) .then((result) => { sendResponse({ success: true, result: result[message.key] }); }) .catch((err) => { sendResponse({ success: false, error: err.message }); }); } else { chrome.storage.local .set(message.data) .then(() => { sendResponse({ success: true }); }) .catch((err) => { sendResponse({ success: false, error: err.message }); }); } return true; // async response (#23) } if (message.target !== MessageTarget.BACKGROUND) { return false; } log.debug("[background] Popup message:", message.type); // Reset auto-lock on any popup interaction (#28) resetAutoLockTimer(); switch (message.type) { case MessageTypes.APPROVE_CONNECTION: { handler.approveDiscovery(message.requestId); updateBadge(); sendResponse({ success: true }); return false; // sync response (#23) } case MessageTypes.REJECT_CONNECTION: { handler.rejectDiscovery(message.requestId); updateBadge(); sendResponse({ success: true }); return false; } case MessageTypes.APPROVE_TRANSACTION: { const pending = pendingTransactions.find( (t) => t.messageId === message.messageId, ); if (pending) { pendingTransactions = pendingTransactions.filter( (t) => t.messageId !== message.messageId, ); updateBadge(); const taskId = startBackgroundTask( `tx:${pending.method}`, handleTransactionApproval(pending), ); sendResponse({ success: true, result: { taskId } }); } else { sendResponse({ success: false, error: "Transaction not found" }); } return false; } case MessageTypes.REJECT_TRANSACTION: { const pending = pendingTransactions.find( (t) => t.messageId === message.messageId, ); if (pending) { handler.sendResponse(pending.sessionId, { messageId: pending.messageId, error: "Transaction rejected by user", walletId: WALLET_CONFIG.walletId, }); pendingTransactions = pendingTransactions.filter( (t) => t.messageId !== message.messageId, ); updateBadge(); } sendResponse({ success: true }); return false; } case MessageTypes.APPROVE_CAPABILITIES: { const pending = pendingCapabilities.find( (c) => c.messageId === message.messageId, ); if (pending) { pendingCapabilities = pendingCapabilities.filter( (c) => c.messageId !== message.messageId, ); // Build granted capabilities using the shared active-account helper getGrantableAccounts() .then((grantedAccounts) => { const granted = pending.capabilities.map((cap: any) => { if (cap.type === "accounts") { return { ...cap, accounts: grantedAccounts }; } return { ...cap }; }); return handler.sendResponse(pending.sessionId, { messageId: pending.messageId, result: { version: "1.0", granted, wallet: { name: WALLET_CONFIG.walletName, version: WALLET_CONFIG.walletVersion, }, }, walletId: WALLET_CONFIG.walletId, }); }) .then(async () => { capabilitiesApprovedSessions.add(pending.sessionId); // Persist granted capabilities for auto-reconnect const approvedSession = handler.getSession(pending.sessionId); if (approvedSession) { const trusted = await getTrustedOrigins(); const entry = trusted.find( (t) => t.origin === approvedSession.origin && t.appId === approvedSession.appId, ); if (entry) { entry.grantedCapabilities = pending.capabilities.map( (cap: any) => ({ ...cap }), ); await chrome.storage.local.set({ [TRUSTED_ORIGINS_KEY]: trusted, }); } } updateBadge(); sendResponse({ success: true }); }) .catch((err) => { log.error("[background] Failed to approve capabilities:", err); sendResponse({ success: false, error: getErrorMessage(err) }); }); } else { sendResponse({ success: false, error: "Capability request not found" }); return false; } return true; } case MessageTypes.REJECT_CAPABILITIES: { const pending = pendingCapabilities.find( (c) => c.messageId === message.messageId, ); if (pending) { pendingCapabilities = pendingCapabilities.filter( (c) => c.messageId !== message.messageId, ); handler.sendResponse(pending.sessionId, { messageId: pending.messageId, result: { version: "1.0", granted: [], wallet: { name: WALLET_CONFIG.walletName, version: WALLET_CONFIG.walletVersion, }, }, walletId: WALLET_CONFIG.walletId, }); updateBadge(); } sendResponse({ success: true }); return false; } case MessageTypes.CONFIRM_SESSION: { // User confirmed emojis match — session is now fully active. // Flush any wallet messages that were queued while awaiting verification. pendingSessionVerifications = pendingSessionVerifications.filter( (v) => v.sessionId !== message.sessionId, ); const queued = queuedMessages.get(message.sessionId) ?? []; queuedMessages.delete(message.sessionId); for (const { session, message: msg } of queued) { log.debug("[background] Flushing queued message:", msg.type); processWalletMessage(session, msg); } // Remember this origin as trusted for future reconnections (#30) const confirmedSession = handler.getSession(message.sessionId); if (confirmedSession) { addTrustedOrigin(confirmedSession.origin, confirmedSession.appId); } updateBadge(); sendResponse({ success: true }); return false; } case MessageTypes.REJECT_SESSION: { // User rejected emoji verification — reject queued messages and terminate the session. pendingSessionVerifications = pendingSessionVerifications.filter( (v) => v.sessionId !== message.sessionId, ); const rejected = queuedMessages.get(message.sessionId) ?? []; queuedMessages.delete(message.sessionId); for (const { session, message: msg } of rejected) { handler.sendResponse(session.sessionId, { messageId: msg.messageId, error: "Session verification rejected by user", walletId: WALLET_CONFIG.walletId, }); } handler.terminateSession(message.sessionId); updateBadge(); sendResponse({ success: true }); return false; } case MessageTypes.DISCONNECT_SESSION: { // (#29) Allow users to disconnect a specific dApp session // Remove from trusted origins so next connection requires full approval (#30) const disconnectedSession = handler.getSession(message.sessionId); if (disconnectedSession) { removeTrustedOrigin( disconnectedSession.origin, disconnectedSession.appId, ); } capabilitiesApprovedSessions.delete(message.sessionId); handler.terminateSession(message.sessionId); pushStateToPopup(); sendResponse({ success: true }); return false; } case "getPendingItems": { sendResponse({ success: true, result: getFullState(), }); return false; } case MessageTypes.GET_ACCOUNTS: { chrome.storage.local .get(STORAGE_KEYS.ACCOUNTS) .then((data) => { const accounts = (data[STORAGE_KEYS.ACCOUNTS] || []).map( (acc: any) => ({ address: acc.address, alias: acc.alias, isDeployed: acc.isDeployed, }), ); sendResponse({ success: true, result: accounts }); }) .catch((error) => sendResponse({ success: false, error: error.message }), ); return true; // async (#23) } case MessageTypes.GET_ACTIVE_ACCOUNT: { chrome.storage.local .get(STORAGE_KEYS.ACTIVE_ACCOUNT) .then((data) => { sendResponse({ success: true, result: data[STORAGE_KEYS.ACTIVE_ACCOUNT] || null, }); }) .catch((error) => sendResponse({ success: false, error: error.message }), ); return true; } case MessageTypes.SET_ACTIVE_ACCOUNT: { chrome.storage.local .set({ [STORAGE_KEYS.ACTIVE_ACCOUNT]: message.address }) .then(() => sendResponse({ success: true })) .catch((error) => sendResponse({ success: false, error: error.message }), ); return true; } case MessageTypes.UNLOCK_WALLET: { const taskId = startBackgroundTask( "unlock", sendToOffscreen({ type: MessageTypes.UNLOCK_WALLET, password: message.password, }).then((result) => { walletUnlocked = true; persistState(); resetAutoLockTimer(); return result; }), ); sendResponse({ success: true, result: { taskId } }); return false; } case MessageTypes.GET_WALLET_STATUS: { chrome.storage.local .get(STORAGE_KEYS.PASSWORD_DATA) .then((data) => { sendResponse({ success: true, result: { unlocked: walletUnlocked, hasPassword: !!data[STORAGE_KEYS.PASSWORD_DATA], }, }); }) .catch((error) => sendResponse({ success: false, error: error.message }), ); return true; } case MessageTypes.SETUP_PASSWORD: { const taskId = startBackgroundTask( "setup-password", sendToOffscreen({ type: MessageTypes.SETUP_PASSWORD, password: message.password, }).then((result) => { walletUnlocked = true; persistState(); resetAutoLockTimer(); return result; }), ); sendResponse({ success: true, result: { taskId } }); return false; } case MessageTypes.MARK_DEPLOYED: { chrome.storage.local .get(STORAGE_KEYS.ACCOUNTS) .then((data) => { const accounts = data[STORAGE_KEYS.ACCOUNTS] || []; const account = accounts.find( (a: any) => a.address === message.address, ); if (account) { account.isDeployed = true; return chrome.storage.local.set({ [STORAGE_KEYS.ACCOUNTS]: accounts, }); } }) .then(() => sendResponse({ success: true, result: { success: true } })) .catch((error) => sendResponse({ success: false, error: error.message }), ); return true; } case MessageTypes.CREATE_ACCOUNT: { const taskId = startBackgroundTask( "create-account", sendToOffscreen({ type: MessageTypes.CREATE_ACCOUNT, alias: message.alias, }), ); sendResponse({ success: true, result: { taskId } }); return false; } case MessageTypes.DEPLOY_ACCOUNT: { const taskId = startBackgroundTask( "deploy-account", sendToOffscreen({ type: MessageTypes.DEPLOY_ACCOUNT, address: message.address, }), ); sendResponse({ success: true, result: { taskId } }); return false; } case MessageTypes.EXPORT_WALLET: { const taskId = startBackgroundTask( "export-wallet", sendToOffscreen({ type: MessageTypes.EXPORT_WALLET }), ); sendResponse({ success: true, result: { taskId } }); return false; } case MessageTypes.IMPORT_WALLET: { // Wipe wallet data from chrome.storage.local and lock the wallet chrome.storage.local.remove([ STORAGE_KEYS.ACCOUNTS, STORAGE_KEYS.PASSWORD_DATA, STORAGE_KEYS.ACTIVE_ACCOUNT, ]); walletUnlocked = false; persistState(); // Tell offscreen to clear cached key sendToOffscreen({ type: MessageTypes.LOCK_WALLET }).catch(() => {}); sendResponse({ success: true, result: { success: true } }); return false; } case MessageTypes.IMPORT_WALLET_ACCOUNTS: { const taskId = startBackgroundTask( "import-wallet-accounts", sendToOffscreen({ type: MessageTypes.IMPORT_WALLET_ACCOUNTS, accounts: message.accounts, activeAccount: message.activeAccount, }).then((result) => { walletUnlocked = true; persistState(); resetAutoLockTimer(); return result; }), ); sendResponse({ success: true, result: { taskId } }); return false; } default: { log.warn("[background] Unknown message type:", message.type); return false; } } }); async function handleTransactionApproval( pending: PendingTransaction, ): Promise { try { const result = await sendToOffscreen({ type: MessageTypes.WALLET_METHOD, method: pending.method, args: pending.args, }); await handler.sendResponse(pending.sessionId, { messageId: pending.messageId, result, walletId: WALLET_CONFIG.walletId, }); return result; } catch (error: any) { log.error( "[background] Transaction approval failed:", pending.method, error, ); await handler.sendResponse(pending.sessionId, { messageId: pending.messageId, error: error.message, walletId: WALLET_CONFIG.walletId, }); throw error; } } ``` > [Source code: docs/examples/webapp-tutorial/test-extension/src/background.ts#L904-L1402](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/background.ts#L904-L1402) The `handler.approveDiscovery()` call: 1. Marks the discovery as approved 2. Sends a discovery response to the dApp with wallet info 3. Triggers the key exchange phase ## Key Exchange Phase[​](#key-exchange-phase "Direct link to Key Exchange Phase") After approval, the dApp initiates key exchange by sending its ECDH public key. The `BackgroundConnectionHandler` handles this automatically — the pseudocode below shows the conceptual flow, not code you write: ``` // Conceptual flow inside BackgroundConnectionHandler (from wallet SDK) async handleKeyExchangeRequest(sessionId, request) { // Generate our ECDH key pair const keyPair = await generateKeyPair(); const publicKey = await exportPublicKey(keyPair.publicKey); // Derive shared secret from their public key const appPublicKey = await importPublicKey(request.publicKey); const sessionKeys = await deriveSessionKeys(keyPair, appPublicKey, false); // Store session with shared encryption key const session = { sessionId, sharedKey: sessionKeys.encryptionKey, verificationHash: sessionKeys.verificationHash, // For emoji display // ... }; // Send our public key back this.transport.sendToTab(tabId, { type: 'KEY_EXCHANGE_RESPONSE', publicKey, }); } ``` The key exchange uses: * **ECDH** (Elliptic Curve Diffie-Hellman) for shared secret derivation * **AES-GCM** for subsequent message encryption * **Verification hash** that can be displayed as emojis for visual confirmation ## Emoji Verification[​](#emoji-verification "Direct link to Emoji Verification") The verification hash can be converted to emojis for users to confirm they're talking to the right wallet: ``` // Convert hash to emoji sequence function hashToEmojis(hash: string): string { const emojis = ['🔐', '🎮', '🚀', '⭐', '🎯', '💎', '🔥', '🌟']; return hash .slice(0, 8) .split('') .map((c) => emojis[parseInt(c, 16) % emojis.length]) .join(''); } ``` Both the dApp and wallet should display the same emoji sequence, confirming the connection is secure. ## Secure Messaging[​](#secure-messaging "Direct link to Secure Messaging") Once key exchange completes, all messages are encrypted: ``` // In BackgroundConnectionHandler async handleEncryptedMessage(sessionId, encrypted) { const session = this.activeSessions.get(sessionId); if (!session) return; // Decrypt using shared key const message = await decrypt(session.sharedKey, encrypted); // Call our handler this.callbacks.onWalletMessage?.(session, message); } async sendResponse(sessionId, response) { const session = this.activeSessions.get(sessionId); if (!session) return; // Encrypt response const encrypted = await encrypt(session.sharedKey, JSON.stringify(response)); // Send to content script this.transport.sendToTab(session.tabId, { type: 'SECURE_RESPONSE', sessionId, content: encrypted, }); } ``` ## Message Routing[​](#message-routing "Direct link to Message Routing") The extension checks whether a wallet method needs user approval before forwarding: ``` // sendTx always requires approval — it's a state-changing operation. // batch requires approval only if it contains a sendTx. // Read-only calls (getAccounts, simulateTx, executeUtility, etc.) auto-execute. const needsApproval = message.type === 'sendTx' || (message.type === 'batch' && Array.isArray(message.args?.[0]) && message.args[0].some((m: any) => m.name === 'sendTx')); ``` `requestCapabilities` has its own approval flow — the wallet stores it as pending and shows a capability grant prompt. For methods that don't need approval, the extension forwards directly to offscreen via a persistent port. The port uses `messageId`-based request/response correlation with a 5-minute timeout and automatic retry if the offscreen document is torn down by Chrome: send-to-offscreen ``` /** * Persistent port to the offscreen document. * Unlike chrome.runtime.sendMessage() (broadcast), a port gives us: * - Point-to-point channel (no broadcast to all extension pages) * - Automatic disconnect detection (offscreen teardown) * - No `return true`/`false` landmine for async responses */ let offscreenPort: chrome.runtime.Port | null = null; const pendingOffscreenCalls = new Map< string, { resolve: (value: any) => void; reject: (error: Error) => void; timer: ReturnType; } >(); let offscreenMessageId = 0; const OFFSCREEN_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes function connectOffscreenPort() { const port = chrome.runtime.connect({ name: "offscreen" }); offscreenPort = port; port.onMessage.addListener((message: any) => { // Progress updates — relay to popup if (message.type === "task-progress") { const runningTask = backgroundTasks.find((t) => t.status === "running"); if (runningTask) { runningTask.progress = message.stage; notifyPopup({ type: "task-update", task: { ...runningTask } }); } return; } // Request/response correlation const pending = pendingOffscreenCalls.get(message.messageId); if (!pending) return; pendingOffscreenCalls.delete(message.messageId); clearTimeout(pending.timer); if (message.success) { pending.resolve(message.result); } else { pending.reject(new Error(message.error || "Unknown error")); } }); port.onDisconnect.addListener(() => { log.debug("[background] Offscreen port disconnected"); offscreenPort = null; // Reject all pending calls — sendToOffscreen will retry for (const [id, pending] of pendingOffscreenCalls) { clearTimeout(pending.timer); pending.reject(new Error("Offscreen port disconnected")); pendingOffscreenCalls.delete(id); } }); } /** * Sends a message to the offscreen document and waits for response. * Uses a persistent port with request/response correlation via messageId. * Retries once if the offscreen document was torn down. (#15) */ async function sendToOffscreen(message: any, _retried = false): Promise { await ensureOffscreenDocument(); if (!offscreenPort) { connectOffscreenPort(); } const messageId = `off-${++offscreenMessageId}`; return new Promise((resolve, reject) => { const timer = setTimeout(() => { pendingOffscreenCalls.delete(messageId); reject(new Error(`Offscreen call timed out: ${message.type}`)); }, OFFSCREEN_TIMEOUT_MS); pendingOffscreenCalls.set(messageId, { resolve, reject, timer }); try { if (!offscreenPort) { throw new Error("Offscreen port not connected"); } offscreenPort.postMessage({ ...message, messageId }); } catch (err: unknown) { pendingOffscreenCalls.delete(messageId); clearTimeout(timer); // Port may have disconnected — retry once if (!_retried) { log.warn("[background] Offscreen port send failed, retrying..."); offscreenPort = null; offscreenCreating = null; sendToOffscreen(message, true).then(resolve, reject); } else { reject(err instanceof Error ? err : new Error(String(err))); } } }); } ``` > [Source code: docs/examples/webapp-tutorial/test-extension/src/background.ts#L72-L176](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/background.ts#L72-L176) For methods that need approval, the extension stores them as pending and waits for user action: ``` // Store pending transaction const pending = { sessionId: session.sessionId, messageId: message.messageId, method: message.type, args: message.args, from: message.args?.options?.from, origin: session.origin, timestamp: Date.now(), }; pendingTransactions.push(pending); updateBadge(); ``` ## Transport Implementation[​](#transport-implementation "Direct link to Transport Implementation") The transport bridges Chrome's messaging APIs: transport ``` const transport: BackgroundTransport = { sendToTab: (tabId, message) => { log.debug( "[background] sendToTab:", tabId, message.type, message.sessionId, ); chrome.tabs.sendMessage(tabId, message); }, addContentListener: (handler) => { chrome.runtime.onMessage.addListener((message, sender) => { // Skip targeted messages (popup, offscreen), storage proxy, and progress updates if (message.target) return; if (message.type === "storage-get" || message.type === "storage-set") return; log.debug( "[background] Content message received:", message.origin, message.type, "from tab:", sender.tab?.id, ); handler(message, { tab: sender.tab ? { id: sender.tab.id, url: sender.tab.url } : undefined, }); }); }, }; ``` > [Source code: docs/examples/webapp-tutorial/test-extension/src/background.ts#L499-L532](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/background.ts#L499-L532) Key points: * `sendToTab` uses `chrome.tabs.sendMessage` to reach content scripts * `addContentListener` uses `chrome.runtime.onMessage` to receive content script messages * The listener skips messages with a `target` field (those are popup → background) and storage proxy messages (those are offscreen → background via broadcast) ## Session Lifecycle[​](#session-lifecycle "Direct link to Session Lifecycle") Sessions are cleaned up automatically when: * **Page refresh** — `onPendingDiscovery` terminates stale sessions from the same tab before processing the new discovery * **Tab closed** — `chrome.tabs.onRemoved` calls `handler.terminateForTab()` to remove all sessions and discoveries for the tab * **User disconnects** — The popup sends `DISCONNECT_SESSION`, which also removes the origin from trusted origins ## Next Steps[​](#next-steps "Direct link to Next Steps") With the protocol in place, let's set up [PXE Integration](/developers/testnet/docs/tutorials/js_tutorials/wallet-extension/pxe-integration.md) - running a full Private eXecution Environment inside the extension. --- # Building a Webapp on Aztec In this tutorial you'll build a **Pod Racing** game webapp — a fully functional application where players privately allocate points across racing tracks, with their strategies hidden from opponents using Aztec's privacy features. ## What you'll build[​](#what-youll-build "Direct link to What you'll build") A two-player competitive game where: * Each player distributes up to 9 points across 5 racing tracks per round (3 rounds total) * Point allocations are **private** — stored as encrypted notes only you can read * After all rounds, players reveal their totals and a winner is determined (best of 5 tracks) * The app connects to a local Aztec network, optionally via browser extension wallet ### How the game works[​](#how-the-game-works "Direct link to How the game works") Each round, you distribute up to 9 points across 5 tracks (think of each track as an independent race). After 3 rounds, each player's per-track totals are compared: whoever allocated more points to a track wins that track. The overall winner is the player who wins the majority of the 5 tracks (best of 5). ### Why privacy matters[​](#why-privacy-matters "Direct link to Why privacy matters") Without privacy, your opponent could see your point allocations as you play and adjust their strategy to counter yours. Aztec keeps each player's allocations encrypted as private notes — your opponent only learns that you submitted a round, not how you distributed your points. Strategies are revealed only after both players finish, making the game fair. ## What you'll learn[​](#what-youll-learn "Direct link to What you'll learn") * Writing and compiling a Noir smart contract with private state * Deploying and interacting with the contract from a TypeScript script * Setting up a Vite + React project that runs Aztec's WASM modules in-browser * Connecting to Aztec via an **embedded wallet** (local dev) or the **wallet SDK** (browser extension) * Sending **private transactions** and reading **private state** * Paying transaction fees with the **SponsoredFPC** contract ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * [Node.js](https://nodejs.org/) v22 or later * The Aztec CLI installed (`aztec` command available) * A local Aztec network running (`aztec start --local-network`) * Basic familiarity with React and TypeScript ## Clone the example[​](#clone-the-example "Direct link to Clone the example") The tutorial walks through a complete working example. Clone it first, then follow along: ``` git clone https://github.com/AztecProtocol/aztec-packages.git cd aztec-packages git checkout v5.0.0-rc.2 cd docs/examples/webapp-tutorial ./setup.sh ``` ## Architecture[​](#architecture "Direct link to Architecture") ``` ┌──────────────────────────────────────────────────┐ │ Browser │ │ │ │ ┌──────────┐ ┌────────┐ ┌──────────────┐ │ │ │ React UI │───▸│ Wallet │───▸│ PXE (WASM) │ │ │ └──────────┘ └────────┘ └──────┬───────┘ │ │ │ │ └───────────────────────────────────────┼──────────┘ │ RPC ┌──────▼───────┐ │ Aztec Node │ │ (local or │ │ remote) │ └──────────────┘ ``` **PXE** (Private eXecution Environment) runs in the browser as WASM. It handles private state, note discovery, and proof generation — your secrets never leave the browser. ## Tutorial sections[​](#tutorial-sections "Direct link to Tutorial sections") 1. [The Contract](/developers/testnet/docs/tutorials/js_tutorials/webapp/the-contract.md) — understand the Pod Racing contract, compile it, deploy and interact via script 2. [Project Setup](/developers/testnet/docs/tutorials/js_tutorials/webapp/project-setup.md) — project structure overview, Vite config, environment 3. [Network & Wallet](/developers/testnet/docs/tutorials/js_tutorials/webapp/network-and-wallet.md) — connecting to Aztec, embedded wallet, wallet SDK 4. [Contract Interaction](/developers/testnet/docs/tutorials/js_tutorials/webapp/contract-interaction.md) — deploying and calling contracts from the webapp, game lobby and gameplay 5. [Transactions & Fees](/developers/testnet/docs/tutorials/js_tutorials/webapp/transactions-and-fees.md) — tx lifecycle, SponsoredFPC 6. [Putting It Together](/developers/testnet/docs/tutorials/js_tutorials/webapp/putting-it-together.md) — full App component, running the app 7. [Wallet SDK](/developers/testnet/docs/tutorials/js_tutorials/webapp/wallet-sdk.md) — deep dive into discovery, encrypted channels, capabilities, and integration on both dApp and wallet sides ## Completed example[​](#completed-example "Direct link to Completed example") The full working example including the contract source is available at [`docs/examples/webapp-tutorial/`](https://github.com/AztecProtocol/aztec-packages/tree/v5.0.0-rc.2/docs/examples/webapp-tutorial). The example also includes a **functional wallet extension** (`test-extension/`) that can deploy accounts and send real transactions using SponsoredFPC for fee payment. See the [Network & Wallet](/developers/testnet/docs/tutorials/js_tutorials/webapp/network-and-wallet.md#testing-with-the-tutorial-wallet-extension) section for setup instructions. Building a Wallet Extension? If you want to learn how to build a browser extension wallet for Aztec, check out the companion [Wallet Extension Tutorial](/developers/testnet/docs/tutorials/js_tutorials/wallet-extension.md). It covers service workers, offscreen documents, encrypted key storage, and transaction approval flows. --- # Contract Interaction & Gameplay Now that you have a wallet, you can deploy the Pod Racing contract and interact with it. This section covers the contract helper functions, the game lobby, and the gameplay components that handle private state. ## Contract helpers[​](#contract-helpers "Direct link to Contract helpers") Before your PXE (Private eXecution Environment) can interact with a contract, it needs two pieces of information: the **artifact** (the compiled contract bytecode and ABI) and the **instance** (the deployed address and constructor parameters). Without these, PXE cannot construct proofs or route transactions to the correct contract. Open [`src/contract.ts`](https://github.com/AztecProtocol/aztec-packages/tree/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/contract.ts). This file imports the generated `PodRacingContract` class from the compiled artifacts and wraps each contract method in a simple async function that the UI components call. The key functions are described below. ### Deploying a new contract[​](#deploying-a-new-contract "Direct link to Deploying a new contract") The `deployContract` function calls `PodRacingContract.deploy()` to construct a deployment request and `.send()` to submit it to the network, wait for it to be mined, and return a typed contract instance you can call methods on. The deploy flow handles artifact registration with PXE internally. ### Attaching to an existing contract[​](#attaching-to-an-existing-contract "Direct link to Attaching to an existing contract") When joining someone else's game, you didn't deploy the contract, so your PXE doesn't know about it. `attachToContract` first registers the contract with your PXE by fetching the onchain instance from the node and providing the compiled artifact — this is required for private function execution, since PXE needs the contract bytecode locally to generate proofs. It then calls `PodRacingContract.at()` to create a typed contract handle bound to the existing contract address and wallet. ### Game actions[​](#game-actions "Direct link to Game actions") Each function maps to a step in the game lifecycle: * `createGame(gameId)` — **public**. Creates a new `Race` struct in public storage with the caller as player 1 and sets a block deadline. * `joinGame(gameId)` — **public**. Updates the `Race` to add the caller as player 2. The game is now active. * `playRound(gameId, round, tracks)` — **private**. Creates a `GameRoundNote` storing your point allocation for this round. The note is encrypted and only you can read it. Enqueues a public call to increment your round counter so your opponent can see you've completed a round (without seeing your points). * `finishGame(gameId)` — **private**. Reads all your `GameRoundNote`s, sums up totals per track, and publishes the aggregated scores to public state. This is the "reveal" phase. * `finalizeGame(gameId)` — **public**. Compares both players' track totals, declares the winner (best of 5 tracks), and updates the win history. ## Game lobby component[​](#game-lobby-component "Direct link to Game lobby component") The lobby handles two-player coordination. Player 1 deploys a new contract and creates a game, then shares the contract address with their opponent. Player 2 pastes that address, attaches to the existing contract, and joins the game. Open `src/components/GameLobby.tsx`: game-lobby-imports ``` import React, { useState } from 'react'; import { AztecAddress } from '@aztec/aztec.js/addresses'; import type { Wallet } from '@aztec/aztec.js/wallet'; import type { PodRacingContract } from '../artifacts/PodRacing'; import { deployContract, createGame, joinGame, attachToContract } from '../contract'; import { useTransactionLog } from './TransactionLog'; interface GameLobbyProps { wallet: Wallet; account: AztecAddress; onGameJoined: (contract: PodRacingContract, gameId: bigint) => void; } ``` > [Source code: docs/examples/webapp-tutorial/src/components/GameLobby.tsx#L1-L14](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/components/GameLobby.tsx#L1-L14) ### Creating a game[​](#creating-a-game "Direct link to Creating a game") handle-create ``` async function handleCreateGame() { setIsCreating(true); setStatus('Deploying Pod Racing contract...'); addLog('Starting contract deployment...', 'pending'); try { let gId: bigint; try { gId = BigInt(gameId); if (gId <= 0n) throw new Error('must be positive'); } catch { setStatus('Invalid game ID — enter a positive integer'); setIsCreating(false); return; } addLog('Compiling and sending deployment transaction...', 'pending'); const contract = await deployContract(wallet, account); addLog(`Contract deployed at ${contract.address.toString()}`, 'success'); setStatus('Creating game...'); addLog('Creating game...', 'pending'); const receipt = await createGame(contract, account, gId); addLog(`Game ${gId} created successfully`, 'success', receipt.receipt.txHash?.toString()); setStatus(`Game created! Share contract address: ${contract.address}`); onGameJoined(contract, gId); } catch (err: unknown) { const msg = err instanceof Error ? err.message : String(err); setStatus(`Error: ${msg}`); addLog(`Error: ${msg}`, 'error'); } finally { setIsCreating(false); } } ``` > [Source code: docs/examples/webapp-tutorial/src/components/GameLobby.tsx#L25-L60](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/components/GameLobby.tsx#L25-L60) This deploys a fresh contract and creates a game in a single flow. The contract address is displayed so the creator can copy and share it with an opponent. ### Joining a game[​](#joining-a-game "Direct link to Joining a game") handle-join ``` async function handleJoinGame() { if (!joinContractAddress || !joinGameIdInput) { setStatus('Enter contract address and game ID'); return; } setIsJoining(true); setStatus('Joining game...'); addLog('Attaching to existing contract...', 'pending'); try { let gId: bigint; try { gId = BigInt(joinGameIdInput); if (gId <= 0n) throw new Error('must be positive'); } catch { setStatus('Invalid game ID — enter a positive integer'); setIsJoining(false); return; } const contractAddr = AztecAddress.fromStringUnsafe(joinContractAddress); const contract = await attachToContract( wallet, contractAddr ); addLog(`Attached to contract ${contractAddr.toString()}`, 'info'); addLog(`Joining game ${gId}...`, 'pending'); const receipt = await joinGame(contract, account, gId); addLog(`Joined game ${gId} successfully`, 'success', receipt.receipt.txHash?.toString()); setStatus('Joined game!'); onGameJoined(contract, gId); } catch (err: unknown) { const msg = err instanceof Error ? err.message : String(err); setStatus(`Error: ${msg}`); addLog(`Error joining game: ${msg}`, 'error'); } finally { setIsJoining(false); } } ``` > [Source code: docs/examples/webapp-tutorial/src/components/GameLobby.tsx#L62-L102](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/components/GameLobby.tsx#L62-L102) The opponent pastes the contract address and game ID to join. Under the hood, this calls `attachToContract` to register the contract with the joiner's PXE, then sends a `join_game` transaction. ## The game board component[​](#the-game-board-component "Direct link to The game board component") Open [`src/components/GameBoard.tsx`](https://github.com/AztecProtocol/aztec-packages/tree/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/components/GameBoard.tsx). The board lets you allocate points across 5 tracks each round. There is a constraint: your total points per round must sum to less than 10 (i.e., at most 9 points). This forces strategic trade-offs — you can't dominate every track. ### Submitting a round (private transaction)[​](#submitting-a-round-private-transaction "Direct link to Submitting a round (private transaction)") submit-round ``` async function handleSubmitRound() { if (total >= 10) { setStatus(`Points must sum to less than 10 (currently ${total})`); return; } setLoading(true); setStatus('Submitting your allocation (private transaction)...'); addLog(`Round ${currentRound}: Submitting allocation [${allocations.join(', ')}]...`, 'pending'); try { addLog('Building private transaction proof...', 'pending'); const receipt = await playRound(contract, account, gameId, currentRound, allocations); addLog(`Round ${currentRound} submitted successfully`, 'success', receipt.receipt.txHash?.toString()); setStatus('Round submitted!'); setAllocations([2, 2, 2, 2, 1]); onRoundPlayed(); } catch (err: unknown) { const msg = err instanceof Error ? err.message : String(err); setStatus(`Error: ${msg}`); addLog(`Error submitting round: ${msg}`, 'error'); } finally { setLoading(false); } } ``` > [Source code: docs/examples/webapp-tutorial/src/components/GameBoard.tsx#L36-L61](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/components/GameBoard.tsx#L36-L61) When the `playRound` helper sends the transaction (via `.send()`), the following happens under the hood: 1. PXE executes the private function locally, creating a `GameRoundNote` 2. A ZK proof is generated (proving validity without revealing inputs) 3. The proof and encrypted note are sent to the network 4. The network validates the proof and includes the transaction ### Finishing and finalizing[​](#finishing-and-finalizing "Direct link to Finishing and finalizing") After all 3 rounds, the game has two more phases: finish-and-finalize ``` async function handleFinishGame() { setLoading(true); setStatus('Revealing your total scores...'); addLog('Revealing scores (finish_game)...', 'pending'); try { addLog('Reading private notes and computing totals...', 'pending'); const receipt = await finishGame(contract, account, gameId); addLog('Scores revealed successfully', 'success', receipt.receipt.txHash?.toString()); setStatus('Scores revealed! Waiting for opponent to reveal, then finalize.'); } catch (err: unknown) { const msg = err instanceof Error ? err.message : String(err); setStatus(`Error: ${msg}`); addLog(`Error revealing scores: ${msg}`, 'error'); } finally { setLoading(false); } } async function handleFinalizeGame() { setLoading(true); setStatus('Determining winner...'); addLog('Finalizing game and determining winner...', 'pending'); try { const receipt = await finalizeGame(contract, account, gameId); addLog('Game finalized! Winner determined.', 'success', receipt.receipt.txHash?.toString()); setStatus('Game finalized! Winner determined.'); } catch (err: unknown) { const msg = err instanceof Error ? err.message : String(err); setStatus(`Error: ${msg}`); addLog(`Error finalizing game: ${msg}`, 'error'); } finally { setLoading(false); } } ``` > [Source code: docs/examples/webapp-tutorial/src/components/GameBoard.tsx#L63-L98](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/components/GameBoard.tsx#L63-L98) * **`finish_game`**: A private function that reads all your `GameRoundNote`s, sums up totals per track, and publishes the aggregated scores. This is the "reveal" — your per-round choices stay hidden, but your final totals become public. * **`finalize_game`**: A public function that compares both players' totals track by track and declares the winner (best of 5 tracks). Can only be called after the game's block deadline. Why two separate phases? Both players must call `finish_game` before anyone can call `finalize_game`. This ensures neither player can see the other's totals before committing their own. The block deadline adds a time constraint: the game must reach a certain block number before finalization, preventing a player from waiting indefinitely. ## Game status display[​](#game-status-display "Direct link to Game status display") Open `src/components/GameStatus.tsx`: game-status-component ``` /** * Displays the current game status. * * In the Pod Racing contract, round progress is tracked publicly * (which round each player is on), but point allocations are private. * The currentRound is tracked locally in React state and incremented * after each successful play_round transaction. */ export function GameStatus({ account, gameId, currentRound }: GameStatusProps) { const addr = account.toString(); const display = `${addr.slice(0, 10)}...${addr.slice(-6)}`; return (

Game Status

Game ID: {gameId.toString()}

Playing as: {display}

Current Round: {currentRound} / 3

Your point allocations are stored as private notes. Opponents cannot see your strategy until you reveal scores.

); } ``` > [Source code: docs/examples/webapp-tutorial/src/components/GameStatus.tsx#L10-L36](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/components/GameStatus.tsx#L10-L36) ## Why opponents can't see your allocations[​](#why-opponents-cant-see-your-allocations "Direct link to Why opponents can't see your allocations") Private functions execute in **your** PXE using **your** decryption keys. Your opponent's PXE doesn't have your keys, so it can't decrypt your `GameRoundNote`s. This is Aztec's privacy model: private state is truly private by construction. The only public information during gameplay is each player's round counter (which round they're on). The actual point allocations are only revealed when a player calls `finish_game`. ## Next steps[​](#next-steps "Direct link to Next steps") Continue to [how transactions and fee payment work](/developers/testnet/docs/tutorials/js_tutorials/webapp/transactions-and-fees.md). --- # Network & Wallet This section covers connecting your webapp to the Aztec network and setting up a wallet. You'll walk through three source files (`config.ts`, `embedded-wallet.ts`, `wallet-connection.ts`) plus shared fee utilities and UI components. ## Key concepts[​](#key-concepts "Direct link to Key concepts") Before looking at code, it helps to understand two pieces of infrastructure that every Aztec app relies on: **[PXE (Private eXecution Environment)](/developers/testnet/docs/foundational-topics/pxe.md)** is a client-side runtime that runs in the browser as WASM. It stores your private notes, manages your encryption keys, executes private functions, and generates zero-knowledge proofs. Because PXE runs locally, your private data never leaves the browser. Every Aztec app needs a PXE — either one it creates itself or one provided by a wallet extension. **Aztec node** is the server-side component that maintains the network's public state and sequences transactions into blocks. Your PXE connects to a node (a local network during development, or a remote node in production) to sync state and submit transactions. The node never sees your private data — it only receives proofs and encrypted outputs. The relationship is straightforward: PXE handles everything private (notes, keys, proofs), the node handles everything public (state, blocks, sequencing), and they communicate over a standard RPC interface. ## Network configuration[​](#network-configuration "Direct link to Network configuration") Open `src/config.ts`. This determines which Aztec node to connect to and provides a helper for creating an in-browser PXE: config ``` import { createAztecNodeClient } from "@aztec/aztec.js/node"; import { getPXEConfig } from "@aztec/pxe/config"; import { createPXE } from "@aztec/pxe/client/lazy"; export type NetworkType = "local" | "remote"; export function getNodeUrl(network: NetworkType): string { if (network === "local") { return process.env.AZTEC_NODE_URL || "http://localhost:8080"; } // For remote networks, the wallet extension manages the node connection return process.env.AZTEC_NODE_URL || "http://localhost:8080"; } /** * Creates an in-browser PXE instance connected to an Aztec node. * PXE (Private eXecution Environment) runs locally and handles * private state, note discovery, and transaction creation. */ export async function createLocalPXE(nodeUrl: string) { const aztecNode = createAztecNodeClient(nodeUrl); const config = getPXEConfig(); const isLocal = nodeUrl.includes("localhost") || nodeUrl.includes("127.0.0.1"); config.proverEnabled = !isLocal; const pxe = await createPXE(aztecNode, config, {}); console.log("PXE connected to node at:", nodeUrl); return { pxe, aztecNode }; } ``` > [Source code: docs/examples/webapp-tutorial/src/config.ts#L1-L32](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/config.ts#L1-L32) `createLocalPXE` sets up PXE in three steps: 1. `createAztecNodeClient` — opens an RPC connection to the Aztec node so PXE can sync public state and submit transactions. 2. `getPXEConfig` + `getL1ContractAddresses` — fetches protocol configuration from the node, including L1 contract addresses and network parameters that PXE needs to construct valid proofs. 3. `createPXE` — starts a full PXE instance in the browser. From this point on, all private execution happens locally. ## Wallet modes[​](#wallet-modes "Direct link to Wallet modes") Aztec supports two wallet modes, and the app implements both: * **Embedded wallet** — your app creates PXE and manages accounts directly. Best for local development with the local network. * **Wallet SDK** — your app connects to an external browser extension that owns PXE and accounts. Required for production. Both modes produce the same `Wallet` interface, so the rest of your app doesn't need to know which one is in use. ## Embedded wallet (local development)[​](#embedded-wallet-local-development "Direct link to Embedded wallet (local development)") For local development, the app uses a custom `EmbeddedWallet` class that extends the official `EmbeddedWallet` from `@aztec/wallets/embedded`. The official wallet already provides account creation and persistence, transaction sending with gas estimation, automatic authwitness generation, and stub-account simulation. The tutorial subclass adds one thing: **SponsoredFPC fee payment** so users don't need to hold fee tokens. Open `src/embedded-wallet.ts`: ### Imports[​](#imports "Direct link to Imports") embedded-wallet-imports ``` import { NO_FROM } from "@aztec/aztec.js/account"; import { AztecAddress } from "@aztec/aztec.js/addresses"; import { getContractInstanceFromInstantiationParams } from "@aztec/aztec.js/contracts"; import { SponsoredFeePaymentMethod } from "@aztec/aztec.js/fee"; import { Fr } from "@aztec/aztec.js/fields"; import { SPONSORED_FPC_SALT } from "@aztec/constants"; import { AccountFeePaymentMethodOptions } from "@aztec/entrypoints/account"; import { getInitialTestAccountsData } from "@aztec/accounts/testing/lazy"; import type { ContractArtifact } from "@aztec/stdlib/abi"; import { type CompleteFeeOptionsConfig, type FeeOptions, } from "@aztec/wallet-sdk/base-wallet"; import { EmbeddedWallet as BaseEmbeddedWallet } from "@aztec/wallets/embedded"; ``` > [Source code: docs/examples/webapp-tutorial/src/embedded-wallet.ts#L1-L16](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/embedded-wallet.ts#L1-L16) ### Initialization[​](#initialization "Direct link to Initialization") initialize ``` /** * Creates a new EmbeddedWallet connected to the given Aztec node URL. * Sets up an in-browser PXE and registers the SponsoredFPC contract. */ static async initialize(nodeUrl: string) { const isLocal = nodeUrl.includes("localhost") || nodeUrl.includes("127.0.0.1"); const wallet = await EmbeddedWallet.create(nodeUrl, { ephemeral: true, pxeConfig: { proverEnabled: !isLocal }, }); // Register SponsoredFPC so we can pay fees const fpc = await EmbeddedWallet.#getSponsoredFPCContract(); await wallet.registerContract(fpc.instance, fpc.artifact); return wallet; } ``` > [Source code: docs/examples/webapp-tutorial/src/embedded-wallet.ts#L61-L80](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/embedded-wallet.ts#L61-L80) The `initialize` factory calls the inherited `create()` method, which sets up an in-browser PXE and account storage. It then registers the SponsoredFPC contract with PXE so that fee payment works out of the box. ### Connecting a test account[​](#connecting-a-test-account "Direct link to Connecting a test account") The local network ships with pre-deployed test accounts. These are Schnorr-signature accounts that are already registered and funded, so you can use them immediately without deploying a new account contract. The inherited `createSchnorrAccount` handles account creation, contract registration with PXE, and persistence in the wallet database. You select one by index (0, 1, 2, etc.). connect-test-account ``` /** * Connects one of the pre-deployed test accounts available on the local network. * Uses the inherited createSchnorrAccount which handles account creation, * contract registration, and WalletDB persistence. */ async connectTestAccount(index: number) { const testAccounts = await getInitialTestAccountsData(); const accountData = testAccounts[index]; const accountManager = await this.createSchnorrAccount( accountData.secret, accountData.salt, accountData.signingKey, ); this.connectedAccount = accountManager.address; return this.connectedAccount; } ``` > [Source code: docs/examples/webapp-tutorial/src/embedded-wallet.ts#L97-L116](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/embedded-wallet.ts#L97-L116) ### Fee payment[​](#fee-payment "Direct link to Fee payment") Every Aztec transaction must pay a fee (similar to gas on Ethereum). Rather than requiring users to hold fee tokens during development, the embedded wallet overrides `completeFeeOptions` to inject SponsoredFPC as the default fee payer for every transaction. Callers never need to pass fee options manually. note SponsoredFPC only works on the local network. Production Aztec networks deployed to Ethereum mainnet require an alternative fee payment strategy. fee-options ``` /** * Uses SponsoredFPC for fee payment by default, so users * don't need to hold fee tokens. */ override async completeFeeOptions( config: CompleteFeeOptionsConfig, ): Promise { const feeOptions = await super.completeFeeOptions(config); if (config.feePayer) { return feeOptions; } const fpc = await EmbeddedWallet.#getSponsoredFPCContract(); return { ...feeOptions, walletFeePaymentMethod: new SponsoredFeePaymentMethod( fpc.instance.address, ), accountFeePaymentMethodOptions: config.from !== NO_FROM ? AccountFeePaymentMethodOptions.EXTERNAL : feeOptions.accountFeePaymentMethodOptions, }; } ``` > [Source code: docs/examples/webapp-tutorial/src/embedded-wallet.ts#L33-L59](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/embedded-wallet.ts#L33-L59) ### Full class[​](#full-class "Direct link to Full class") The complete `EmbeddedWallet` — most of the heavy lifting (simulation, proving, gas estimation) is inherited from the official wallet: embedded-wallet-class ``` /** * A tutorial wallet for local development. * Extends the official EmbeddedWallet to add SponsoredFPC fee payment * so users don't need to hold fee tokens. * * Inherits from the SDK's EmbeddedWallet which provides: * - Account creation and persistence via WalletDB * - Pre-simulation with gas estimation in sendTx * - Automatic authwitness generation * - Stub-account simulation (no expensive kernel proving) */ export class EmbeddedWallet extends BaseEmbeddedWallet { connectedAccount: AztecAddress | null = null; /** * Uses SponsoredFPC for fee payment by default, so users * don't need to hold fee tokens. */ override async completeFeeOptions( config: CompleteFeeOptionsConfig, ): Promise { const feeOptions = await super.completeFeeOptions(config); if (config.feePayer) { return feeOptions; } const fpc = await EmbeddedWallet.#getSponsoredFPCContract(); return { ...feeOptions, walletFeePaymentMethod: new SponsoredFeePaymentMethod( fpc.instance.address, ), accountFeePaymentMethodOptions: config.from !== NO_FROM ? AccountFeePaymentMethodOptions.EXTERNAL : feeOptions.accountFeePaymentMethodOptions, }; } /** * Creates a new EmbeddedWallet connected to the given Aztec node URL. * Sets up an in-browser PXE and registers the SponsoredFPC contract. */ static async initialize(nodeUrl: string) { const isLocal = nodeUrl.includes("localhost") || nodeUrl.includes("127.0.0.1"); const wallet = await EmbeddedWallet.create(nodeUrl, { ephemeral: true, pxeConfig: { proverEnabled: !isLocal }, }); // Register SponsoredFPC so we can pay fees const fpc = await EmbeddedWallet.#getSponsoredFPCContract(); await wallet.registerContract(fpc.instance, fpc.artifact); return wallet; } static async #getSponsoredFPCContract() { const { SponsoredFPCContractArtifact } = await import( "@aztec/noir-contracts.js/SponsoredFPC" ); const instance = await getContractInstanceFromInstantiationParams( SponsoredFPCContractArtifact, { salt: new Fr(SPONSORED_FPC_SALT) }, ); return { instance, artifact: SponsoredFPCContractArtifact }; } getConnectedAccount() { return this.connectedAccount; } /** * Connects one of the pre-deployed test accounts available on the local network. * Uses the inherited createSchnorrAccount which handles account creation, * contract registration, and WalletDB persistence. */ async connectTestAccount(index: number) { const testAccounts = await getInitialTestAccountsData(); const accountData = testAccounts[index]; const accountManager = await this.createSchnorrAccount( accountData.secret, accountData.salt, accountData.signingKey, ); this.connectedAccount = accountManager.address; return this.connectedAccount; } /** * Fetches a contract instance from the Aztec node (onchain) and registers it * with this wallet's PXE. Required before calling private functions on contracts * deployed by another wallet/PXE. */ async registerContractFromNode( address: AztecAddress, artifact: ContractArtifact, ) { const instance = await this.aztecNode.getContract(address); if (!instance) { throw new Error(`Contract not found onchain at ${address}`); } await this.registerContract(instance, artifact); } ``` > [Source code: docs/examples/webapp-tutorial/src/embedded-wallet.ts#L18-L133](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/embedded-wallet.ts#L18-L133) ## Wallet SDK (browser extension)[​](#wallet-sdk-browser-extension "Direct link to Wallet SDK (browser extension)") Users may connect via a browser extension wallet (like MetaMask on Ethereum). The wallet extension owns the PXE, manages keys, and signs transactions. Your app communicates with it through the **wallet SDK**, which handles discovery, secure channel setup, and verification. ### Testing with the tutorial wallet extension[​](#testing-with-the-tutorial-wallet-extension "Direct link to Testing with the tutorial wallet extension") The tutorial includes a fully functional wallet extension in `test-extension/`. Unlike a mock extension, this wallet can: * Create and store encrypted accounts * Deploy account contracts using SponsoredFPC (no fee tokens needed) * Sign and submit real transactions * Show approval popups for connections and transactions To use it: 1. Build the extension (from the `webapp-tutorial` directory): ``` node esbuild.extension.mjs ``` 2. Load it in Chrome: * Navigate to `chrome://extensions/` * Enable "Developer mode" (toggle in top-right) * Click "Load unpacked" * Select the `test-extension/` folder 3. Set up the wallet: * Click the extension icon * Create a master password on the setup screen * Create your first account (enter an alias) * Click "Deploy" to deploy the account contract 4. After making changes to the extension source, rebuild and click the refresh icon in `chrome://extensions/` Learn How It Works Want to build your own wallet extension? The [Wallet Extension Tutorial](/developers/testnet/docs/tutorials/js_tutorials/wallet-extension.md) explains the architecture and implementation in detail, covering service workers, offscreen documents, encrypted storage, and approval flows. Open `src/wallet-connection.ts`: ### Step 1: Discover available wallets[​](#step-1-discover-available-wallets "Direct link to Step 1: Discover available wallets") Your app needs to find which wallet extensions the user has installed. The SDK does this through a `window.postMessage`-based discovery protocol: your app broadcasts a discovery request, and any installed wallet extension responds with its provider info (name, icon, supported chain). The `discoverWallets` function starts this process and calls your `onUpdate` callback each time a new wallet extension responds. You use the resulting list to show users a "pick your wallet" UI. discover-wallets ``` /** * Starts discovering available wallet extensions. * Wallet extensions broadcast their availability via window.postMessage. * Returns a cancel function and calls onUpdate with each discovered wallet. */ export function discoverWallets( chainId: number, appId: string, onUpdate: (providers: WalletProvider[]) => void ): { cancel: () => void; done: Promise } { const manager = WalletManager.configure({ extensions: { enabled: true }, }); const providers: WalletProvider[] = []; const discovery = manager.getAvailableWallets({ chainInfo: { chainId: new Fr(chainId), version: new Fr(1), }, appId, onWalletDiscovered: (provider) => { // Deduplicate by wallet ID (StrictMode or remounts can cause duplicate discoveries) if (providers.some(p => p.id === provider.id)) { return; } providers.push(provider); onUpdate([...providers]); }, }); return { cancel: () => discovery.cancel(), done: discovery.done, }; } ``` > [Source code: docs/examples/webapp-tutorial/src/wallet-connection.ts#L26-L64](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/wallet-connection.ts#L26-L64) ### Step 2: Connect and verify[​](#step-2-connect-and-verify "Direct link to Step 2: Connect and verify") Once the user picks a wallet, you need to establish a secure communication channel. This is important because `window.postMessage` is visible to every script on the page — without encryption, a malicious script could intercept private data flowing between your app and the wallet. The connection uses an **ECDH key exchange**: your app and the wallet extension each generate an ephemeral key pair and derive a shared secret. All subsequent messages are encrypted with this shared secret. To guard against man-in-the-middle attacks (where a malicious script intercepts the key exchange and substitutes its own keys), the SDK produces a verification hash that gets converted to a short **emoji string**. Your app displays these emojis, and the user checks that their wallet extension shows the same emojis. If they match, the channel is secure. If they don't, the connection should be rejected. After the user confirms the emojis match, calling `confirm()` completes the handshake and returns a `Wallet` instance connected to the extension. connect-wallet ``` /** * Connects to a discovered wallet provider. * This establishes a secure encrypted channel using ECDH key exchange. * The returned emojis should be shown to the user for verification. */ export async function connectToProvider( provider: WalletProvider, appId: string ): Promise<{ emojis: string; confirm: () => Promise; cancel: () => void; }> { console.log('[wallet-connection] Calling establishSecureChannel for provider:', provider.name); const pending = await provider.establishSecureChannel(appId); console.log('[wallet-connection] Secure channel established, verificationHash:', pending.verificationHash); const emojis = hashToEmoji(pending.verificationHash); console.log('[wallet-connection] Emojis:', emojis); return { emojis, confirm: () => pending.confirm(), cancel: () => pending.cancel(), }; } ``` > [Source code: docs/examples/webapp-tutorial/src/wallet-connection.ts#L66-L92](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/wallet-connection.ts#L66-L92) ## Fee payment helpers[​](#fee-payment-helpers "Direct link to Fee payment helpers") Both wallet modes need access to the SponsoredFPC contract. **SponsoredFPC** (Fee Payment Contract) is a special contract deployed at a well-known deterministic address that agrees to pay transaction fees on behalf of any caller. It's available on the local network, making it useful for onboarding users who don't yet have fee tokens. Open `src/fees.ts`: get-sponsored-fpc ``` /** * Returns the SponsoredFPC contract details. * The SponsoredFPC (Fee Payment Contract) pays transaction fees on behalf of users. * This is deployed at a well-known address derived from a fixed salt. */ export async function getSponsoredFPCContract() { const { SponsoredFPCContractArtifact } = await import( '@aztec/noir-contracts.js/SponsoredFPC' ); const instance = await getContractInstanceFromInstantiationParams( SponsoredFPCContractArtifact, { salt: new Fr(SPONSORED_FPC_SALT) } ); return { instance, artifact: SponsoredFPCContractArtifact }; } ``` > [Source code: docs/examples/webapp-tutorial/src/fees.ts#L8-L24](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/fees.ts#L8-L24) PXE needs the SponsoredFPC artifact registered so it can include fee payment logic when constructing transaction proofs. Without this registration, PXE wouldn't know how to interact with the fee contract: register-fpc ``` /** * Registers the SponsoredFPC contract with PXE so it can be used for fee payment. * This must be called before sending any transactions. */ export async function registerSponsoredFPC(pxe: PXE) { const contract = await getSponsoredFPCContract(); await pxe.registerContract(contract); return contract.instance.address; } ``` > [Source code: docs/examples/webapp-tutorial/src/fees.ts#L26-L36](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/fees.ts#L26-L36) ## Components[​](#components "Direct link to Components") With the wallet logic in place, three UI components wire it together. ### Network picker (`src/components/NetworkPicker.tsx`)[​](#network-picker-srccomponentsnetworkpickertsx "Direct link to network-picker-srccomponentsnetworkpickertsx") Lets the user choose between "Local" (embedded wallet) and "Browser Wallet" (wallet extension). Calls `onNetworkChange` with the selected `NetworkType`, which determines which wallet mode the app uses. network-picker ``` import React from 'react'; import type { NetworkType } from '../config'; interface NetworkPickerProps { network: NetworkType; onNetworkChange: (network: NetworkType) => void; disabled?: boolean; } /** * Toggle between local network (uses EmbeddedWallet) and remote (uses wallet extension). */ export function NetworkPicker({ network, onNetworkChange, disabled }: NetworkPickerProps) { return (
); } ``` > [Source code: docs/examples/webapp-tutorial/src/components/NetworkPicker.tsx#L1-L29](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/components/NetworkPicker.tsx#L1-L29) ### Wallet connect (`src/components/WalletConnect.tsx`)[​](#wallet-connect-srccomponentswalletconnecttsx "Direct link to wallet-connect-srccomponentswalletconnecttsx") Handles both wallet modes in a single component. For local networks it shows the embedded wallet flow (pick a test account, connect). For the browser wallet it runs the SDK discovery and verification flow. Once connected, it calls `onWalletConnected` with the `Wallet` instance. Open `src/components/WalletConnect.tsx`: wallet-connect-imports ``` import React, { useState, useEffect } from 'react'; import type { Wallet, GrantedAccountsCapability } from '@aztec/aztec.js/wallet'; import type { WalletProvider } from '@aztec/wallet-sdk/manager'; import type { NetworkType } from '../config'; import { EmbeddedWallet } from '../embedded-wallet'; import { discoverWallets, connectToProvider, getAppCapabilities } from '../wallet-connection'; import { getNodeUrl } from '../config'; import { useTransactionLog } from './TransactionLog'; interface WalletConnectProps { network: NetworkType; onWalletConnected: (wallet: Wallet | EmbeddedWallet) => void; } ``` > [Source code: docs/examples/webapp-tutorial/src/components/WalletConnect.tsx#L1-L15](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/components/WalletConnect.tsx#L1-L15) wallet-connect-component ``` export function WalletConnect({ network, onWalletConnected }: WalletConnectProps) { const [status, setStatus] = useState(''); const [providers, setProviders] = useState([]); const [verificationEmojis, setVerificationEmojis] = useState(null); const [testAccountIndex, setTestAccountIndex] = useState(0); const [loading, setLoading] = useState(false); const [connected, setConnected] = useState(false); const [discoveryDone, setDiscoveryDone] = useState(false); const { addLog } = useTransactionLog(); /** Connect using the embedded wallet with a pre-deployed test account */ async function connectLocal() { setLoading(true); setStatus('Initializing PXE (this may take a moment)...'); addLog('Initializing local PXE client...', 'pending'); try { const nodeUrl = getNodeUrl('local'); addLog(`Connecting to node at ${nodeUrl}`, 'info'); const wallet = await EmbeddedWallet.initialize(nodeUrl); addLog('PXE initialized successfully', 'success'); setStatus('Connecting test account...'); addLog(`Connecting test account #${testAccountIndex + 1}...`, 'pending'); await wallet.connectTestAccount(testAccountIndex); addLog(`Test account #${testAccountIndex + 1} connected`, 'success'); setStatus('Connected!'); onWalletConnected(wallet); } catch (err: unknown) { const msg = err instanceof Error ? err.message : String(err); setStatus(`Error: ${msg}`); addLog(`Connection error: ${msg}`, 'error'); } finally { setLoading(false); } } /** Discover and connect to a browser extension wallet */ useEffect(() => { if (network !== 'remote') return; setStatus('Discovering wallet extensions...'); setDiscoveryDone(false); const { cancel, done } = discoverWallets(31337, 'pod-racing', (found) => { setProviders(found); setStatus(`Found ${found.length} wallet(s)`); }); let isMounted = true; done.then(() => { if (isMounted) setDiscoveryDone(true); }).catch((err) => { if (isMounted) setStatus(`Discovery error: ${err.message}`); }); return () => { isMounted = false; cancel(); }; }, [network]); async function connectExtension(provider: WalletProvider) { setLoading(true); setStatus('Establishing secure channel...'); try { const { emojis, confirm } = await connectToProvider( provider, 'pod-racing' ); // Show emojis for reference — the wallet extension is the authority // that verifies the emojis match. confirm() is a local operation // that creates the ExtensionWallet proxy (no message sent to extension). setVerificationEmojis(emojis); setStatus('Verify these emojis match in the wallet extension, then approve there.'); const wallet = await confirm(); // Request capabilities — the dApp declares all permissions it needs upfront. // The extension shows an approval dialog; this call blocks until the user approves. setStatus('Requesting permissions from wallet extension...'); const manifest = getAppCapabilities(); const capabilities = await wallet.requestCapabilities(manifest); setVerificationEmojis(null); console.log('[WalletConnect] Granted capabilities:', capabilities); // Check if accounts were granted const accountsCap = capabilities.granted.find( (c): c is GrantedAccountsCapability => c.type === 'accounts' ); if (!accountsCap?.accounts?.length) { setStatus('No accounts granted. Please approve the capabilities request in the wallet extension.'); setLoading(false); return; } setConnected(true); setStatus('Connected!'); addLog('Connected to extension wallet', 'success'); onWalletConnected(wallet); } catch (err: unknown) { const msg = err instanceof Error ? err.message : String(err); setStatus(`Error: ${msg}`); addLog(`Connection error: ${msg}`, 'error'); } finally { setLoading(false); } } return (

Connect Wallet

{status &&

{status}

} {network === 'local' && (
)} {network === 'remote' && !verificationEmojis && !connected && (
{providers.length === 0 && ( discoveryDone ?

No wallet extensions found. Install an Aztec wallet extension.

:

Looking for wallet extensions...

)} {providers.map((provider, i) => ( ))}
)} {verificationEmojis && (

Verify Connection

Check that these emojis match what your wallet extension shows, then approve there:

{verificationEmojis}
)}
); } ``` > [Source code: docs/examples/webapp-tutorial/src/components/WalletConnect.tsx#L17-L198](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/components/WalletConnect.tsx#L17-L198) ### Account info (`src/components/AccountInfo.tsx`)[​](#account-info-srccomponentsaccountinfotsx "Direct link to account-info-srccomponentsaccountinfotsx") Displays the connected account's address. Takes the wallet as a prop. account-info ``` import React from 'react'; import { AztecAddress } from '@aztec/aztec.js/addresses'; interface AccountInfoProps { address: AztecAddress; } /** * Displays the connected account address (truncated). */ export function AccountInfo({ address }: AccountInfoProps) { const addr = address.toString(); const display = `${addr.slice(0, 10)}...${addr.slice(-6)}`; return (
Connected: {display}
); } ``` > [Source code: docs/examples/webapp-tutorial/src/components/AccountInfo.tsx#L1-L23](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/components/AccountInfo.tsx#L1-L23) ## Next steps[​](#next-steps "Direct link to Next steps") With a wallet connected, you can now [deploy and interact with the Pod Racing contract from the webapp](/developers/testnet/docs/tutorials/js_tutorials/webapp/contract-interaction.md). --- # Project Setup ## Overview[​](#overview "Direct link to Overview") This section walks through the project structure and key configuration files. Since you [cloned the example](/developers/testnet/docs/tutorials/js_tutorials/webapp.md#clone-the-example) in the introduction, everything is already in place — this page explains what each piece does. ## Project structure[​](#project-structure "Direct link to Project structure") ``` webapp-tutorial/ ├── contracts/ # Noir smart contract source │ ├── Nargo.toml │ └── src/ │ ├── main.nr │ ├── game_round_note.nr │ └── race.nr ├── scripts/ # Standalone scripts (deploy-and-interact) ├── src/ │ ├── artifacts/ # Generated contract bindings (from yarn prep) │ ├── components/ # React components (GameBoard, GameLobby, etc.) │ ├── App.tsx # Main app component │ ├── config.ts # Network configuration │ ├── contract.ts # Contract deploy/call helpers │ ├── embedded-wallet.ts # Embedded wallet for local dev │ ├── fees.ts # SponsoredFPC fee payment utilities │ ├── game-constants.ts # Shared game constants │ ├── main.tsx # React entry point │ └── wallet-connection.ts # Wallet SDK connection logic ├── test-extension/ # Tutorial wallet extension (browser wallet) ├── index.html # HTML entry point ├── vite.config.ts # Vite configuration ├── tsconfig.json # TypeScript configuration ├── .env.example # Environment variables to override └── package.json ``` ## Vite configuration[​](#vite-configuration "Direct link to Vite configuration") Aztec uses WASM modules that require `SharedArrayBuffer`, which needs specific HTTP headers. Open `vite.config.ts`: vite-config ``` import { defineConfig, type Plugin } from "vite"; import react from "@vitejs/plugin-react-swc"; import { type PolyfillOptions, nodePolyfills, } from "vite-plugin-node-polyfills"; // Unfortunate, but needed due to https://github.com/davidmyersdev/vite-plugin-node-polyfills/issues/81 const nodePolyfillsFix = (options?: PolyfillOptions): Plugin => ({ ...nodePolyfills(options), resolveId(source: string) { const m = /^vite-plugin-node-polyfills\/shims\/(buffer|global|process)$/.exec( source, ); if (m) { return `./node_modules/vite-plugin-node-polyfills/shims/${m[1]}/dist/index.cjs`; } }, }); export default defineConfig({ plugins: [ react(), nodePolyfillsFix({ globals: { process: true, Buffer: true, }, }), ], server: { // Headers required for SharedArrayBuffer (needed by bb WASM) headers: { "Cross-Origin-Opener-Policy": "same-origin", "Cross-Origin-Embedder-Policy": "require-corp", }, }, // Exclude WASM-containing packages from pre-bundling optimizeDeps: { include: ["pino", "pino/browser"], exclude: [ "@aztec/noir-noirc_abi", "@aztec/noir-acvm_js", "@aztec/bb.js", "@aztec/noir-noir_js", ], }, }); ``` > [Source code: docs/examples/webapp-tutorial/vite.config.ts#L1-L51](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/vite.config.ts#L1-L51) Why each piece is needed: * **COOP/COEP headers**: Enable `SharedArrayBuffer` for multithreaded WASM (barretenberg proving) * **Node polyfills**: Aztec libraries use Node.js APIs that need polyfilling in the browser * **optimizeDeps.exclude**: Prevents Vite from pre-bundling WASM-containing packages (`@aztec/bb.js`, `@aztec/noir-noirc_abi`, etc.) which would corrupt the WASM modules ## TypeScript configuration[​](#typescript-configuration "Direct link to TypeScript configuration") The project uses a standard Vite + React TypeScript setup. `tsconfig.json` references `tsconfig.app.json`, which targets `ES2020` with `"jsx": "react-jsx"` and `"moduleResolution": "Bundler"`. No special configuration is needed for Aztec. ## Environment variables[​](#environment-variables "Direct link to Environment variables") Copy `.env.example` to `.env`: ``` cp .env.example .env ``` and paste the below into the file. ``` AZTEC_NODE_URL=http://localhost:8080 ``` This tells the app where to find the Aztec node. For local development, this points to the default local network port. When using the wallet extension, it manages the node connection. ## Compiling the contract[​](#compiling-the-contract "Direct link to Compiling the contract") If you haven't already compiled the contract from the [previous section](/developers/testnet/docs/tutorials/js_tutorials/webapp/the-contract.md#compile-the-contract): ``` yarn prep ``` This produces `src/artifacts/PodRacing.ts` and `src/artifacts/PodRacing.json`. The webapp imports the typed contract class from `PodRacing.ts`. ## Next steps[​](#next-steps "Direct link to Next steps") With the project structure understood, continue to [network and wallet setup](/developers/testnet/docs/tutorials/js_tutorials/webapp/network-and-wallet.md). --- # Putting It Together In this final section, you wire all the components together into a working app and run it. ## Entry point[​](#entry-point "Direct link to Entry point") Open `src/main.tsx`: main ``` import React from 'react'; import ReactDOM from 'react-dom/client'; import { App } from './App'; import './App.css'; ReactDOM.createRoot(document.getElementById('root')!).render( , ); ``` > [Source code: docs/examples/webapp-tutorial/src/main.tsx#L1-L12](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/main.tsx#L1-L12) ## App component[​](#app-component "Direct link to App component") Open `src/App.tsx`. The app is structured as a simple state machine with three phases: * **connect**: The user picks a wallet mode (embedded or browser extension) and connects a wallet. During this phase, PXE (Private eXecution Environment) is initialized and an account is linked. * **lobby**: The user creates a new game (deploying a contract) or joins an existing one (attaching to a contract). Once a game is active, the app transitions forward. * **playing**: The user plays rounds, reveals scores, and determines the winner. All gameplay actions are contract calls. ### Imports[​](#imports "Direct link to Imports") app-imports ``` import React, { useState } from 'react'; import type { Wallet } from '@aztec/aztec.js/wallet'; import { AztecAddress } from '@aztec/aztec.js/addresses'; import type { NetworkType } from './config'; import type { PodRacingContract } from './artifacts/PodRacing'; import { NetworkPicker } from './components/NetworkPicker'; import { WalletConnect } from './components/WalletConnect'; import { AccountInfo } from './components/AccountInfo'; import { GameLobby } from './components/GameLobby'; import { GameBoard } from './components/GameBoard'; import { GameStatus } from './components/GameStatus'; import { ErrorBoundary } from './components/ErrorBoundary'; import { LogProvider, TransactionLog } from './components/TransactionLog'; import { TwoPlayerLocal } from './components/TwoPlayerLocal'; import { EmbeddedWallet } from './embedded-wallet'; ``` > [Source code: docs/examples/webapp-tutorial/src/App.tsx#L1-L17](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/App.tsx#L1-L17) ### State[​](#state "Direct link to State") app-state ``` type AppPhase = 'connect' | 'lobby' | 'playing'; function App() { const [network, setNetwork] = useState('local'); const [wallet, setWallet] = useState(null); const [account, setAccount] = useState(null); const [phase, setPhase] = useState('connect'); const [contract, setContract] = useState(null); const [gameId, setGameId] = useState(BigInt(0)); const [currentRound, setCurrentRound] = useState(1); ``` > [Source code: docs/examples/webapp-tutorial/src/App.tsx#L19-L30](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/App.tsx#L19-L30) ### Event handlers[​](#event-handlers "Direct link to Event handlers") Each handler transitions the app to the next phase: * `handleWalletConnected` — receives the wallet instance from the `WalletConnect` component. For embedded wallets it reads the connected account directly; for extension wallets it fetches the account list. Then it transitions from `connect` to `lobby`. * `handleGameJoined` — receives the typed contract instance and game ID from the `GameLobby` component, and transitions from `lobby` to `playing`. app-handlers ``` async function handleWalletConnected(w: Wallet | EmbeddedWallet) { setWallet(w); if (w instanceof EmbeddedWallet) { setAccount(w.getConnectedAccount()); setPhase('lobby'); } else { // Extension wallet — getAccounts returns the active account(s) try { const accounts = await w.getAccounts(); console.log('Accounts received:', accounts); if (accounts && accounts.length > 0) { const addr = accounts[0].item; console.log('Setting account:', addr); setAccount(addr); setPhase('lobby'); } else { alert('Please create an account in the wallet extension first, then refresh the page.'); } } catch (err: unknown) { console.error('Error getting accounts:', err); alert(`Error connecting to wallet: ${err}`); } } } function handleGameJoined(c: PodRacingContract, gId: bigint) { setContract(c); setGameId(gId); setCurrentRound(1); setPhase('playing'); } function handleRoundPlayed() { setCurrentRound((r) => r + 1); } ``` > [Source code: docs/examples/webapp-tutorial/src/App.tsx#L32-L68](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/App.tsx#L32-L68) ### Render[​](#render "Direct link to Render") The render function conditionally displays components based on the current phase. Props flow downward: `network` goes to `WalletConnect`, `wallet` and `account` go to `GameLobby`, `contract`, `gameId`, and `currentRound` go to `GameBoard`. Each child component calls back to the parent via the event handlers above when its phase is complete. app-render ``` return (

Pod Racing on Aztec

{network === 'remote' && account && }
{/* Local network: Two-player split-screen mode */} {network === 'local' && } {/* Remote: Single-player mode with wallet extension */} {network === 'remote' && phase === 'connect' && ( )} {network === 'remote' && phase === 'lobby' && wallet && account && ( )} {network === 'remote' && phase === 'playing' && wallet && account && contract && (
)}
); ``` > [Source code: docs/examples/webapp-tutorial/src/App.tsx#L70-L128](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/App.tsx#L70-L128) ## Running the app[​](#running-the-app "Direct link to Running the app") ``` # Terminal 1: Start the local network aztec start --local-network # Terminal 2: Start the dev server yarn dev ``` Open `http://localhost:5173`. ### Playing a game (local, split-screen)[​](#playing-a-game-local-split-screen "Direct link to Playing a game (local, split-screen)") When you select "Local" network, the app renders a two-player split-screen on a single page. Both players are side by side: 1. **Player 1 panel**: Click "Connect Account #1" — initializes a PXE and connects the first test account 2. **Player 2 panel**: Click "Connect Account #2" — initializes a separate PXE and connects the second test account 3. **Player 1 panel**: Click "Deploy Contract & Create Game" — deploys the Pod Racing contract and creates a game 4. **Player 2 panel**: Click "Join Game" — registers the contract with Player 2's PXE and joins the game 5. **Both panels**: Adjust the track sliders (total must be < 10) and click "Submit Round" — repeat for rounds 1, 2, and 3 6. **Both panels**: Click "Reveal Scores" to call `finish_game` 7. **Player 1 panel**: Click "Finalize Game" to call `finalize_game` and determine the winner ### With browser extension wallet[​](#with-browser-extension-wallet "Direct link to With browser extension wallet") 1. Build and install the tutorial wallet extension (see [`test-extension/README.md`](https://github.com/AztecProtocol/aztec-packages/tree/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension) for build and install instructions) 2. Run `yarn dev` 3. Select "Browser Wallet" → connect via the wallet extension → verify emojis match 4. Play the same flow as above (share the contract address with your opponent) ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### "SharedArrayBuffer is not defined"[​](#sharedarraybuffer-is-not-defined "Direct link to \"SharedArrayBuffer is not defined\"") `SharedArrayBuffer` is a browser API for shared memory between threads. Aztec's WASM proving engine (Barretenberg) uses it for multithreaded proof generation. It requires the `Cross-Origin-Opener-Policy` and `Cross-Origin-Embedder-Policy` headers to be set, which the Vite config handles. Make sure you're using `yarn dev` (not opening the HTML file directly). Check `vite.config.ts`. ### PXE initialization is slow[​](#pxe-initialization-is-slow "Direct link to PXE initialization is slow") The first load downloads and compiles WASM modules. Subsequent loads use the browser cache. ### "Account not found" errors[​](#account-not-found-errors "Direct link to \"Account not found\" errors") Make sure the local network is running (`aztec start --local-network`) and that test accounts are deployed. ### Transaction fails with fee errors[​](#transaction-fails-with-fee-errors "Direct link to Transaction fails with fee errors") Ensure SponsoredFPC is registered with PXE. The `EmbeddedWallet` does this automatically. If using the wallet SDK, the extension handles fees. ### Contract compilation fails[​](#contract-compilation-fails "Direct link to Contract compilation fails") Make sure the `aztec` CLI is installed and matches your package versions. Run `aztec --version` to check. The `Nargo.toml` dependency path must point to a valid `aztec-nr` location. ## Next steps[​](#next-steps "Direct link to Next steps") You now have a working Aztec webapp. From here you could: * Add more game features (tournaments, betting, leaderboards) * Deploy your own contract with custom game logic * Integrate with a production wallet * Add persistent storage for game history --- # The Contract Prerequisites Make sure you've completed the [setup steps](/developers/testnet/docs/tutorials/js_tutorials/webapp.md#clone-the-example) before continuing. In this section, you walk through the Pod Racing smart contract, compile it, and run a standalone script that deploys and plays a full game against a local network. ## Overview[​](#overview "Direct link to Overview") Before looking at code, here is how the contract is structured. **Storage:** * `admin` — the address that deployed the contract * `races` — a public map from game ID to `Race` struct (the shared game state) * `progress` — a private map storing each player's per-round point allocations as encrypted notes * `win_history` — a public map tracking each player's lifetime win count **Game lifecycle:** 1. `create_game` — player 1 calls this to create a new `Race` in public storage, setting a block deadline 2. `join_game` — player 2 joins, filling the second slot in the `Race` 3. `play_round` (private) — each player submits a `GameRoundNote` containing their point allocation for one round; a public follow-up increments the round counter without revealing points 4. `finish_game` (private) — reads all of your `GameRoundNote`s, sums totals per track, and publishes the aggregated scores publicly 5. `finalize_game` (public) — compares both players' track totals, declares the winner (best of 5), and updates win history **Key types:** * `GameRoundNote` — a private note storing one round's point allocation (5 track values), the round number, and the owner * `Race` — the public game state: both player addresses, round counters, final per-track scores, block deadline, and winner The core design principle is that **private functions** (`play_round`, `finish_game`) hide your strategy, while **public functions** (`create_game`, `join_game`, `finalize_game`) coordinate shared state that both players can see. ## Nargo.toml[​](#nargotoml "Direct link to Nargo.toml") Open `contracts/Nargo.toml`. This configures the Noir compiler for the contract: ``` [package] name = "pod_racing_contract" authors = [""] compiler_version = ">=0.25.0" type = "contract" [dependencies] aztec = { path = "../../../../noir-projects/aztec-nr/aztec" } ``` note The `aztec` dependency path assumes you're working within the `aztec-packages` monorepo. If you're working outside the monorepo, use the git dependency instead: ``` aztec = { git = "https://github.com/AztecProtocol/aztec-nr/", tag = "v5.0.0-rc.2", directory = "aztec" } ``` Replace the tag with your Aztec version. ## GameRoundNote[​](#gameroundnote "Direct link to GameRoundNote") Open `contracts/src/game_round_note.nr`. This is a private note that stores a player's point allocation for one round: game-round-note ``` use aztec::{macros::notes::note, protocol::{traits::Packable, address::AztecAddress}}; /// A private note storing a player's point allocation for one round. /// These notes remain private until the player calls finish_game to reveal totals. #[derive(Eq, Packable)] #[note] pub struct GameRoundNote { pub track1: u8, pub track2: u8, pub track3: u8, pub track4: u8, pub track5: u8, pub round: u8, pub owner: AztecAddress, } impl GameRoundNote { pub fn new(track1: u8, track2: u8, track3: u8, track4: u8, track5: u8, round: u8, owner: AztecAddress) -> Self { Self { track1, track2, track3, track4, track5, round, owner } } } ``` > [Source code: docs/examples/webapp-tutorial/contracts/src/game\_round\_note.nr#L1-L24](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/contracts/src/game_round_note.nr#L1-L24) The fields `track1` through `track5` store the points allocated to each track for that round. `round` identifies which round (1, 2, or 3) the note belongs to. `owner` is the player's address — only the owner's PXE (Private eXecution Environment) can decrypt and read this note. ### How private notes work[​](#how-private-notes-work "Direct link to How private notes work") In Aztec, private state is stored as **notes** — encrypted data objects that live in your PXE local database. When a note is created, it is encrypted with the owner's public key so that only the owner's PXE can decrypt and read it. The note is also committed to the network as a hash (called a commitment), which allows for proving the note exists without revealing its contents. When you call `play_round`, the contract creates a `GameRoundNote` with your allocation. The flow: 1. You call `play_round(gameId, round, 3, 2, 1, 2, 1)` — a **private function** 2. The contract creates a `GameRoundNote` with your allocation, stored privately 3. It then enqueues a **public** call to `validate_and_play_round` which increments your round counter (visible) without revealing your points (hidden). "Enqueues" means the private function schedules a public function to run after the private execution completes. Private functions cannot modify public state directly, so they use this mechanism to trigger public side effects (e.g., state changes or emitting logs). 4. Your opponent's PXE cannot decrypt your notes — they only see that you completed a round This means: * **You** know your own allocations * **Your opponent** only sees that you've played a round (public round counter) * **The network** only sees encrypted data ## Race struct[​](#race-struct "Direct link to Race struct") Open `contracts/src/race.nr`. This is the public game state: race ``` use aztec::protocol::{ address::AztecAddress, traits::{Deserialize, Serialize, Packable}, }; /// Public game state: player addresses, round progress, and final track scores. #[derive(Deserialize, Serialize, Eq, Packable)] pub struct Race { pub player1: AztecAddress, pub player2: AztecAddress, pub total_rounds: u8, pub player1_round: u8, pub player2_round: u8, pub player1_track1_final: u64, pub player1_track2_final: u64, pub player1_track3_final: u64, pub player1_track4_final: u64, pub player1_track5_final: u64, pub player2_track1_final: u64, pub player2_track2_final: u64, pub player2_track3_final: u64, pub player2_track4_final: u64, pub player2_track5_final: u64, pub end_block: u32, } impl Race { pub fn new(player1: AztecAddress, total_rounds: u8, end_block: u32) -> Race { Self { player1, player2: AztecAddress::zero(), total_rounds, player1_round: 0, player2_round: 0, player1_track1_final: 0, player1_track2_final: 0, player1_track3_final: 0, player1_track4_final: 0, player1_track5_final: 0, player2_track1_final: 0, player2_track2_final: 0, player2_track3_final: 0, player2_track4_final: 0, player2_track5_final: 0, end_block, } } pub fn join(self, player2: AztecAddress) -> Race { assert(!self.player1.eq(AztecAddress::zero()) & self.player2.eq(AztecAddress::zero())); assert(!self.player1.eq(player2)); Self { player1: self.player1, player2, total_rounds: self.total_rounds, player1_round: self.player1_round, player2_round: self.player2_round, player1_track1_final: 0, player1_track2_final: 0, player1_track3_final: 0, player1_track4_final: 0, player1_track5_final: 0, player2_track1_final: 0, player2_track2_final: 0, player2_track3_final: 0, player2_track4_final: 0, player2_track5_final: 0, end_block: self.end_block, } } pub fn increment_player_round(self, player: AztecAddress, round: u8) -> Race { assert(round < self.total_rounds + 1); let ret = if player.eq(self.player1) { assert(round == self.player1_round + 1); Option::some(Self { player1: self.player1, player2: self.player2, total_rounds: self.total_rounds, player1_round: round, player2_round: self.player2_round, player1_track1_final: self.player1_track1_final, player1_track2_final: self.player1_track2_final, player1_track3_final: self.player1_track3_final, player1_track4_final: self.player1_track4_final, player1_track5_final: self.player1_track5_final, player2_track1_final: self.player2_track1_final, player2_track2_final: self.player2_track2_final, player2_track3_final: self.player2_track3_final, player2_track4_final: self.player2_track4_final, player2_track5_final: self.player2_track5_final, end_block: self.end_block, }) } else if player.eq(self.player2) { assert(round == self.player2_round + 1); Option::some(Self { player1: self.player1, player2: self.player2, total_rounds: self.total_rounds, player1_round: self.player1_round, player2_round: round, player1_track1_final: self.player1_track1_final, player1_track2_final: self.player1_track2_final, player1_track3_final: self.player1_track3_final, player1_track4_final: self.player1_track4_final, player1_track5_final: self.player1_track5_final, player2_track1_final: self.player2_track1_final, player2_track2_final: self.player2_track2_final, player2_track3_final: self.player2_track3_final, player2_track4_final: self.player2_track4_final, player2_track5_final: self.player2_track5_final, end_block: self.end_block, }) } else { Option::none() }; ret.unwrap() } pub fn set_player_scores(self, player: AztecAddress, track1_final: u64, track2_final: u64, track3_final: u64, track4_final: u64, track5_final: u64) -> Race { let ret = if player.eq(self.player1) { assert( self.player1_track1_final + self.player1_track2_final + self.player1_track3_final + self.player1_track4_final + self.player1_track5_final == 0 ); Option::some(Self { player1: self.player1, player2: self.player2, total_rounds: self.total_rounds, player1_round: self.player1_round, player2_round: self.player2_round, player1_track1_final: track1_final, player1_track2_final: track2_final, player1_track3_final: track3_final, player1_track4_final: track4_final, player1_track5_final: track5_final, player2_track1_final: self.player2_track1_final, player2_track2_final: self.player2_track2_final, player2_track3_final: self.player2_track3_final, player2_track4_final: self.player2_track4_final, player2_track5_final: self.player2_track5_final, end_block: self.end_block, }) } else if player.eq(self.player2) { assert( self.player2_track1_final + self.player2_track2_final + self.player2_track3_final + self.player2_track4_final + self.player2_track5_final == 0 ); Option::some(Self { player1: self.player1, player2: self.player2, total_rounds: self.total_rounds, player1_round: self.player1_round, player2_round: self.player2_round, player1_track1_final: self.player1_track1_final, player1_track2_final: self.player1_track2_final, player1_track3_final: self.player1_track3_final, player1_track4_final: self.player1_track4_final, player1_track5_final: self.player1_track5_final, player2_track1_final: track1_final, player2_track2_final: track2_final, player2_track3_final: track3_final, player2_track4_final: track4_final, player2_track5_final: track5_final, end_block: self.end_block, }) } else { Option::none() }; ret.unwrap() } pub fn calculate_winner(self, current_block_number: u32) -> AztecAddress { assert(current_block_number > self.end_block); let mut player1_wins = 0; let mut player2_wins = 0; if self.player1_track1_final > self.player2_track1_final { player1_wins += 1; } else { player2_wins += 1; }; if self.player1_track2_final > self.player2_track2_final { player1_wins += 1; } else { player2_wins += 1; }; if self.player1_track3_final > self.player2_track3_final { player1_wins += 1; } else { player2_wins += 1; }; if self.player1_track4_final > self.player2_track4_final { player1_wins += 1; } else { player2_wins += 1; }; if self.player1_track5_final > self.player2_track5_final { player1_wins += 1; } else { player2_wins += 1; }; if (player1_wins > player2_wins) { self.player1 } else { self.player2 } } } ``` > [Source code: docs/examples/webapp-tutorial/contracts/src/race.nr#L1-L166](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/contracts/src/race.nr#L1-L166) The `Race` struct holds both player addresses, per-player round counters, the final aggregated scores for each track (filled in when a player calls `finish_game`), and a block deadline. The winner calculation compares track-by-track totals: whoever wins more of the 5 tracks wins the game. ## Main contract[​](#main-contract "Direct link to Main contract") Open `contracts/src/main.nr`. This is the main contract file that defines the game flow. ### Storage[​](#storage "Direct link to Storage") storage ``` #[storage] struct Storage { admin: PublicMutable, races: Map, Context>, progress: Map, Context>, Context>, win_history: Map, Context>, } ``` > [Source code: docs/examples/webapp-tutorial/contracts/src/main.nr#L37-L45](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/contracts/src/main.nr#L37-L45) The storage maps directly to the overview above: `admin` for the deployer, `races` for game state, `progress` for private round notes, and `win_history` for tracking wins. ### Creating and joining a game[​](#creating-and-joining-a-game "Direct link to Creating and joining a game") create-game ``` #[external("public")] fn create_game(game_id: Field) { assert(self.storage.races.at(game_id).read().player1.eq(AztecAddress::zero())); let game = Race::new( self.msg_sender(), TOTAL_ROUNDS, self.context.block_number() + GAME_LENGTH, ); self.storage.races.at(game_id).write(game); } ``` > [Source code: docs/examples/webapp-tutorial/contracts/src/main.nr#L53-L64](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/contracts/src/main.nr#L53-L64) join-game ``` #[external("public")] fn join_game(game_id: Field) { let maybe_existing_game = self.storage.races.at(game_id).read(); let joined_game = maybe_existing_game.join(self.msg_sender()); self.storage.races.at(game_id).write(joined_game); } ``` > [Source code: docs/examples/webapp-tutorial/contracts/src/main.nr#L66-L73](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/contracts/src/main.nr#L66-L73) Both are public functions. `create_game` initializes a new `Race` with the caller as player 1 and sets a block deadline. `join_game` fills in player 2. ### Playing a round (private)[​](#playing-a-round-private "Direct link to Playing a round (private)") play-round ``` /// Allocates points across 5 tracks for a round. /// This is a PRIVATE function - the allocation remains hidden from the opponent. #[external("private")] fn play_round( game_id: Field, round: u8, track1: u8, track2: u8, track3: u8, track4: u8, track5: u8, ) { assert(track1 + track2 + track3 + track4 + track5 < 10); let player = self.msg_sender(); self .storage .progress .at(game_id) .at(player) .insert(GameRoundNote::new(track1, track2, track3, track4, track5, round, player)) .deliver(MessageDelivery::onchain_unconstrained()); self.enqueue(PodRacing::at(self.context.this_address()).validate_and_play_round( player, game_id, round, )); } ``` > [Source code: docs/examples/webapp-tutorial/contracts/src/main.nr#L75-L106](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/contracts/src/main.nr#L75-L106) This is a **private function** — the point allocation remains hidden from the opponent. It creates a `GameRoundNote`, then enqueues a public call to `validate_and_play_round` to increment the round counter without revealing points. ### Finishing the game (reveal)[​](#finishing-the-game-reveal "Direct link to Finishing the game (reveal)") finish-game ``` /// Reveals a player's total scores per track. /// Reads private round notes and publishes aggregated totals. #[external("private")] fn finish_game(game_id: Field) { let player = self.msg_sender(); let totals = self.storage.progress.at(game_id).at(player).get_notes(NoteGetterOptions::new()); let mut total_track1: u64 = 0; let mut total_track2: u64 = 0; let mut total_track3: u64 = 0; let mut total_track4: u64 = 0; let mut total_track5: u64 = 0; for i in 0..TOTAL_ROUNDS { total_track1 += totals.get(i as u32).note.track1 as u64; total_track2 += totals.get(i as u32).note.track2 as u64; total_track3 += totals.get(i as u32).note.track3 as u64; total_track4 += totals.get(i as u32).note.track4 as u64; total_track5 += totals.get(i as u32).note.track5 as u64; } self.enqueue(PodRacing::at(self.context.this_address()).validate_finish_game_and_reveal( player, game_id, total_track1, total_track2, total_track3, total_track4, total_track5, )); } ``` > [Source code: docs/examples/webapp-tutorial/contracts/src/main.nr#L115-L148](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/contracts/src/main.nr#L115-L148) Another private function. It reads all your `GameRoundNote`s, sums up totals per track, and publishes the aggregated scores to public state. Your per-round choices stay hidden, but your final totals become visible. ### Finalizing the game[​](#finalizing-the-game "Direct link to Finalizing the game") finalize-game ``` /// Determines the winner after both players have revealed and the game has expired. #[external("public")] fn finalize_game(game_id: Field) { let game_in_progress = self.storage.races.at(game_id).read(); let winner = game_in_progress.calculate_winner(self.context.block_number()); let previous_wins = self.storage.win_history.at(winner).read(); self.storage.win_history.at(winner).write(previous_wins + 1); } ``` > [Source code: docs/examples/webapp-tutorial/contracts/src/main.nr#L172-L181](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/contracts/src/main.nr#L172-L181) A public function that compares both players' track totals, declares the winner (best of 5 tracks), and updates the win history. Can only be called after the game's block deadline has passed. ## Compile the contract[​](#compile-the-contract "Direct link to Compile the contract") With the Aztec CLI installed and the contract source in place: ``` # Compile the Noir contract and generate TypeScript bindings yarn prep ``` This runs `yarn compile && yarn codegen`, producing `src/artifacts/PodRacing.ts` (the typed contract class) and `src/artifacts/PodRacing.json` (the compiled artifact). You'll import from `PodRacing.ts` throughout the app. ## Deploy and interact via script[​](#deploy-and-interact-via-script "Direct link to Deploy and interact via script") To verify everything works, run a standalone TypeScript script that deploys the contract and plays a full game against a local network. Open `scripts/deploy-and-interact.ts`. It follows the same pattern as the [aztec.js Getting Started](/developers/testnet/docs/tutorials/js_tutorials/aztecjs-getting-started.md) guide. ### Setup[​](#setup "Direct link to Setup") script-setup ``` import { EmbeddedWallet } from "@aztec/wallets/embedded"; import { getInitialTestAccountsData } from "@aztec/accounts/testing"; // @ts-ignore — generated artifact, may not exist until compiled import { PodRacingContract } from "../src/artifacts/PodRacing.js"; const nodeUrl = process.env.AZTEC_NODE_URL ?? "http://localhost:8080"; const wallet = await EmbeddedWallet.create(nodeUrl, { ephemeral: true }); const [alice, bob] = await getInitialTestAccountsData(); await wallet.createSchnorrAccount(alice.secret, alice.salt); await wallet.createSchnorrAccount(bob.secret, bob.salt); console.log("Accounts ready:", alice.address.toString(), bob.address.toString()); ``` > [Source code: docs/examples/webapp-tutorial/scripts/deploy-and-interact.ts#L1-L14](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/scripts/deploy-and-interact.ts#L1-L14) This uses `EmbeddedWallet` from `@aztec/wallets/embedded` — a ready-made embedded wallet that handles PXE creation and account management. `getInitialTestAccountsData` provides pre-deployed test accounts available on the local network. The `{ ephemeral: true }` option means PXE state is not persisted between runs. ### Deploy the contract[​](#deploy-the-contract "Direct link to Deploy the contract") script-deploy ``` const { contract } = await PodRacingContract.deploy(wallet, alice.address).send({ from: alice.address, }); console.log("Contract deployed at:", contract.address.toString()); ``` > [Source code: docs/examples/webapp-tutorial/scripts/deploy-and-interact.ts#L16-L21](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/scripts/deploy-and-interact.ts#L16-L21) ### Create and join a game[​](#create-and-join-a-game "Direct link to Create and join a game") script-create-join ``` const gameId = 1n; await contract.methods.create_game(gameId).send({ from: alice.address }); console.log("Game created"); await contract.methods.join_game(gameId).send({ from: bob.address }); console.log("Bob joined the game"); ``` > [Source code: docs/examples/webapp-tutorial/scripts/deploy-and-interact.ts#L23-L30](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/scripts/deploy-and-interact.ts#L23-L30) ### Play rounds[​](#play-rounds "Direct link to Play rounds") script-play-rounds ``` // Round 1 await contract.methods .play_round(gameId, 1, 3, 2, 1, 2, 1) .send({ from: alice.address }); await contract.methods .play_round(gameId, 1, 1, 1, 3, 2, 2) .send({ from: bob.address }); // Round 2 await contract.methods .play_round(gameId, 2, 2, 3, 1, 1, 2) .send({ from: alice.address }); await contract.methods .play_round(gameId, 2, 2, 2, 2, 2, 1) .send({ from: bob.address }); // Round 3 await contract.methods .play_round(gameId, 3, 1, 1, 2, 3, 2) .send({ from: alice.address }); await contract.methods .play_round(gameId, 3, 3, 1, 1, 1, 3) .send({ from: bob.address }); console.log("All rounds played"); ``` > [Source code: docs/examples/webapp-tutorial/scripts/deploy-and-interact.ts#L32-L57](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/scripts/deploy-and-interact.ts#L32-L57) Each `play_round` call is a private transaction — the point allocations are encrypted as notes. Alice and Bob each play 3 rounds. ### Finish and finalize[​](#finish-and-finalize "Direct link to Finish and finalize") script-finish-finalize ``` await contract.methods.finish_game(gameId).send({ from: alice.address }); await contract.methods.finish_game(gameId).send({ from: bob.address }); console.log("Both players revealed scores"); await contract.methods.finalize_game(gameId).send({ from: alice.address }); console.log("Game finalized! Winner determined."); ``` > [Source code: docs/examples/webapp-tutorial/scripts/deploy-and-interact.ts#L59-L66](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/scripts/deploy-and-interact.ts#L59-L66) Both players call `finish_game` to reveal their aggregated scores, then either player calls `finalize_game` to determine the winner. ### Run it[​](#run-it "Direct link to Run it") Make sure you have a local network running first: ``` # Terminal 1: Start the local network aztec start --local-network ``` Once the network is ready (you can check with `curl http://localhost:8080/status`), run the script in a separate terminal: ``` # Terminal 2: Run the script yarn interact ``` You should see output showing the contract deployment, game creation, rounds being played, and the winner being determined. The script takes a few minutes to complete as each transaction requires proof generation. ## Next steps[​](#next-steps "Direct link to Next steps") With the contract understood and verified, continue to [project setup](/developers/testnet/docs/tutorials/js_tutorials/webapp/project-setup.md) to see how the webapp is structured. --- # Transactions & Fees This section covers what happens when you send a transaction on Aztec and how fee payment works with SponsoredFPC. ## Transaction lifecycle[​](#transaction-lifecycle "Direct link to Transaction lifecycle") When you call `.send()` on a contract method, it handles the entire lifecycle automatically — proving, submission, and waiting for confirmation: ``` .send() → PXE proves locally → Sent to node → Included in block → Receipt returned ``` 1. **Private execution**: PXE (Private eXecution Environment) simulates the function locally using your private state and decryption keys, producing new notes, nullifiers (which mark old notes as spent), and any enqueued public function calls. 2. **Proof generation**: Barretenberg (Aztec's proving system) generates a ZK-SNARK proving that the execution was valid — without revealing your private inputs to anyone. 3. **Submission**: The proof, encrypted notes, nullifiers, and any public function calls are bundled together and sent to the Aztec node. 4. **Inclusion**: The node's sequencer validates the proof, executes any enqueued public functions, and includes the transaction in the next block. 5. **Confirmation**: The block is published to L1 (Ethereum), and a receipt with the transaction hash is returned to your app. ## Tracking transaction status[​](#tracking-transaction-status "Direct link to Tracking transaction status") Open `src/components/TxStatus.tsx`: tx-status-component ``` /** * Displays the current transaction lifecycle stage. * Transactions flow: send() -> proving -> confirmed (or error). */ export function TxStatus({ state, txHash, error }: TxStatusProps) { if (state === 'idle') return null; const messages: Record = { idle: '', sending: 'Sending transaction...', proving: 'Proving transaction (generating ZK proof)...', confirmed: 'Transaction confirmed!', error: `Transaction failed: ${error}`, }; return (

{messages[state]}

{txHash &&

Tx: {txHash.slice(0, 10)}...

}
); } ``` > [Source code: docs/examples/webapp-tutorial/src/components/TxStatus.tsx#L11-L34](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/components/TxStatus.tsx#L11-L34) Integrate with contract calls: ``` setTxState('sending'); try { const receipt = await contract.methods .play_round(gameId, round, t1, t2, t3, t4, t5) .send({ from: account }); setTxState('confirmed'); setTxHash(receipt.receipt.txHash.toString()); } catch (err) { setTxState('error'); setTxError(err.message); } ``` `.send()` handles the full lifecycle: it generates a proof, submits the transaction to the node, waits for it to be included in a block, and returns the receipt. If you need to send without waiting, pass `wait: NO_WAIT` in the options to get a `TxHash` back immediately instead. `NO_WAIT` is exported from `@aztec/aztec.js/contracts`. ## Fee payment with SponsoredFPC[​](#fee-payment-with-sponsoredfpc "Direct link to Fee payment with SponsoredFPC") Every Aztec transaction requires fee payment, similar to gas on Ethereum. Without a fee, the sequencer won't include your transaction. **SponsoredFPC** (Fee Payment Contract) is a special contract that agrees to pay fees on behalf of any transaction. This is useful for onboarding new users who don't yet have fee tokens. ### How it works[​](#how-it-works "Direct link to How it works") get-sponsored-fpc ``` /** * Returns the SponsoredFPC contract details. * The SponsoredFPC (Fee Payment Contract) pays transaction fees on behalf of users. * This is deployed at a well-known address derived from a fixed salt. */ export async function getSponsoredFPCContract() { const { SponsoredFPCContractArtifact } = await import( '@aztec/noir-contracts.js/SponsoredFPC' ); const instance = await getContractInstanceFromInstantiationParams( SponsoredFPCContractArtifact, { salt: new Fr(SPONSORED_FPC_SALT) } ); return { instance, artifact: SponsoredFPCContractArtifact }; } ``` > [Source code: docs/examples/webapp-tutorial/src/fees.ts#L8-L24](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/fees.ts#L8-L24) `SPONSORED_FPC_SALT` is a fixed constant so that the SponsoredFPC contract is deployed at a deterministic, well-known address across all networks (local network and beyond). Your app can always compute where it lives without querying a registry. ### Registering with PXE[​](#registering-with-pxe "Direct link to Registering with PXE") PXE needs the SponsoredFPC contract artifact registered so it can include fee payment logic when constructing transaction proofs. Without registration, PXE wouldn't know how to interact with the fee contract. register-fpc ``` /** * Registers the SponsoredFPC contract with PXE so it can be used for fee payment. * This must be called before sending any transactions. */ export async function registerSponsoredFPC(pxe: PXE) { const contract = await getSponsoredFPCContract(); await pxe.registerContract(contract); return contract.instance.address; } ``` > [Source code: docs/examples/webapp-tutorial/src/fees.ts#L26-L36](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/fees.ts#L26-L36) ### Manual fee payment[​](#manual-fee-payment "Direct link to Manual fee payment") For explicit control: create-fee-payment ``` /** * Creates a SponsoredFeePaymentMethod that can be passed as the * `paymentMethod` option when sending transactions. */ export async function createSponsoredFeePayment() { const contract = await getSponsoredFPCContract(); return new SponsoredFeePaymentMethod(contract.instance.address); } ``` > [Source code: docs/examples/webapp-tutorial/src/fees.ts#L38-L47](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/fees.ts#L38-L47) ``` const paymentMethod = await createSponsoredFeePayment(); await contract.methods .play_round(gameId, round, t1, t2, t3, t4, t5) .send({ from: account, fee: { paymentMethod }, }); ``` The `EmbeddedWallet` handles this automatically via `completeFeeOptions` — you don't need to pass `fee` options manually when using it. info SponsoredFPC is for development. Production apps use their own fee payment strategy. ## Next steps[​](#next-steps "Direct link to Next steps") Now [put everything together](/developers/testnet/docs/tutorials/js_tutorials/webapp/putting-it-together.md). --- # Wallet SDK The `@aztec/wallet-sdk` package defines how dApps and wallet extensions communicate on Aztec. It handles wallet discovery, establishes encrypted channels via ECDH key exchange, and provides a capability-based permission system — analogous to EIP-1193 and MetaMask's provider on Ethereum, but with built-in encryption and visual verification. This section covers **both sides** of the integration: 1. [**dApp Integration**](/developers/testnet/docs/tutorials/js_tutorials/webapp/wallet-sdk/dapp-integration.md) — Discover wallets, establish secure channels, request capabilities, and use the wallet 2. [**Wallet Extension Integration**](/developers/testnet/docs/tutorials/js_tutorials/webapp/wallet-sdk/wallet-integration.md) — Handle discovery requests, manage sessions, route messages, and extend `BaseWallet` ## Features[​](#features "Direct link to Features") | Feature | Description | | ---------------------------- | ------------------------------------------------------------------------------------------------- | | **Wallet discovery** | dApps broadcast via `window.postMessage`, wallet extensions respond after user approval | | **ECDH key exchange** | P-256 ephemeral key pairs derive a shared secret for each session | | **AES-256-GCM encryption** | All wallet method calls and responses are encrypted after key exchange | | **Emoji verification** | A 9-emoji grid (72-bit security) lets users visually confirm there's no man-in-the-middle | | **Capability permissions** | dApps declare what they need (`AppCapabilities`), wallets grant or deny each capability | | **Trusted origin reconnect** | Previously approved origins auto-reconnect without re-verification | | **BaseWallet** | Abstract class that wallet extensions extend — provides `sendTx`, `simulateTx`, `batch`, and more | ## Architecture[​](#architecture "Direct link to Architecture") ``` ┌──────────────┐ window.postMessage ┌──────────────────────────────┐ │ │ ←─────────────────────────────→ │ Content Script │ │ dApp │ (discovery only) │ ContentScriptConnectionHandler │ │ └──────────┬───────────────────┘ │ WalletManager│ MessagePort (encrypted) │ chrome.runtime │ ExtensionWallet ←────────────────────────────→ ┌──────────▼───────────────────┐ │ │ │ Background Service Worker │ └──────────────┘ │ BackgroundConnectionHandler │ └──────────┬───────────────────┘ │ persistent port ┌──────────▼───────────────────┐ │ Offscreen Document │ │ OffscreenWallet (BaseWallet) │ │ PXE + WASM proofs │ └──────────────────────────────┘ ``` **Discovery** uses `window.postMessage` (unencrypted, public). After the user approves, a `MessagePort` is transferred and **ECDH key exchange** establishes an encrypted channel. All subsequent wallet method calls flow through this encrypted `MessagePort`. ## Package Exports[​](#package-exports "Direct link to Package Exports") The SDK is split into focused entry points: | Import path | What it provides | | -------------------------------------- | --------------------------------------------------------------------------------------------------------------- | | `@aztec/wallet-sdk/manager` | `WalletManager`, `WalletProvider`, `PendingConnection`, `DiscoverySession` — dApp-side discovery and connection | | `@aztec/wallet-sdk/crypto` | `hashToEmoji` — convert verification hash to emoji string | | `@aztec/wallet-sdk/extension/handlers` | `BackgroundConnectionHandler`, `ContentScriptConnectionHandler` — wallet extension handlers | | `@aztec/wallet-sdk/extension/provider` | `ExtensionWallet`, `ExtensionProvider` — low-level provider classes | | `@aztec/wallet-sdk/base-wallet` | `BaseWallet` — abstract wallet class that extensions subclass | ## Security Model[​](#security-model "Direct link to Security Model") The protocol has three phases with increasing trust: 1. **Discovery (public)** — The dApp broadcasts a request. Wallet extensions only respond after the user explicitly approves the connection in the extension popup. No cryptographic material is exchanged. 2. **Key exchange (authenticated)** — Both sides generate ephemeral ECDH P-256 key pairs. The shared secret is expanded via HKDF into an AES-256-GCM encryption key and an HMAC verification key. A 2-second timeout limits the window for interception. 3. **Verified channel (encrypted)** — The HMAC verification key produces a hash that both sides independently convert to a 9-emoji grid. The user visually confirms the emojis match on both the dApp and wallet extension, defending against man-in-the-middle attacks. After confirmation, all messages are encrypted with AES-256-GCM. ## Next steps[​](#next-steps "Direct link to Next steps") * **Building a dApp?** Start with [dApp Integration](/developers/testnet/docs/tutorials/js_tutorials/webapp/wallet-sdk/dapp-integration.md) * **Building a wallet?** Start with [Wallet Extension Integration](/developers/testnet/docs/tutorials/js_tutorials/webapp/wallet-sdk/wallet-integration.md) * **New to Aztec wallets?** Read [Network & Wallet](/developers/testnet/docs/tutorials/js_tutorials/webapp/network-and-wallet.md) first for the basics Related Tutorials The [Wallet Extension Tutorial](/developers/testnet/docs/tutorials/js_tutorials/wallet-extension.md) walks through building a complete Chrome extension wallet step by step, covering service workers, offscreen documents, encrypted storage, and approval flows. --- # dApp Integration This page covers how a dApp discovers wallet extensions, establishes an encrypted channel, requests permissions, and uses the wallet. The patterns here come from the [Pod Racing tutorial](/developers/testnet/docs/tutorials/js_tutorials/webapp/network-and-wallet.md) and [GregoSwap](https://github.com/AztecProtocol/gregoswap), a reference Aztec DEX. ## Installation[​](#installation "Direct link to Installation") Your dApp needs two packages: ``` npm install @aztec/wallet-sdk @aztec/aztec.js ``` The key imports: ``` import { WalletManager, type WalletProvider, type PendingConnection } from '@aztec/wallet-sdk/manager'; import { hashToEmoji } from '@aztec/wallet-sdk/crypto'; import { Fr } from '@aztec/aztec.js/fields'; import type { Wallet, AppCapabilities, GrantedAccountsCapability } from '@aztec/aztec.js/wallet'; ``` ## Step 1: Discover Wallets[​](#step-1-discover-wallets "Direct link to Step 1: Discover Wallets") Wallet discovery broadcasts a request via `window.postMessage`. Any installed Aztec wallet extension that the user has approved responds with its info. discover-wallets ``` /** * Starts discovering available wallet extensions. * Wallet extensions broadcast their availability via window.postMessage. * Returns a cancel function and calls onUpdate with each discovered wallet. */ export function discoverWallets( chainId: number, appId: string, onUpdate: (providers: WalletProvider[]) => void ): { cancel: () => void; done: Promise } { const manager = WalletManager.configure({ extensions: { enabled: true }, }); const providers: WalletProvider[] = []; const discovery = manager.getAvailableWallets({ chainInfo: { chainId: new Fr(chainId), version: new Fr(1), }, appId, onWalletDiscovered: (provider) => { // Deduplicate by wallet ID (StrictMode or remounts can cause duplicate discoveries) if (providers.some(p => p.id === provider.id)) { return; } providers.push(provider); onUpdate([...providers]); }, }); return { cancel: () => discovery.cancel(), done: discovery.done, }; } ``` > [Source code: docs/examples/webapp-tutorial/src/wallet-connection.ts#L26-L64](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/wallet-connection.ts#L26-L64) `WalletManager.configure()` accepts options to filter extensions: ``` const manager = WalletManager.configure({ extensions: { enabled: true, allowList: ['my-trusted-wallet-id'], // Optional: only allow specific wallets blockList: ['unwanted-wallet-id'], // Optional: block specific wallets }, }); ``` `getAvailableWallets()` returns a `DiscoverySession` with: * `wallets` — an `AsyncIterable` that yields providers as they're discovered * `cancel()` — stops discovery * `done` — a `Promise` that resolves when the discovery timeout expires The `onWalletDiscovered` callback fires each time a wallet extension responds. Each `WalletProvider` includes: * `id` — unique wallet identifier * `name` — display name (e.g., "Aztec Tutorial Wallet") * `icon` — optional data URI or URL for the wallet icon * `metadata` — optional version info note Discovery requires user action in the wallet extension — the wallet won't reveal itself without the user clicking "Connect" in their extension popup. This is a privacy feature: websites can't silently detect which wallets you have installed. ## Step 2: Establish a Secure Channel[​](#step-2-establish-a-secure-channel "Direct link to Step 2: Establish a Secure Channel") Once the user picks a wallet, establish an encrypted channel using ECDH key exchange: connect-wallet ``` /** * Connects to a discovered wallet provider. * This establishes a secure encrypted channel using ECDH key exchange. * The returned emojis should be shown to the user for verification. */ export async function connectToProvider( provider: WalletProvider, appId: string ): Promise<{ emojis: string; confirm: () => Promise; cancel: () => void; }> { console.log('[wallet-connection] Calling establishSecureChannel for provider:', provider.name); const pending = await provider.establishSecureChannel(appId); console.log('[wallet-connection] Secure channel established, verificationHash:', pending.verificationHash); const emojis = hashToEmoji(pending.verificationHash); console.log('[wallet-connection] Emojis:', emojis); return { emojis, confirm: () => pending.confirm(), cancel: () => pending.cancel(), }; } ``` > [Source code: docs/examples/webapp-tutorial/src/wallet-connection.ts#L66-L92](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/wallet-connection.ts#L66-L92) `establishSecureChannel()` performs the ECDH P-256 key exchange and returns a `PendingConnection` with: * `verificationHash` — hex string that both sides compute independently * `confirm()` — creates the encrypted `Wallet` proxy (call after user verifies emojis) * `cancel()` — aborts the connection ## Step 3: Emoji Verification[​](#step-3-emoji-verification "Direct link to Step 3: Emoji Verification") The verification hash is converted to a 9-emoji grid using `hashToEmoji()`. Both the dApp and wallet extension compute the same emojis independently. The user visually confirms they match, defending against man-in-the-middle attacks. ``` const emojis = hashToEmoji(pending.verificationHash); // Display emojis to the user ``` Show the emojis prominently in your UI: ``` {verificationEmojis && (

Verify Connection

Check that these emojis match what your wallet extension shows:

{verificationEmojis}
)} ``` After the user confirms the emojis match in the wallet extension, call `confirm()` to create the wallet instance: ``` const wallet = await confirm(); ``` Trusted Origins On subsequent visits, the wallet extension can auto-approve discovery and skip emoji verification for trusted origins. The user only goes through the full flow once per origin. ## Step 4: Request Capabilities[​](#step-4-request-capabilities "Direct link to Step 4: Request Capabilities") After connecting, your dApp should declare the permissions it needs using `requestCapabilities()`. The wallet extension shows the user an approval dialog. The `AppCapabilities` manifest describes your dApp's identity and the permissions it requires. Each capability type maps to a set of wallet operations — for example, `accounts` lets you read account addresses, `transaction` lets you send transactions, and `simulation` lets you simulate without sending. The wallet may grant all, some, or none of the requested capabilities. app-capabilities ``` export function getAppCapabilities(): AppCapabilities { return { version: '1.0', metadata: { name: 'Pod Racing', version: '1.0.0', description: 'Pod Racing game on Aztec', url: window.location.origin, }, capabilities: [ { type: 'accounts', canGet: true }, { type: 'contracts', contracts: '*', canRegister: true, canGetMetadata: true }, { type: 'simulation', transactions: { scope: '*' }, utilities: { scope: '*' } }, { type: 'transaction', scope: '*' }, ], }; } ``` > [Source code: docs/examples/webapp-tutorial/src/wallet-connection.ts#L94-L112](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/wallet-connection.ts#L94-L112) ### Capability Types[​](#capability-types "Direct link to Capability Types") | Type | What it grants | Key properties | | ----------------- | ------------------------------------ | ------------------------------------------------------------------- | | `accounts` | Read accounts, create auth witnesses | `canGet`, `canCreateAuthWit` | | `contracts` | Register contracts, query metadata | `contracts: '*' \| AztecAddress[]`, `canRegister`, `canGetMetadata` | | `contractClasses` | Query contract class metadata | `classes: '*' \| Fr[]`, `canGetMetadata` | | `simulation` | Simulate transactions and utilities | `transactions.scope`, `utilities.scope` | | `transaction` | Send transactions | `scope: '*' \| ContractFunctionPattern[]` | | `data` | Read address book, private events | `addressBook`, `privateEvents: { contracts }` | ### Checking Granted Capabilities[​](#checking-granted-capabilities "Direct link to Checking Granted Capabilities") The wallet may deny or reduce capabilities. Check what was actually granted: ``` const capabilities = await wallet.requestCapabilities(manifest); // Check if accounts were granted const accountsCap = capabilities.granted.find( (c): c is GrantedAccountsCapability => c.type === 'accounts' ); if (!accountsCap?.accounts?.length) { throw new Error('No accounts granted by wallet'); } // The wallet decides which accounts to share const account = accountsCap.accounts[0]; console.log(`Connected as: ${account.alias} (${account.item})`); ``` The `WalletCapabilities` response includes: * `granted` — array of capabilities the wallet approved (missing = denied) * `wallet` — wallet name and version ## Step 5: Use the Wallet[​](#step-5-use-the-wallet "Direct link to Step 5: Use the Wallet") After `confirm()`, you have a standard `Wallet` instance. All method calls are encrypted transparently — you use it like any other wallet: ``` // Get accounts const accounts = await wallet.getAccounts(); // Register a contract await wallet.registerContract(contractInstance, contractArtifact); // Send a transaction const receipt = await wallet.sendTx(executionPayload, { from: account.item }); // Simulate without sending const simulation = await wallet.simulateTx(executionPayload, { from: account.item }); // Create an auth witness const authWit = await wallet.createAuthWit(account.item, messageHashOrIntent); // Batch multiple calls const results = await wallet.batch([ { name: 'simulateTx', args: [payload1, opts1] }, { name: 'simulateTx', args: [payload2, opts2] }, ]); ``` ## Disconnect Handling[​](#disconnect-handling "Direct link to Disconnect Handling") The wallet can disconnect unexpectedly (extension unloaded, user disconnects from popup). Register a callback to handle this: ``` // Register disconnect handler const unsubscribe = wallet.onDisconnect(() => { console.log('Wallet disconnected'); // Fall back to embedded wallet or show reconnect UI }); // Check connection status if (wallet.isDisconnected()) { // Need to reconnect } // Graceful disconnect await wallet.disconnect(); ``` GregoSwap uses this pattern to fall back to an embedded wallet on disconnect: ``` function handleUnexpectedDisconnect() { // Restore embedded wallet if available if (embeddedWallet) { setWallet(embeddedWallet); setAddress(embeddedAddress); } else { setWallet(null); } } const unsubscribe = provider.onDisconnect(handleUnexpectedDisconnect); ``` ## Complete Flow[​](#complete-flow "Direct link to Complete Flow") Here's the full connection flow in a React component: remote-connect ``` /** Discover and connect to a browser extension wallet */ useEffect(() => { if (network !== 'remote') return; setStatus('Discovering wallet extensions...'); setDiscoveryDone(false); const { cancel, done } = discoverWallets(31337, 'pod-racing', (found) => { setProviders(found); setStatus(`Found ${found.length} wallet(s)`); }); let isMounted = true; done.then(() => { if (isMounted) setDiscoveryDone(true); }).catch((err) => { if (isMounted) setStatus(`Discovery error: ${err.message}`); }); return () => { isMounted = false; cancel(); }; }, [network]); async function connectExtension(provider: WalletProvider) { setLoading(true); setStatus('Establishing secure channel...'); try { const { emojis, confirm } = await connectToProvider( provider, 'pod-racing' ); // Show emojis for reference — the wallet extension is the authority // that verifies the emojis match. confirm() is a local operation // that creates the ExtensionWallet proxy (no message sent to extension). setVerificationEmojis(emojis); setStatus('Verify these emojis match in the wallet extension, then approve there.'); const wallet = await confirm(); // Request capabilities — the dApp declares all permissions it needs upfront. // The extension shows an approval dialog; this call blocks until the user approves. setStatus('Requesting permissions from wallet extension...'); const manifest = getAppCapabilities(); const capabilities = await wallet.requestCapabilities(manifest); setVerificationEmojis(null); console.log('[WalletConnect] Granted capabilities:', capabilities); // Check if accounts were granted const accountsCap = capabilities.granted.find( (c): c is GrantedAccountsCapability => c.type === 'accounts' ); if (!accountsCap?.accounts?.length) { setStatus('No accounts granted. Please approve the capabilities request in the wallet extension.'); setLoading(false); return; } setConnected(true); setStatus('Connected!'); addLog('Connected to extension wallet', 'success'); onWalletConnected(wallet); } catch (err: unknown) { const msg = err instanceof Error ? err.message : String(err); setStatus(`Error: ${msg}`); addLog(`Connection error: ${msg}`, 'error'); } finally { setLoading(false); } } ``` > [Source code: docs/examples/webapp-tutorial/src/components/WalletConnect.tsx#L55-L130](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/components/WalletConnect.tsx#L55-L130) ## State Types[​](#state-types "Direct link to State Types") For tracking wallet discovery state in your app: wallet-sdk-types ``` export interface WalletDiscoveryState { providers: WalletProvider[]; selectedProvider: WalletProvider | null; verificationEmojis: string | null; wallet: Wallet | null; status: 'idle' | 'discovering' | 'verifying' | 'connected' | 'error'; error: string | null; } export const initialWalletState: WalletDiscoveryState = { providers: [], selectedProvider: null, verificationEmojis: null, wallet: null, status: 'idle', error: null, }; ``` > [Source code: docs/examples/webapp-tutorial/src/wallet-connection.ts#L6-L24](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/src/wallet-connection.ts#L6-L24) ## Next steps[​](#next-steps "Direct link to Next steps") * [Wallet Extension Integration](/developers/testnet/docs/tutorials/js_tutorials/webapp/wallet-sdk/wallet-integration.md) — Build the other side: handle discovery, manage sessions, and extend `BaseWallet` * [Contract Interaction](/developers/testnet/docs/tutorials/js_tutorials/webapp/contract-interaction.md) — Deploy and call contracts with the connected wallet --- # Wallet Extension Integration This page is a reference for wallet extension developers. It walks through each component of the SDK integration — you don't need to follow it step-by-step. For the full source, see the [`test-extension/`](https://github.com/AztecProtocol/aztec-packages/tree/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension) directory. ## What you'll learn[​](#what-youll-learn "Direct link to What you'll learn") * How to set up a **content script** that relays messages between a dApp page and the extension background * How to use `BackgroundConnectionHandler` to manage **discovery**, **ECDH key exchange**, and **encrypted sessions** * How to **route wallet method calls** — deciding which need user approval and which auto-execute * How to extend `BaseWallet` to implement your own **wallet methods** (accounts, transactions, fees) * How to handle **session lifecycle** — trusted origins, cleanup, and state persistence across service worker restarts ## Overview[​](#overview "Direct link to Overview") A wallet extension has three components that use the SDK: | Component | SDK class | Responsibility | | ------------------------- | -------------------------------- | -------------------------------------------------- | | Content script | `ContentScriptConnectionHandler` | Relay messages between page and background | | Background service worker | `BackgroundConnectionHandler` | Manage sessions, route messages, trigger approvals | | Offscreen document | `BaseWallet` subclass | Execute wallet methods (sendTx, simulateTx, etc.) | ## Content Script[​](#content-script "Direct link to Content Script") The content script is the simplest piece — it relays messages between the page and the background service worker, and never sees encryption keys. Create a `ContentScriptConnectionHandler` with a transport that provides `sendToBackground(message)` and `addBackgroundListener(handler)`, then call `handler.start()`. See [`test-extension/src/content-script.ts`](https://github.com/AztecProtocol/aztec-packages/tree/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/content-script.ts) for the full implementation. ## Background Service Worker[​](#background-service-worker "Direct link to Background Service Worker") The background script is where most of the SDK integration happens. ### Configuration[​](#configuration "Direct link to Configuration") Define your wallet's identity in a config object with `walletId`, `name`, `icon`, and `chainId`. See [`test-extension/src/config.ts`](https://github.com/AztecProtocol/aztec-packages/tree/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/config.ts). ### Transport[​](#transport "Direct link to Transport") The background transport sends messages to content scripts via `chrome.tabs.sendMessage` and filters incoming messages — only discovery, key exchange, and encrypted wallet messages reach the handler (popup and storage proxy messages are filtered out). See the transport setup in [`test-extension/src/background.ts`](https://github.com/AztecProtocol/aztec-packages/tree/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/background.ts). ### Handler Initialization[​](#handler-initialization "Direct link to Handler Initialization") Create the handler with your config, transport, and callbacks: ``` const handler = new BackgroundConnectionHandler(WALLET_CONFIG, transport, callbacks); handler.initialize(); ``` ## Callbacks[​](#callbacks "Direct link to Callbacks") The SDK provides four optional callbacks at different protocol stages. The tutorial uses three (the fourth, `onSessionTerminated`, fires when a session ends and is useful for cleanup): ### onPendingDiscovery[​](#onpendingdiscovery "Direct link to onPendingDiscovery") Called when a dApp broadcasts a discovery request. You decide whether to show an approval UI or auto-approve: callbacks ``` const callbacks: BackgroundConnectionCallbacks = { onPendingDiscovery: async (discovery) => { log.debug( "[background] Pending discovery:", discovery.requestId, "from", discovery.origin, ); // Clean up stale sessions from this tab (e.g. page refresh creates a new // discovery while the old session is still in activeSessions). for (const session of handler.getActiveSessions()) { if (session.tabId === discovery.tabId) { log.debug( "[background] Terminating stale session for tab:", discovery.tabId, session.sessionId, ); capabilitiesApprovedSessions.delete(session.sessionId); queuedMessages.delete(session.sessionId); handler.terminateSession(session.sessionId); } } // Deduplicate: reject any existing discovery from the same tab const existing = handler .getPendingDiscoveries() .find( (d) => d.tabId === discovery.tabId && d.requestId !== discovery.requestId, ); if (existing) { handler.rejectDiscovery(existing.requestId); } // Auto-approve if origin is already trusted (reconnection after page refresh) if (await isTrustedOrigin(discovery.origin, discovery.appId)) { log.debug( "[background] Auto-approving trusted origin:", discovery.origin, ); handler.approveDiscovery(discovery.requestId); return; } updateBadge(); openPopupWithFallback(); }, onSessionEstablished: async (session: ActiveSession) => { log.debug("[background] Session established:", session.sessionId); // Auto-confirm if origin is already trusted (skip emoji verification) if (await isTrustedOrigin(session.origin, session.appId)) { log.debug( "[background] Auto-confirming trusted session:", session.sessionId, ); // Pre-approve capabilities if previously granted (enables seamless reconnect) const savedCaps = await getStoredCapabilities( session.origin, session.appId, ); if (savedCaps) { capabilitiesApprovedSessions.add(session.sessionId); } // Flush any queued messages immediately (same logic as CONFIRM_SESSION handler) const queued = queuedMessages.get(session.sessionId) ?? []; queuedMessages.delete(session.sessionId); for (const { session: s, message: msg } of queued) { processWalletMessage(s, msg); } pushStateToPopup(); return; } // New origin — require emoji verification log.debug( "[background] Awaiting emoji verification for:", session.sessionId, ); // SDK automatically removes the discovery when key exchange completes. // Show emojis in approvals so user can compare with the webapp pendingSessionVerifications.push({ sessionId: session.sessionId, origin: session.origin, appId: session.appId, verificationHash: session.verificationHash, timestamp: Date.now(), }); updateBadge(); // Only open popup if not already connected — calling openPopup() on an // already-open popup rejects, and the fallback creates a second window // that steals the popupPort from the original. if (!popupPort) { openPopupWithFallback(); } pushStateToPopup(); }, /** * Handles wallet method calls from the ExtensionWallet proxy. * Messages are queued while emoji verification is pending — the extension * user must confirm before any dApp calls are processed. */ onWalletMessage: async (session: ActiveSession, message: any) => { log.debug( "[background] Wallet message:", message.type, "from session:", session.sessionId, ); // Block wallet messages until the user confirms emoji verification in the extension. // The dApp's calls (e.g. getAccounts) will wait until the extension user approves. const awaitingVerification = pendingSessionVerifications.some( (v) => v.sessionId === session.sessionId, ); if (awaitingVerification) { log.debug( "[background] Session awaiting verification, queuing message:", message.type, ); const queue = queuedMessages.get(session.sessionId) ?? []; queue.push({ session, message }); queuedMessages.set(session.sessionId, queue); return; } await processWalletMessage(session, message); }, }; ``` > [Source code: docs/examples/webapp-tutorial/test-extension/src/background.ts#L744-L884](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/background.ts#L744-L884) Key responsibilities: * **Stale session cleanup** — terminate sessions from the same tab (handles page refresh) * **Deduplication** — reject duplicate discoveries from the same tab * **Trusted origins** — auto-approve if the user previously connected to this origin * **Show UI** — open the popup for the user to approve new connections Call `handler.approveDiscovery(requestId)` to proceed with key exchange, or `handler.rejectDiscovery(requestId)` to deny. ### onSessionEstablished[​](#onsessionestablished "Direct link to onSessionEstablished") Called after ECDH key exchange completes. The session has a `verificationHash` for emoji verification: * For **trusted origins**: auto-confirm the session and restore saved capabilities * For **new origins**: store the session as pending verification and show the emoji grid in the popup ### onWalletMessage[​](#onwalletmessage "Direct link to onWalletMessage") Called when a dApp sends an encrypted wallet method call. Messages arriving before emoji verification are queued and flushed after the user confirms: on-wallet-message ``` /** * Handles wallet method calls from the ExtensionWallet proxy. * Messages are queued while emoji verification is pending — the extension * user must confirm before any dApp calls are processed. */ onWalletMessage: async (session: ActiveSession, message: any) => { log.debug( "[background] Wallet message:", message.type, "from session:", session.sessionId, ); // Block wallet messages until the user confirms emoji verification in the extension. // The dApp's calls (e.g. getAccounts) will wait until the extension user approves. const awaitingVerification = pendingSessionVerifications.some( (v) => v.sessionId === session.sessionId, ); if (awaitingVerification) { log.debug( "[background] Session awaiting verification, queuing message:", message.type, ); const queue = queuedMessages.get(session.sessionId) ?? []; queue.push({ session, message }); queuedMessages.set(session.sessionId, queue); return; } await processWalletMessage(session, message); }, ``` > [Source code: docs/examples/webapp-tutorial/test-extension/src/background.ts#L850-L882](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/background.ts#L850-L882) ## Message Routing[​](#message-routing "Direct link to Message Routing") The core routing logic decides which methods need user approval: approval-check ``` // sendTx always requires approval — it's a state-changing operation. // batch requires approval only if it contains a sendTx (e.g. BatchCall.send()). // Read-only batches (simulateTx, executeUtility, etc.) auto-execute. const needsApproval = message.type === "sendTx" || (message.type === "batch" && Array.isArray(message.args?.[0]) && message.args[0].some((m: any) => m.name === "sendTx")); if (needsApproval) { // Extract `from` address from the method args: // - sendTx args: [executionPayload, sendOptions] → from is in sendOptions // - batch args: [methodsArray] → find the sendTx entry and get from from its opts let from = ""; if (message.type === "sendTx") { from = message.args?.[1]?.from?.toString?.() || ""; } else if (message.type === "batch") { const sendTxMethod = message.args[0].find( (m: any) => m.name === "sendTx", ); from = sendTxMethod?.args?.[1]?.from?.toString?.() || ""; } const pending: PendingTransaction = { sessionId: session.sessionId, messageId: message.messageId, method: message.type, args: message.args, from, origin: session.origin, timestamp: Date.now(), }; pendingTransactions.push(pending); updateBadge(); log.debug("[background] Transaction pending approval:", pending.method); openPopupWithFallback(); return; } ``` > [Source code: docs/examples/webapp-tutorial/test-extension/src/background.ts#L582-L623](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/background.ts#L582-L623) The approval matrix: | Method | Approval needed? | Why | | --------------------------- | ---------------- | ---------------------------------- | | `sendTx` | Yes | State-changing transaction | | `batch` containing `sendTx` | Yes | Contains state-changing calls | | `requestCapabilities` | Yes (first time) | Grants permissions to the dApp | | `simulateTx` | No | Read-only simulation | | `executeUtility` | No | Unconstrained function call | | `getAccounts` | No | Returns account info | | `registerContract` | No | Registers contract with PXE | | Everything else | No | Read-only or background operations | ## Sending Responses[​](#sending-responses "Direct link to Sending Responses") Every wallet message must get a response. Use `handler.sendResponse()` — it encrypts and sends via the secure channel: ``` // Success response await handler.sendResponse(session.sessionId, { messageId: message.messageId, result: someResult, walletId: WALLET_CONFIG.walletId, }); // Error response await handler.sendResponse(session.sessionId, { messageId: message.messageId, error: 'Something went wrong', walletId: WALLET_CONFIG.walletId, }); ``` For auto-executing methods, forward to the offscreen document and return the result: send-to-offscreen ``` /** * Persistent port to the offscreen document. * Unlike chrome.runtime.sendMessage() (broadcast), a port gives us: * - Point-to-point channel (no broadcast to all extension pages) * - Automatic disconnect detection (offscreen teardown) * - No `return true`/`false` landmine for async responses */ let offscreenPort: chrome.runtime.Port | null = null; const pendingOffscreenCalls = new Map< string, { resolve: (value: any) => void; reject: (error: Error) => void; timer: ReturnType; } >(); let offscreenMessageId = 0; const OFFSCREEN_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes function connectOffscreenPort() { const port = chrome.runtime.connect({ name: "offscreen" }); offscreenPort = port; port.onMessage.addListener((message: any) => { // Progress updates — relay to popup if (message.type === "task-progress") { const runningTask = backgroundTasks.find((t) => t.status === "running"); if (runningTask) { runningTask.progress = message.stage; notifyPopup({ type: "task-update", task: { ...runningTask } }); } return; } // Request/response correlation const pending = pendingOffscreenCalls.get(message.messageId); if (!pending) return; pendingOffscreenCalls.delete(message.messageId); clearTimeout(pending.timer); if (message.success) { pending.resolve(message.result); } else { pending.reject(new Error(message.error || "Unknown error")); } }); port.onDisconnect.addListener(() => { log.debug("[background] Offscreen port disconnected"); offscreenPort = null; // Reject all pending calls — sendToOffscreen will retry for (const [id, pending] of pendingOffscreenCalls) { clearTimeout(pending.timer); pending.reject(new Error("Offscreen port disconnected")); pendingOffscreenCalls.delete(id); } }); } /** * Sends a message to the offscreen document and waits for response. * Uses a persistent port with request/response correlation via messageId. * Retries once if the offscreen document was torn down. (#15) */ async function sendToOffscreen(message: any, _retried = false): Promise { await ensureOffscreenDocument(); if (!offscreenPort) { connectOffscreenPort(); } const messageId = `off-${++offscreenMessageId}`; return new Promise((resolve, reject) => { const timer = setTimeout(() => { pendingOffscreenCalls.delete(messageId); reject(new Error(`Offscreen call timed out: ${message.type}`)); }, OFFSCREEN_TIMEOUT_MS); pendingOffscreenCalls.set(messageId, { resolve, reject, timer }); try { if (!offscreenPort) { throw new Error("Offscreen port not connected"); } offscreenPort.postMessage({ ...message, messageId }); } catch (err: unknown) { pendingOffscreenCalls.delete(messageId); clearTimeout(timer); // Port may have disconnected — retry once if (!_retried) { log.warn("[background] Offscreen port send failed, retrying..."); offscreenPort = null; offscreenCreating = null; sendToOffscreen(message, true).then(resolve, reject); } else { reject(err instanceof Error ? err : new Error(String(err))); } } }); } ``` > [Source code: docs/examples/webapp-tutorial/test-extension/src/background.ts#L72-L176](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/background.ts#L72-L176) ## Extending BaseWallet[​](#extending-basewallet "Direct link to Extending BaseWallet") The offscreen document hosts your wallet implementation. Extend `BaseWallet` to get `sendTx`, `simulateTx`, `batch`, and other methods for free: wallet-instance ``` /** Single wallet class used for all operations. (#18, #20) */ import type { BaseWallet } from '@aztec/wallet-sdk/base-wallet'; /** * The wallet instance holds a BaseWallet subclass with an additional * registerAccount method for tracking which accounts we can sign for. * BaseWallet is dynamically imported at runtime; using `import type` gives * us the type without a runtime dependency. (#20) */ type OffscreenWalletType = BaseWallet & { registerAccount(address: string, account: Account): void }; let walletInstance: OffscreenWalletType | null = null; /** * Creates a SponsoredFPC contract instance from its artifact and well-known salt. * Shared between OffscreenWallet.ensureSponsoredFPC() and handleDeployAccount(). */ async function getSponsoredFPCInstance() { const { Fr, SponsoredFPCContract, SPONSORED_FPC_SALT, getContractInstanceFromInstantiationParams } = await getAztecDeploy(); return getContractInstanceFromInstantiationParams( SponsoredFPCContract.artifact, { salt: new Fr(SPONSORED_FPC_SALT) }, ); } async function getWallet() { if (walletInstance) return walletInstance; const { BaseWallet, AztecAddress, SignerlessAccount } = await getAztecWallet(); const { pxe, node } = await ensurePXE(); // AccountFeePaymentMethodOptions.EXTERNAL = 0 — fee is paid by an external FPC const EXTERNAL_FEE_PAYMENT = 0; class OffscreenWallet extends BaseWallet { protected minFeePadding = 1.0; // 100% padding for fee estimation variance private accounts: Map = new Map(); private sponsoredFPCAddress: any | null = null; constructor(pxeInstance: PXE, aztecNode: AztecNode) { super(pxeInstance, aztecNode); } registerAccount(address: string, account: Account) { this.accounts.set(address, account); } protected async getAccountFromAddress(address: any): Promise { if (address.equals(AztecAddress.ZERO)) { return new SignerlessAccount(); } const key = address.toString(); const account = this.accounts.get(key); if (!account) { throw new Error(`Account not found for address: ${key}`); } return account; } async getAccounts() { return Array.from(this.accounts.entries()).map(([, acc]) => ({ alias: '', item: acc.getAddress(), })); } /** Lazily registers the SponsoredFPC contract and caches its address. */ private async ensureSponsoredFPC() { if (this.sponsoredFPCAddress) return this.sponsoredFPCAddress; const { SponsoredFPCContract } = await getAztecDeploy(); const sponsoredFPCInstance = await getSponsoredFPCInstance(); await this.registerContract(sponsoredFPCInstance, SponsoredFPCContract.artifact); this.sponsoredFPCAddress = sponsoredFPCInstance.address; return this.sponsoredFPCAddress; } /** * Always uses SponsoredFPC for fee payment, mirroring the deployment flow. * The tutorial wallet doesn't hold fee juice, so every tx is sponsor-paid. * * If the execution payload already has a feePayer (e.g. DeployAccountMethod * embeds SponsoredFPC in its own payload), we skip injecting a wallet-level * payment method to avoid calling sponsor_unconditionally() twice, which * would trigger "Cannot enter the revertible phase twice". */ protected async completeFeeOptions(from: any, feePayer?: any, gasSettings?: any) { const base = await super.completeFeeOptions(from, feePayer, gasSettings); // If the payload already includes a fee payer, don't inject another one if (feePayer) { return { ...base, accountFeePaymentMethodOptions: EXTERNAL_FEE_PAYMENT, }; } const address = await this.ensureSponsoredFPC(); const { SponsoredFeePaymentMethod } = await getAztecDeploy(); return { ...base, walletFeePaymentMethod: new SponsoredFeePaymentMethod(address), accountFeePaymentMethodOptions: EXTERNAL_FEE_PAYMENT, }; } /** * Overrides sendTx to auto-extract auth witnesses from offchain effects. * * dApps like gregoswap don't explicitly create auth witnesses. Instead, they * expect the wallet to handle it: simulate with a stub account (which passes * all auth checks), extract the authorization requests emitted by * `#[authorize_once]` in Noir contracts, sign them, and include them in the * real transaction. */ async sendTx(executionPayload: any, opts: any): Promise { if (executionPayload.authWitnesses.length === 0 && opts.from && !opts.from.equals(AztecAddress.ZERO)) { try { await this.extractAndInjectAuthWitnesses(executionPayload, opts.from, opts.fee?.gasSettings); } catch (err: any) { log.error('[offscreen] Auth witness extraction failed, proceeding without:', err.message, err.stack); } } return super.sendTx(executionPayload, opts); } /** * Simulates the tx with a stub account to collect offchain effects, * parses CallAuthorizationRequest objects, and creates real auth witnesses. */ private async extractAndInjectAuthWitnesses(executionPayload: any, from: any, feeGasSettings?: any) { const { Fr, getContractInstanceFromInstantiationParams } = await getAztecCore(); // Step 1: Create a stub account that passes all auth checks unconditionally log.info('[offscreen] Step 1: Loading stub account module...'); const realAccount = await this.getAccountFromAddress(from); const originalAddress = realAccount.getCompleteAddress(); log.info('[offscreen] Got complete address:', originalAddress.address.toString()); const { createStubAccount, getStubAccountContractArtifact } = await import('@aztec/accounts/stub/lazy'); log.info('[offscreen] Loaded @aztec/accounts/stub/lazy'); const stubArtifact = await getStubAccountContractArtifact(); log.info('[offscreen] Loaded stub artifact:', stubArtifact.name); const stubAccount = createStubAccount(originalAddress); const stubInstance = await getContractInstanceFromInstantiationParams(stubArtifact, { salt: Fr.random() }); log.info('[offscreen] Created stub account and instance'); // Step 2: Simulate with the stub account swapped in via PXE overrides log.info('[offscreen] Step 2: Simulating tx with stub account...'); const feeOptions = await this.completeFeeOptions(from, executionPayload.feePayer, feeGasSettings); const chainInfo = await this.getChainInfo(); const txRequest = await stubAccount.createTxExecutionRequest( executionPayload, feeOptions.gasSettings, chainInfo, { txNonce: Fr.random(), cancellable: false, feePaymentMethodOptions: feeOptions.accountFeePaymentMethodOptions }, ); log.info('[offscreen] Created tx execution request, simulating...'); const simResult = await this.pxe.simulateTx(txRequest, { simulatePublic: true, skipTxValidation: true, skipFeeEnforcement: true, overrides: { contracts: { [from.toString()]: { instance: stubInstance, artifact: stubArtifact } } }, scopes: [from], }); log.info('[offscreen] Simulation succeeded'); // Step 3: Extract auth witness requests from offchain effects log.info('[offscreen] Step 3: Extracting offchain effects...'); const { collectOffchainEffects } = await import('@aztec/stdlib/tx'); const { CallAuthorizationRequest } = await import('@aztec/aztec.js/authorization'); if (!simResult.privateExecutionResult) { log.warn('[offscreen] No privateExecutionResult in simulation result'); return; } const effects = collectOffchainEffects(simResult.privateExecutionResult); log.info(`[offscreen] Found ${effects.length} offchain effect(s)`); // Pre-filter by CallAuthorizationRequest selector (matching e2e test pattern) const callAuthSelector = await CallAuthorizationRequest.getSelector(); const authEffects = effects.filter((e: any) => e.data.length > 0 && e.data[0].equals(callAuthSelector.toField()), ); log.info(`[offscreen] ${authEffects.length} are CallAuthorizationRequest(s)`); // Step 4: Create auth witnesses from parsed authorization requests let count = 0; for (const effect of authEffects) { const authRequest = await CallAuthorizationRequest.fromFields(effect.data); log.info(`[offscreen] Auth request: consumer=${effect.contractAddress.toString()}, innerHash=${authRequest.innerHash.toString()}`); const wit = await this.createAuthWit(from, { consumer: effect.contractAddress, innerHash: authRequest.innerHash, }); executionPayload.authWitnesses.push(wit); count++; log.info(`[offscreen] Created auth witness #${count}: messageHash=${wit.requestHash.toString()}`); } log.info(`[offscreen] Auth witness extraction complete: ${count} witness(es) from ${effects.length} effect(s)`); } } walletInstance = new OffscreenWallet(pxe, node); return walletInstance; } ``` > [Source code: docs/examples/webapp-tutorial/test-extension/src/offscreen/offscreen.ts#L157-L369](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/offscreen/offscreen.ts#L157-L369) ### What You Must Implement[​](#what-you-must-implement "Direct link to What You Must Implement") | Method | Purpose | | -------------------------------- | ------------------------------------------ | | `getAccountFromAddress(address)` | Look up an `Account` by its `AztecAddress` | | `getAccounts()` | Return all accounts (with aliases) | Custom Fee Payment `completeFeeOptions` has a default implementation that uses the sender's fee juice balance. Override it to inject a custom fee payment strategy (e.g., `SponsoredFPC`). The tutorial wallet overrides this — see [`test-extension/src/offscreen/offscreen.ts`](https://github.com/AztecProtocol/aztec-packages/tree/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/offscreen/offscreen.ts) for the implementation. ### What BaseWallet Provides[​](#what-basewallet-provides "Direct link to What BaseWallet Provides") | Method | What it does | | -------------------------------------- | ---------------------------------------------------------------------------------- | | `sendTx(payload, opts)` | Completes fee options, creates execution request, generates proof, submits to node | | `simulateTx(payload, opts)` | Simulates without proving | | `executeUtility(call, opts)` | Executes an unconstrained function call | | `batch(methods)` | Batches multiple wallet method calls | | `createAuthWit(from, intent)` | Creates authorization witnesses | | `registerContract(instance, artifact)` | Registers contracts with PXE | | `getChainInfo()` | Returns chain ID and version | note `requestCapabilities()` is part of the `Wallet` interface but throws `"Not implemented"` in BaseWallet by default. The `BackgroundConnectionHandler` handles capability requests for extension wallets — see [Callbacks](#callbacks) above. ### Dynamic Method Dispatch[​](#dynamic-method-dispatch "Direct link to Dynamic Method Dispatch") The offscreen document handles all wallet methods dynamically using `WalletSchema` for type-safe argument parsing: wallet-method-handler ``` /** * Handles wallet method calls from the ExtensionWallet proxy via the SDK protocol. * * Serialization notes: * 1. ARGS: Arrive as plain JSON. We use WalletSchema to parse them back into * proper Aztec types (AztecAddress, Fr, ExecutionPayload, etc.). * 2. RESULT: Contains class instances that lose prototypes through Chrome messaging. * We serialize with jsonStringify before returning. */ async function handleWalletMethod(method: string, args: any[]): Promise { log.debug('[offscreen] Handling wallet method:', method); const wallet = await getWallet(); // Dynamic dispatch: the wallet protocol sends method names as strings. // Cast to Record for dynamic access since TypeScript can't know the method at compile time. const walletObj = wallet as unknown as Record any>; if (typeof walletObj[method] !== 'function') { throw new Error(`Unknown wallet method: ${method}`); } const { WalletSchema, jsonStringify, schemaHasMethod } = await getAztecWallet(); // Parse args through WalletSchema to reconstruct proper Aztec types (Buffer, Fr, etc.) // from their JSON representations. The schema's .parameters() returns a zod tuple that // requires all positional elements even if some are optional. Pad with undefined so the // tuple length matches and the parse succeeds. let parsedArgs: any[] = args || []; if (schemaHasMethod(WalletSchema, method)) { const schema = WalletSchema[method as keyof typeof WalletSchema]; const paramSchema = schema.parameters(); const expectedLength = (paramSchema as any)?._def?.items?.length ?? 0; const paddedArgs = [...(args || [])]; while (paddedArgs.length < expectedLength) { paddedArgs.push(undefined); } try { parsedArgs = await paramSchema.parseAsync(paddedArgs); } catch (parseErr: any) { log.warn('[offscreen] Args parse warning for', method, ':', parseErr.message); parsedArgs = args || []; } } // Report initial progress for long-running methods so the popup shows something // before the PXE log matchers kick in const longRunningMethods = ['sendTx', 'simulateTx', 'profileTx']; if (longRunningMethods.includes(method)) { reportProgress(`Starting ${method}...`); } const result = await walletObj[method](...parsedArgs); // Serialize to JSON-safe format before returning through Chrome messaging const jsonSafe = JSON.parse(jsonStringify(result)); log.debug('[offscreen] Wallet method completed:', method); return jsonSafe; } ``` > [Source code: docs/examples/webapp-tutorial/test-extension/src/offscreen/offscreen.ts#L460-L519](https://github.com/AztecProtocol/aztec-packages/blob/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/offscreen/offscreen.ts#L460-L519) ## Session Lifecycle[​](#session-lifecycle "Direct link to Session Lifecycle") ### Trusted Origins[​](#trusted-origins "Direct link to Trusted Origins") Store approved origins so returning users get auto-reconnected. When a trusted origin connects, the wallet: 1. Auto-approves discovery (no popup) 2. Auto-confirms the session (no emoji verification) 3. Auto-grants capabilities (if requesting the same set) ### Cleanup[​](#cleanup "Direct link to Cleanup") Sessions are cleaned up when: * **Page refresh** — `onPendingDiscovery` terminates stale sessions from the same tab * **Tab closed** — `chrome.tabs.onRemoved` calls `handler.terminateForTab(tabId)` * **User disconnects** — popup sends `DISCONNECT_SESSION`, which also removes the origin from trusted origins The handler provides cleanup methods: * `handler.terminateSession(sessionId)` — end a specific session * `handler.terminateForTab(tabId)` — end all sessions for a tab * `handler.getPendingDiscoveries()` — list pending discovery requests * `handler.getActiveSessions()` — list active sessions ## State Persistence[​](#state-persistence "Direct link to State Persistence") Service workers restart frequently. Use `chrome.storage.session` to persist critical state (like trusted origins) — it survives service worker restarts but clears when the browser closes. note The `BackgroundConnectionHandler`'s internal state (active sessions, pending discoveries) is **not** persisted — sessions don't survive extension reloads. On restart, dApps will re-discover and reconnect. Trusted origin auto-approve makes this seamless. ## Extension Lifecycle[​](#extension-lifecycle "Direct link to Extension Lifecycle") On install/update, clear pending state since sessions don't survive reloads. On startup, restore persisted state and preload the offscreen document to warm up WASM. See the lifecycle handlers in [`test-extension/src/background.ts`](https://github.com/AztecProtocol/aztec-packages/tree/v5.0.0-rc.2/docs/examples/webapp-tutorial/test-extension/src/background.ts). ## Next steps[​](#next-steps "Direct link to Next steps") * [dApp Integration](/developers/testnet/docs/tutorials/js_tutorials/webapp/wallet-sdk/dapp-integration.md) — See how the other side connects to your wallet * [Wallet Extension Tutorial](/developers/testnet/docs/tutorials/js_tutorials/wallet-extension.md) — Full step-by-step guide to building a wallet extension, including accounts, transactions, and approval UIs --- # Testing Governance Rollup Upgrade on Local Network This guide walks through deploying a new rollup and executing a governance upgrade on a local Aztec network. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * [Aztec tooling](/developers/testnet/getting_started_on_local_network.md) * Node.js and yarn ## Local Network Governance Timing[​](#local-network-governance-timing "Direct link to Local Network Governance Timing") The default governance configuration for local networks: | Parameter | Value | Description | | -------------- | ---------------- | ------------------------------------------- | | votingDelay | 60 seconds | Time before voting starts | | votingDuration | 1 hour | Voting period length | | executionDelay | 60 seconds | Delay after voting ends before execution | | gracePeriod | 7 days | Window to execute after becoming executable | | lockDelay | 30 days | Token lock period for proposers | | lockAmount | 1,000,000 tokens | Tokens locked when proposing | *** ## Step 1: Start Local Network[​](#step-1-start-local-network "Direct link to Step 1: Start Local Network") Ensure you are on the correct Aztec version: ``` aztec-up install 5.0.0-rc.2 ``` ``` aztec start --local-network ``` Wait for output showing deployed contract addresses. To get the **Registry Address** and other L1 contract addresses, query the running node: ``` curl -s http://localhost:8080 -X POST -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"aztec_getNodeInfo","params":[],"id":1}' | jq '.result.l1ContractAddresses' ``` Note the `registryAddress` from the output. *** ## Step 2: Clone and Set Up l1-contracts[​](#step-2-clone-and-set-up-l1-contracts "Direct link to Step 2: Clone and Set Up l1-contracts") Clone the l1-contracts repo and checkout the version matching your Aztec installation. Run `aztec --version` to find your version: ``` git clone https://github.com/AztecProtocol/l1-contracts.git cd l1-contracts git checkout 5.0.0-rc.2 ``` Install dependencies and set up the build environment: ``` # Install forge dependencies mkdir -p lib cd lib git clone --depth 1 https://github.com/foundry-rs/forge-std forge-std git clone --depth 1 https://github.com/OpenZeppelin/openzeppelin-contracts openzeppelin-contracts cd .. # Install solc (uses forge's built-in svm). The Aztec installer ships # Foundry as `aztec-forge`/`aztec-cast`/`aztec-anvil` -- substitute your # own `forge` install if you have one. aztec-forge build --use 0.8.30 src/core/libraries/ConstantsGen.sol cp ~/.svm/0.8.30/solc-0.8.30 ./solc-0.8.30 # Copy the HonkVerifier to the generated directory (required for build) mkdir -p generated cp src/HonkVerifier.sol generated/HonkVerifier.sol echo '{}' > generated/default.json # Remove zkpassport-dependent files (not needed for rollup deployment) rm -f src/mock/StakingAssetHandler.sol rm -rf src/mock/staking_asset_handler/ ``` *** ## Step 3: Set Environment Variables[​](#step-3-set-environment-variables "Direct link to Step 3: Set Environment Variables") ``` # Anvil's default account 0 export PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 export DEPLOYER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 # Replace with actual address from Step 1 export REGISTRY_ADDRESS=0x... # L1 RPC export L1_RPC_URL=http://localhost:8545 export L1_CHAIN_ID=31337 # Rollup configuration (local network defaults) export AZTEC_SLOT_DURATION=36 export AZTEC_EPOCH_DURATION=16 export AZTEC_TARGET_COMMITTEE_SIZE=48 export AZTEC_LAG_IN_EPOCHS_FOR_VALIDATOR_SET=2 export AZTEC_LAG_IN_EPOCHS_FOR_RANDAO=2 export AZTEC_INBOX_LAG=2 export AZTEC_PROOF_SUBMISSION_EPOCHS=2 export AZTEC_LOCAL_EJECTION_THRESHOLD=0 export AZTEC_SLASHING_ROUND_SIZE_IN_EPOCHS=1 export AZTEC_SLASHING_LIFETIME_IN_ROUNDS=10 export AZTEC_SLASHING_EXECUTION_DELAY_IN_ROUNDS=1 export AZTEC_SLASHING_OFFSET_IN_ROUNDS=0 export AZTEC_SLASHER_ENABLED=false export AZTEC_SLASHING_VETOER=0x0000000000000000000000000000000000000000 export AZTEC_SLASHING_DISABLE_DURATION=0 export AZTEC_MANA_TARGET=100000000 export AZTEC_EXIT_DELAY_SECONDS=0 export AZTEC_PROVING_COST_PER_MANA=0 export AZTEC_SLASH_AMOUNT_SMALL=0 export AZTEC_SLASH_AMOUNT_MEDIUM=0 export AZTEC_SLASH_AMOUNT_LARGE=0 export AZTEC_INITIAL_ETH_PER_FEE_ASSET=10000000 ``` *** ## Step 4: Deploy New Rollup[​](#step-4-deploy-new-rollup "Direct link to Step 4: Deploy New Rollup") ``` aztec-forge script script/deploy/DeployRollupForUpgrade.s.sol:DeployRollupForUpgrade \ --rpc-url $L1_RPC_URL \ --broadcast \ --private-key $PRIVATE_KEY ``` Note the **new rollup address** from the JSON output. ``` export NEW_ROLLUP_ADDRESS=0x... ``` *** ## Step 5: Deploy Governance Payload[​](#step-5-deploy-governance-payload "Direct link to Step 5: Deploy Governance Payload") **Important:** Place flags before the contract path to avoid argument parsing issues. ``` cd l1-contracts aztec-forge create \ --rpc-url $L1_RPC_URL \ --private-key $PRIVATE_KEY \ --broadcast \ test/governance/scenario/RegisterNewRollupVersionPayload.sol:RegisterNewRollupVersionPayload \ --constructor-args $REGISTRY_ADDRESS $NEW_ROLLUP_ADDRESS ``` Note the **payload address** from the output. ``` export PAYLOAD_ADDRESS=0x... ``` *** ## Step 6: Deposit Governance Tokens[​](#step-6-deposit-governance-tokens "Direct link to Step 6: Deposit Governance Tokens") Mint and deposit tokens to get voting power. You need at least 1,000,000 tokens (1e24 wei) to propose: ``` aztec deposit-governance-tokens \ -r $REGISTRY_ADDRESS \ --recipient $DEPLOYER_ADDRESS \ --amount "2000000000000000000000000" \ --mint \ --l1-rpc-urls $L1_RPC_URL \ -c $L1_CHAIN_ID \ --private-key $PRIVATE_KEY ``` *** ## Step 7: Advance Time for Token Checkpoint[​](#step-7-advance-time-for-token-checkpoint "Direct link to Step 7: Advance Time for Token Checkpoint") Critical Step Tokens must be deposited **before** the proposal is created. The governance contract snapshots voting power at the proposal creation timestamp. If your deposit checkpoint timestamp >= proposal creation timestamp, your voting power will be **0** and the proposal will be rejected. Advance Anvil's time to ensure the checkpoint is in the past when the proposal is created: ``` # Get current timestamp and add 120 seconds CURRENT_TS=$(cast block latest --rpc-url $L1_RPC_URL --json | jq -r '.timestamp') TARGET_TS=$((CURRENT_TS + 120)) cast rpc anvil_setNextBlockTimestamp $TARGET_TS --rpc-url $L1_RPC_URL cast rpc anvil_mine 1 --rpc-url $L1_RPC_URL ``` Verify the time has advanced: ``` NEW_TS=$(cast block latest --rpc-url $L1_RPC_URL --json | jq -r '.timestamp') echo "New timestamp: $NEW_TS (should be > $CURRENT_TS)" ``` note `anvil_increaseTime` may not reliably update block timestamps. For consistent results, always use `anvil_setNextBlockTimestamp` with an explicit timestamp. *** ## Step 8: Create Proposal[​](#step-8-create-proposal "Direct link to Step 8: Create Proposal") ``` aztec propose-with-lock \ -r $REGISTRY_ADDRESS \ -p $PAYLOAD_ADDRESS \ --l1-rpc-urls $L1_RPC_URL \ -c $L1_CHAIN_ID \ --private-key $PRIVATE_KEY \ --json ``` Note the **proposal ID** from output. ``` export PROPOSAL_ID=0 ``` *** ## Step 9: Advance Time Past Voting Delay[​](#step-9-advance-time-past-voting-delay "Direct link to Step 9: Advance Time Past Voting Delay") The proposal must transition from Pending to Active (votingDelay = 60 seconds): ``` # Get current timestamp and add 120 seconds (buffer over 60s voting delay) CURRENT_TS=$(cast block latest --rpc-url $L1_RPC_URL --json | jq -r '.timestamp') TARGET_TS=$((CURRENT_TS + 120)) cast rpc anvil_setNextBlockTimestamp $TARGET_TS --rpc-url $L1_RPC_URL cast rpc anvil_mine 1 --rpc-url $L1_RPC_URL ``` Verify the proposal is now Active (state 1): ``` # Get governance address from node info or use the one from Step 1 cast call "getProposalState(uint256)(uint8)" $PROPOSAL_ID --rpc-url $L1_RPC_URL # Expected output: 1 (Active) ``` *** ## Step 10: Vote on Proposal[​](#step-10-vote-on-proposal "Direct link to Step 10: Vote on Proposal") ``` aztec vote-on-governance-proposal \ -p $PROPOSAL_ID \ --in-favor yea \ --wait false \ -r $REGISTRY_ADDRESS \ --l1-rpc-urls $L1_RPC_URL \ -c $L1_CHAIN_ID \ --private-key $PRIVATE_KEY ``` Verify the vote was recorded with your voting power. The CLI output should show non-zero `summedBallot yea` values. If it shows `[0]`, your checkpoint timing was incorrect (see Troubleshooting). *** ## Step 11: Advance Time Past Voting Duration + Execution Delay[​](#step-11-advance-time-past-voting-duration--execution-delay "Direct link to Step 11: Advance Time Past Voting Duration + Execution Delay") Voting duration is 1 hour (3600s) and execution delay is 60 seconds: ``` # Get current timestamp and add 3700 seconds (voting duration + execution delay + buffer) CURRENT_TS=$(cast block latest --rpc-url $L1_RPC_URL --json | jq -r '.timestamp') TARGET_TS=$((CURRENT_TS + 3700)) cast rpc anvil_setNextBlockTimestamp $TARGET_TS --rpc-url $L1_RPC_URL cast rpc anvil_mine 1 --rpc-url $L1_RPC_URL ``` Verify the proposal is now Executable (state 3): ``` cast call "getProposalState(uint256)(uint8)" $PROPOSAL_ID --rpc-url $L1_RPC_URL # Expected output: 3 (Executable) ``` *** ## Step 12: Execute Proposal[​](#step-12-execute-proposal "Direct link to Step 12: Execute Proposal") ``` aztec execute-governance-proposal \ -p $PROPOSAL_ID \ -r $REGISTRY_ADDRESS \ --wait false \ --l1-rpc-urls $L1_RPC_URL \ -c $L1_CHAIN_ID \ --private-key $PRIVATE_KEY ``` ## Step 13: Verify the Upgrade[​](#step-13-verify-the-upgrade "Direct link to Step 13: Verify the Upgrade") Confirm the new rollup is now the canonical rollup: ``` # Check the canonical rollup address (should match NEW_ROLLUP_ADDRESS) cast call $REGISTRY_ADDRESS "getCanonicalRollup()(address)" --rpc-url $L1_RPC_URL # Check the number of rollup versions (should be 2) cast call $REGISTRY_ADDRESS "numberOfVersions()(uint256)" --rpc-url $L1_RPC_URL ``` *** ## Helper Commands[​](#helper-commands "Direct link to Helper Commands") ### Set Anvil timestamp directly[​](#set-anvil-timestamp-directly "Direct link to Set Anvil timestamp directly") If time advancement isn't working as expected, set the timestamp explicitly: ``` # Get the target timestamp (current + desired seconds) cast rpc anvil_setNextBlockTimestamp --rpc-url $L1_RPC_URL cast rpc anvil_mine 1 --rpc-url $L1_RPC_URL ``` ### Check proposal state[​](#check-proposal-state "Direct link to Check proposal state") ``` # States: 0=Pending, 1=Active, 2=Queued, 3=Executable, 4=Rejected, 5=Executed, 6=Dropped, 7=Expired cast call "getProposalState(uint256)(uint8)" $PROPOSAL_ID --rpc-url $L1_RPC_URL ``` ### Check current block timestamp[​](#check-current-block-timestamp "Direct link to Check current block timestamp") ``` cast block latest --rpc-url $L1_RPC_URL | grep timestamp ``` ### Check L1 addresses[​](#check-l1-addresses "Direct link to Check L1 addresses") ``` aztec get-l1-addresses \ -r $REGISTRY_ADDRESS \ -v canonical \ --l1-rpc-urls $L1_RPC_URL \ -c $L1_CHAIN_ID \ --json ``` ### Debug rollup state[​](#debug-rollup-state "Direct link to Debug rollup state") ``` aztec debug-rollup \ --rollup $NEW_ROLLUP_ADDRESS \ --l1-rpc-urls $L1_RPC_URL \ -c $L1_CHAIN_ID ``` The `--rollup` flag is required; without it the command may fail trying to resolve the default rollup address. *** ## Quick Test (Empty Payload)[​](#quick-test-empty-payload "Direct link to Quick Test (Empty Payload)") If you just want to test the governance flow without deploying a real rollup: ``` cd l1-contracts # Deploy empty payload (no constructor args needed) aztec-forge create \ --rpc-url $L1_RPC_URL \ --private-key $PRIVATE_KEY \ --broadcast \ test/governance/governance/TestPayloads.sol:EmptyPayload # Use the deployed address as PAYLOAD_ADDRESS and continue from Step 6 ``` *** ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### "Governance**CheckpointedUintLib**InsufficientValue"[​](#governancecheckpointeduintlibinsufficientvalue "Direct link to governancecheckpointeduintlibinsufficientvalue") * You need more tokens. The minimum to propose is 1,000,000 tokens (1e24 wei). * Deposit more tokens in Step 6. ### "Governance**CheckpointedUintLib**NotInPast"[​](#governancecheckpointeduintlibnotinpast "Direct link to governancecheckpointeduintlibnotinpast") * Tokens were deposited at or after the proposal creation time. * Advance Anvil's time and mine a block before creating the proposal (Step 7). ### "Proposal is not active"[​](#proposal-is-not-active "Direct link to \"Proposal is not active\"") * The voting delay hasn't passed yet. * Advance time past the votingDelay (60 seconds for local networks). ### "Proposal is not executable"[​](#proposal-is-not-executable "Direct link to \"Proposal is not executable\"") * Either voting period is not complete, or execution delay hasn't passed. * Advance time past votingDuration (1 hour) + executionDelay (60 seconds). ### Forge create fails with "Error accessing local wallet"[​](#forge-create-fails-with-error-accessing-local-wallet "Direct link to Forge create fails with \"Error accessing local wallet\"") * Constructor args may be parsing incorrectly. Place `--constructor-args` at the end of the command, after the contract path. ### Time advancement not working[​](#time-advancement-not-working "Direct link to Time advancement not working") * Anvil may have auto-mined blocks that reset the accumulated time. * Use `anvil_setNextBlockTimestamp` to set an explicit timestamp instead of `anvil_increaseTime`. ### Vote fails without explicit amount[​](#vote-fails-without-explicit-amount "Direct link to Vote fails without explicit amount") * If you see `NotInPast` errors during voting, the CLI may have a bug determining voting power. * Workaround: specify `--vote-amount` explicitly with your deposited token amount. --- # Getting Started on Local Network Get started on your local environment using a local network. If you'd rather deploy to a live network, read the [getting started on testnet guide](/developers/testnet/getting_started_on_testnet.md). The local network is a local development Aztec network running fully on your machine, and interacting with a development Ethereum node. You can develop and deploy on it just like on a testnet or mainnet (when the time comes). The local network makes it faster and easier to develop and test your Aztec applications. The local network always owns the local chain it starts. It deploys its own Aztec protocol contracts to the local Ethereum node and is not a mode for connecting to an existing Aztec network. What's included in the local network: * Local Ethereum network (Anvil) * Deployed Aztec protocol contracts (for L1 and L2) * A set of test accounts with some test tokens to pay fees * On-demand block production via the automine sequencer * Development tools to compile contracts and interact with the network (`aztec` and `aztec-wallet`) This guide will teach you how to install the Aztec local network, run it using the Aztec CLI, and interact with contracts using the wallet CLI. To jump right into the testnet instead, click the `Testnet` tab. To see the whole flow before you start, watch this one-minute walkthrough (find more on the [video lessons](/developers/testnet/docs/resources/video_lessons.md) page): [Get Started on Aztec in Under 60 Seconds](https://www.youtube-nocookie.com/embed/_jgHNdNgFOg) ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Aztec libraries require Node.js version 24. If you have an older version installed, the installer will try to upgrade via [nvm](https://github.com/nvm-sh/nvm) if available. If nvm is not installed, you will need to upgrade Node.js manually (e.g. `nvm install 24` after installing nvm). ### macOS-specific requirements[​](#macos-specific-requirements "Direct link to macOS-specific requirements") * **Homebrew**: [Homebrew](https://brew.sh/) is required for installing dependencies on macOS. * **Bash**: macOS ships with an outdated version of Bash (v3.2) that is known to cause issues with the Aztec installer. Install a modern version with `brew install bash`. Even if you use zsh as your default shell, the installer explicitly invokes `bash`. If the installer still picks up the old version, add the Homebrew `bash` to your `$PATH` or [set it as your default shell](https://support.apple.com/en-gb/guide/terminal/trml113/mac). ## Install and run the local network[​](#install-and-run-the-local-network "Direct link to Install and run the local network") ### Install the Aztec toolchain[​](#install-the-aztec-toolchain "Direct link to Install the Aztec toolchain") Run: ``` VERSION=5.0.0-rc.2 bash -i <(curl -sL https://install.aztec.network) ``` This will install the following tools and add them to your `PATH`: * **aztec** - compiles and tests Aztec contracts and launches various infrastructure subsystems (full local network, sequencer, prover, PXE, etc.) and provides utility commands to interact with the network * **aztec-up** - a version manager for the Aztec toolchain. Use `aztec-up install ` to install a new version, `aztec-up use ` to switch between installed versions, or `aztec-up list` to see installed versions. * **aztec-wallet** - a tool for interacting with the Aztec network * **aztec-bb** - the Barretenberg proving backend * **aztec-nargo** - the Noir compiler and simulator * **aztec-forge**, **aztec-cast**, **aztec-anvil**, **aztec-chisel** - the bundled Foundry tools Foundry, Noir, and Barretenberg are bundled at the versions `aztec` needs. Your own `forge` / `nargo` / `bb` installs still work under their bare names. For syntax highlighting and LSP support while editing contracts, see the [Noir VSCode Extension guide](/developers/testnet/docs/aztec-nr/installation.md). ### Start the local network[​](#start-the-local-network "Direct link to Start the local network") Once these have been installed, to start the local network, run: ``` aztec start --local-network ``` **Congratulations, you have just installed and run the Aztec local network!** ``` /\ | | / \ ___| |_ ___ ___ / /\ \ |_ / __/ _ \/ __| / ____ \ / /| || __/ (__ /_/___ \_\/___|\__\___|\___| ``` In the terminal, you will see some logs: 1. Local network version 2. Contract addresses of rollup contracts 3. PXE (private execution environment) setup logs 4. Initial accounts that are shipped with the local network and can be used in tests You'll know the local network is ready to go when you see something like this: ``` [INFO] Aztec Server listening on port 8080 ``` ## Using the local network test accounts[​](#using-the-local-network-test-accounts "Direct link to Using the local network test accounts") For convenience, the local network comes with 3 initial accounts that are prefunded, helping bootstrap payment of any transaction. To use them, you will need to add them to your pxe/wallet. To add the test accounts in the wallet, run this in another terminal: ``` aztec-wallet import-test-accounts ``` We'll use the first test account, `test0`, throughout to pay for transactions. ## Creating an account in the local network[​](#creating-an-account-in-the-local-network "Direct link to Creating an account in the local network") ``` aztec-wallet create-account -a my-wallet -f test0 ``` info `aztec-wallet` will generate transaction proofs by default. This is not required when sending transactions on the local network, but it is required when sending transactions on the devnet or mainnet. You can turn off proof generation by adding the `--prover none` flag to the command or setting `PXE_PROVER=none`. This will create a new wallet with an account and give it the alias `my-wallet`. Accounts can be referenced with `accounts:`. You will see logs telling you the address, public key, secret key, and more. On successful deployment of the account, you should see something like this: ``` New account: Address: 0x066108a2398e3e2ff53ec4b502e4c2e778c6de91bb889de103d5b4567530d99c Public key: 0x007343da506ea513e6c05ba4d5e92e3c682333d97447d45db357d05a28df0656181e47a6257e644c3277c0b11223b28f2b36c94f9b0a954523de61ac967b42662b60e402f55e3b7384ba61261335040fe4cd52cb0383f559a36eeea304daf67d1645b06c38ee6098f90858b21b90129e7e1fdc4666dd58d13ef8fab845b2211906656d11b257feee0e91a42cb28f46b80aabdc70baad50eaa6bb2c5a7acff4e30b5036e1eb8bdf96fad3c81e63836b8aa39759d11e1637bd71e3fc76e3119e500fbcc1a22e61df8f060004104c5a75b52a1b939d0f315ac29013e2f908ca6bc50529a5c4a2604c754d52c9e7e3dee158be21b7e8008e950991174e2765740f58 Secret key: 0x1c94f8b19e91d23fd3ab6e15f7891fde7ba7cae01d3fa94e4c6afb4006ec0cfb Partial address: 0x2fd6b540a6bb129dd2c05ff91a9c981fb5aa2ac8beb4268f10b3aa5fb4a0fcd1 Salt: 0x0000000000000000000000000000000000000000000000000000000000000000 Init hash: 0x28df95b579a365e232e1c63316375c45a16f6a6191af86c5606c31a940262db2 Deployer: 0x0000000000000000000000000000000000000000000000000000000000000000 Waiting for account contract deployment... Deploy tx hash: 0a632ded6269bda38ad6b54cd49bef033078218b4484b902e326c30ce9dc6a36 Deploy tx fee: 200013616 Account stored in database with aliases last & my-wallet ``` You may need to scroll up as there are some other logs printed after it. You can double check by running `aztec-wallet get-alias accounts:my-wallet`. For simplicity we'll keep using the test account, let's deploy our own test token! ## Deploying a contract[​](#deploying-a-contract "Direct link to Deploying a contract") The local network comes with some contracts that you can deploy and play with. One of these is an example token contract. Deploy it with this: ``` aztec-wallet deploy TokenContractArtifact --from accounts:test0 --args accounts:test0 TestToken TST 18 -a testtoken ``` This takes * the contract artifact as the argument, which is `TokenContractArtifact` * the deployer account, which we used `test0` * the args that the contract constructor takes, which is the `admin` (`accounts:test0`), `name` (`TestToken`), `symbol` (`TST`), and `decimals` (`18`). * an alias `testtoken` (`-a`) so we can easily reference it later with `contracts:testtoken` On successful deployment, you should see something like this: ``` aztec:wallet [INFO] Using wallet with address 0x066108a2398e3e2ff53ec4b502e4c2e778c6de91bb889de103d5b4567530d99c +0ms Contract deployed at 0x15ce68d4be65819fe9c335132f10643b725a9ebc7d86fb22871f6eb8bdbc3abd Contract partial address 0x25a91e546590d77108d7b184cb81b0a0999e8c0816da1a83a2fa6903480ea138 Contract init hash 0x0abbaf0570bf684da355bd9a9a4b175548be6999625b9c8e0e9775d140c78506 Deployment tx hash: 0a8ccd1f4e28092a8fa4d1cb85ef877f8533935c4e94b352a38af73eee17944f Deployment salt: 0x266295eb5da322aba96fbb24f9de10b2ba01575dde846b806f884f749d416707 Deployment fee: 200943060 Contract stored in database with aliases last & testtoken ``` In the next step, let's mint some tokens! ## Minting public tokens[​](#minting-public-tokens "Direct link to Minting public tokens") Call the public mint function like this: ``` aztec-wallet send mint_to_public --from accounts:test0 --contract-address contracts:testtoken --args accounts:test0 100 ``` This takes * the function name as the argument, which is `mint_to_public` * the `from` account (caller) which is `accounts:test0` * the contract address, which is aliased as `contracts:testtoken` (or simply `testtoken`) * the args that the function takes, which is the account to mint the tokens into (`test0`), and `amount` (`100`). This only works because we are using the secret key of the admin who has permissions to mint. A successful call should print something like this: ``` aztec:wallet [INFO] Using wallet with address 0x066108a2398e3e2ff53ec4b502e4c2e778c6de91bb889de103d5b4567530d99c +0ms Maximum total tx fee: 1161660 Estimated total tx fee: 116166 Estimated gas usage: da=1127,l2=115039,teardownDA=0,teardownL2=0 Transaction hash: 2ac383e8e2b68216cda154b52e940207a905c1c38dadba7a103c81caacec403d Transaction has been mined Tx fee: 200106180 Status: success Block number: 17 Block hash: 1e27d200600bc45ab94d467c230490808d1e7d64f5ee6cee5e94a08ee9580809 Transaction hash stored in database with aliases last & mint_to_public-9044 ``` You can double-check by calling the function that checks your public account balance: ``` aztec-wallet simulate balance_of_public --from test0 --contract-address testtoken --args accounts:test0 ``` This should print ``` Simulation result: 100n ``` ## Playing with hybrid state and private functions[​](#playing-with-hybrid-state-and-private-functions "Direct link to Playing with hybrid state and private functions") In the following steps, we'll move some tokens from public to private state and check our private and public balance. ``` aztec-wallet send transfer_to_private --from accounts:test0 --contract-address testtoken --args accounts:test0 25 ``` The arguments for `transfer_to_private` function are: * the account address to transfer to * the amount of tokens to send to private A successful call should print something similar to what you've seen before. Now when you call `balance_of_public` again you will see 75! ``` aztec-wallet simulate balance_of_public --from test0 --contract-address testtoken --args accounts:test0 ``` This should print ``` Simulation result: 75n ``` And then call `balance_of_private` to check that you have your tokens! ``` aztec-wallet simulate balance_of_private --from test0 --contract-address testtoken --args accounts:test0 ``` This should print ``` Simulation result: 25n ``` **Congratulations, you now know the fundamentals of working with the Aztec local network!** You are ready to move onto the more fun stuff. ## What's next?[​](#whats-next "Direct link to What's next?") Want to build something cool on Aztec? * Check out the [Token Contract Tutorial](/developers/testnet/docs/tutorials/contract_tutorials/token_contract.md) for a beginner tutorial, or jump into more advanced ones * Ready for a live network? Try [deploying on testnet](/developers/testnet/getting_started_on_testnet.md) * Start on your own thing and check out the How To Guides to help you! Need help? If something does not work, see the [support guide](/developers/testnet/support.md). It tells you when to ask in [Discord](https://discord.gg/aztec) or the [forum](https://forum.aztec.network), when to [open a GitHub issue](https://github.com/AztecProtocol/aztec-packages/issues/new?template=bug_report.yml), and how to disclose security issues responsibly. --- # Getting Started on Testnet This guide walks you through deploying your first contract on the Aztec testnet. You will install the CLI tools, create an account using the Sponsored FPC (so you don't need to bridge Fee Juice yourself), and deploy and interact with a contract. ## Testnet vs Local Network[​](#testnet-vs-local-network "Direct link to Testnet vs Local Network") | Feature | Local Network | Testnet | | --------------- | ------------------------------ | -------------------------------- | | **Environment** | Local machine | Decentralized network on Sepolia | | **Fees** | Free (test accounts prefunded) | Sponsored FPC available | | **Proving** | Optional | Required | | **Accounts** | Test accounts pre-deployed | Must create and deploy your own | info If you want to develop and iterate quickly, start with the [local network guide](/developers/testnet/getting_started_on_local_network.md). The local network has instant blocks and no proving, making it faster for development. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Aztec libraries require Node.js version 24. If you have an older version installed, the installer will try to upgrade via [nvm](https://github.com/nvm-sh/nvm) if available. If nvm is not installed, you will need to upgrade Node.js manually (e.g. `nvm install 24` after installing nvm). ## Install the Aztec toolchain[​](#install-the-aztec-toolchain "Direct link to Install the Aztec toolchain") Install the testnet version of the Aztec CLI: ``` VERSION=5.0.0-rc.2 bash -i <(curl -sL https://install.aztec.network/5.0.0-rc.2) ``` warning Testnet is version-dependent. It is currently running version `5.0.0-rc.2`. Maintain version consistency when interacting with the testnet to avoid errors. This installs: * **aztec** - Compiles and tests Aztec contracts, launches infrastructure, and provides utility commands * **aztec-up** - Version manager for the Aztec toolchain (`aztec-up install`, `aztec-up use`, `aztec-up list`) * **aztec-wallet** - CLI tool for interacting with the Aztec network ## Getting started on testnet[​](#getting-started-on-testnet "Direct link to Getting started on testnet") ### Step 1: Set up your environment[​](#step-1-set-up-your-environment "Direct link to Step 1: Set up your environment") Set the required environment variables: ``` export NODE_URL=https://v5.testnet.rpc.aztec-labs.com export SPONSORED_FPC_ADDRESS=0x1969946536f0c09269e2c75e414eef4e21a76e763c5514125208db33d7d944d7 ``` ### Step 2: Register the Sponsored FPC[​](#step-2-register-the-sponsored-fpc "Direct link to Step 2: Register the Sponsored FPC") The Sponsored FPC (Fee Payment Contract) pays transaction fees on your behalf, so you don't need to bridge Fee Juice from L1. Register it in your wallet: ``` aztec-wallet register-contract \ --node-url $NODE_URL \ --alias sponsoredfpc \ $SPONSORED_FPC_ADDRESS SponsoredFPC \ --salt 0 ``` ### Step 3: Create and deploy an account[​](#step-3-create-and-deploy-an-account "Direct link to Step 3: Create and deploy an account") Unlike the local network, testnet has no pre-deployed accounts. Create and deploy your own: ``` aztec-wallet create-account \ --node-url $NODE_URL \ --alias my-wallet \ --payment method=fpc-sponsored,fpc=$SPONSORED_FPC_ADDRESS ``` note The first transaction will take longer as it downloads proving keys. If you see `Timeout awaiting isMined`, the transaction is still processing: this is normal on testnet. ### Step 4: Deploy a contract[​](#step-4-deploy-a-contract "Direct link to Step 4: Deploy a contract") Deploy a token contract as an example: ``` aztec-wallet deploy \ --node-url $NODE_URL \ --from accounts:my-wallet \ --payment method=fpc-sponsored,fpc=$SPONSORED_FPC_ADDRESS \ --alias token \ TokenContract \ --args accounts:my-wallet Token TOK 18 ``` This deploys the `TokenContract` with: * `admin`: your wallet address * `name`: Token * `symbol`: TOK * `decimals`: 18 You can check the transaction status on [Aztecscan](https://testnet.aztecscan.xyz). ### Step 5: Interact with your contract[​](#step-5-interact-with-your-contract "Direct link to Step 5: Interact with your contract") Mint some tokens: ``` aztec-wallet send mint_to_public \ --node-url $NODE_URL \ --from accounts:my-wallet \ --payment method=fpc-sponsored,fpc=$SPONSORED_FPC_ADDRESS \ --contract-address token \ --args accounts:my-wallet 100 ``` Check your balance: ``` aztec-wallet simulate balance_of_public \ --node-url $NODE_URL \ --from accounts:my-wallet \ --contract-address token \ --args accounts:my-wallet ``` This should print: ``` Simulation result: 100n ``` Move tokens to private state: ``` aztec-wallet send transfer_to_private \ --node-url $NODE_URL \ --from accounts:my-wallet \ --payment method=fpc-sponsored,fpc=$SPONSORED_FPC_ADDRESS \ --contract-address token \ --args accounts:my-wallet 25 ``` Check your private balance: ``` aztec-wallet simulate balance_of_private \ --node-url $NODE_URL \ --from accounts:my-wallet \ --contract-address token \ --args accounts:my-wallet ``` This should print: ``` Simulation result: 25n ``` ## Viewing transactions on the block explorer[​](#viewing-transactions-on-the-block-explorer "Direct link to Viewing transactions on the block explorer") You can view your transactions, contracts, and account on the testnet block explorers: * [Aztecscan](https://testnet.aztecscan.xyz) * [Aztec Explorer](https://aztecexplorer.xyz/?network=testnet) Search by transaction hash, contract address, or account address to see details and status. ## Registering existing contracts[​](#registering-existing-contracts "Direct link to Registering existing contracts") To interact with a contract deployed by someone else, you need to register it in your local PXE first: ``` aztec-wallet register-contract \ --node-url $NODE_URL \ --alias mycontract \ ``` For example, to register a `TokenContract` deployed by someone else: ``` aztec-wallet register-contract \ --node-url $NODE_URL \ --alias external-token \ 0x1234...abcd TokenContract ``` After registration, you can interact with it using `aztec-wallet send` and `aztec-wallet simulate` as shown above. ## Paying fees without the Sponsored FPC[​](#paying-fees-without-the-sponsored-fpc "Direct link to Paying fees without the Sponsored FPC") The Sponsored FPC is convenient for getting started, but you can also pay fees directly by bridging Fee Juice from Ethereum Sepolia. See [Paying Fees](/developers/testnet/docs/aztec-js/how_to_pay_fees.md#bridge-fee-juice-from-l1) for details on bridging and other fee payment methods. ## Getting Fee Juice from the faucet[​](#getting-fee-juice-from-the-faucet "Direct link to Getting Fee Juice from the faucet") If you want to pay fees directly instead of using the Sponsored FPC, you can request **Fee Juice** from the testnet faucet: * [Aztec Fee Juice Faucet](https://aztec-faucet.nethermind.io/) - dispenses testnet Fee Juice to your account Fee Juice is not the AZTEC token This faucet dispenses **Fee Juice**, the asset used to pay transaction fees (gas) on Aztec. Fee Juice lives on Aztec (L2) and is only used to pay fees. It is **not** the AZTEC token, which is a separate asset that lives on Ethereum (L1). This faucet does not dispense AZTEC tokens. ## Testnet information[​](#testnet-information "Direct link to Testnet information") For complete testnet technical details including contract addresses and network configuration, see the [Networks page](/networks.md#testnet). ## Next steps[​](#next-steps "Direct link to Next steps") * Check out the [Tutorials](/developers/testnet/docs/tutorials/contract_tutorials/counter_contract.md) for building more complex contracts * Learn about [paying fees](/developers/testnet/docs/aztec-js/how_to_pay_fees.md) with different methods * Explore [Aztec Playground](https://play.aztec.network/) for an interactive development experience Need help? If something does not work, see the [support guide](/developers/testnet/support.md). It tells you when to ask in [Discord](https://discord.gg/aztec) or the [forum](https://forum.aztec.network), when to [open a GitHub issue](https://github.com/AztecProtocol/aztec-packages/issues/new?template=bug_report.yml), and how to disclose security issues responsibly. --- # Aztec Overview This page outlines Aztec's fundamental technical concepts. It is recommended to read this before diving into building on Aztec. ## What is Aztec?[​](#what-is-aztec "Direct link to What is Aztec?") Aztec is a privacy-first Layer 2 on Ethereum. It supports smart contracts with both private & public state and private & public execution. ![](/assets/ideal-img/Aztec_overview.4d3e9fb.640.png) ## Getting started[​](#getting-started "Direct link to Getting started") Learn about Aztec, what it is, how it works and how to get start writing smart contracts on Aztec with programmable privacy by watching this video course: [Aztec Video Course](https://www.youtube.com/embed/cQIPG_J1W9g) ## High level view[​](#high-level-view "Direct link to High level view") ![](/assets/ideal-img/aztec-high-level.4ac0d53.640.png) 1. A user interacts with Aztec through Aztec.js (like web3js or ethersjs) 2. Private functions are executed in the PXE, which is client-side 3. Proofs and tree updates are sent to the Public VM (running on an Aztec node) 4. Public functions are executed in the Public VM 5. The Public VM rolls up the transactions that include private and public state updates into blocks 6. The block data and proof of a correct state transition are submitted to Ethereum for verification ## Private and public execution[​](#private-and-public-execution "Direct link to Private and public execution") Private functions are executed client side, on user devices to maintain maximum privacy. Public functions are executed by a remote network of nodes, similar to other blockchains. These distinct execution environments create a directional execution flow for a single transaction--a transaction begins in the private context on the user's device then moves to the public network. This means that private functions executed by a transaction can enqueue public functions to be executed later in the transaction life cycle, but public functions cannot call private functions. ### Private Execution Environment (PXE)[​](#private-execution-environment-pxe "Direct link to Private Execution Environment (PXE)") Private functions are executed on the user's device in the Private Execution Environment (PXE, pronounced 'pixie'), then it generates proofs for onchain verification. It is a client-side library for execution and proof-generation of private operations. It holds keys, notes, and generates proofs. It is included in aztec.js, a TypeScript library, and can be run within Node or the browser. Note: It is easy for private functions to be written in a detrimentally unoptimized way, because many intuitions of regular program execution do not apply to proving. For more about writing performant private functions in Noir, see [this page](https://noir-lang.org/docs/explainers/explainer-writing-noir) of the Noir documentation. ### Aztec Virtual Machine (AVM)[​](#aztec-virtual-machine-avm "Direct link to Aztec Virtual Machine (AVM)") Public functions are executed by the Aztec Virtual Machine (AVM), which is conceptually similar to the Ethereum Virtual Machine (EVM). As such, writing efficient public functions follow the same intuition as gas-efficient solidity contracts. The PXE is unaware of the Public VM. And the Public VM is unaware of the PXE. They are completely separate execution environments. This means: * The PXE and the Public VM cannot directly communicate with each other * Private transactions in the PXE are executed first, followed by public transactions ## Private and public state[​](#private-and-public-state "Direct link to Private and public state") Private state works with UTXOs, which are chunks of data that we call notes. To keep things private, notes are stored in an [append-only UTXO tree](/developers/testnet/docs/foundational-topics/advanced/storage/indexed_merkle_tree.md), and a nullifier is created when notes are invalidated (aka deleted). Nullifiers are stored in their own [nullifier tree](/developers/testnet/docs/foundational-topics/advanced/storage/indexed_merkle_tree.md). Public state works similarly to other chains like Ethereum, behaving like a public ledger. Public data is stored in a public data tree. ![Public vs private state](/assets/images/public-and-private-state-diagram-ff88262b40b259d4fe4c8b7d667924aa.png) Aztec [smart contract](/developers/testnet/docs/aztec-nr/framework-description/contract_structure.md) developers should keep in mind that different data types are used when manipulating private or public state. Working with private state is creating commitments and nullifiers to state, whereas working with public state is directly updating state. ## Accounts and keys[​](#accounts-and-keys "Direct link to Accounts and keys") ### Account abstraction[​](#account-abstraction "Direct link to Account abstraction") Every account in Aztec is a smart contract (account abstraction). This allows implementing different schemes for authorizing transactions, nonce management, and fee payments. Developers can write their own account contract to define the rules by which user transactions are authorized and paid for, as well as how user keys are managed. Learn more about account contracts [here](/developers/testnet/docs/foundational-topics/accounts.md). ### Key pairs[​](#key-pairs "Direct link to Key pairs") Each account in Aztec is backed by 3 key pairs: * A **nullifier key pair** used for note nullifier computation * A **incoming viewing key pair** used to encrypt a note for the recipient * A **outgoing viewing key pair** used to encrypt a note for the sender As Aztec has native account abstraction, accounts do not automatically have a signing key pair to authenticate transactions. This is up to the account contract developer to implement. ## Noir[​](#noir "Direct link to Noir") Noir is a zero-knowledge domain specific language used for writing smart contracts for the Aztec network. It is also possible to write circuits with Noir that can be verified on or offchain. For more in-depth docs into the features of Noir, go to the [Noir website](https://noir-lang.org/). Need help? If something does not work, or you are not sure where to ask, see the [support guide](/developers/testnet/support.md). It explains the right channel for questions, bug reports, feature requests, and security disclosures. --- # Support This page tells you where to go when something does not work, when you want to file a bug, when you have a feature idea, or when you have found a possible security issue. Pick the section that matches your situation. Security issues are different If your issue involves loss of funds, key or seed disclosure, leakage of private notes, a way to forge or replay transactions, or anything you suspect could harm users, **do not open a public GitHub issue or post in Discord**. Use the [security disclosure process](#security-issues) instead. ## Quick decision tree[​](#quick-decision-tree "Direct link to Quick decision tree") | Your situation | Where to go | | ------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------- | | Possible security issue (funds, keys, privacy, exploit) | [Security disclosure](#security-issues) | | You are not sure if the bug is real, or you cannot reproduce it yet | [Ask first: Forum or Discord](#ask-first-forum-and-discord) | | You have a reproducible bug in `aztec-packages` (PXE, aztec.js, aztec-nr, local network, CLI, AVM, barretenberg, L1 contracts) | [File a bug on GitHub](#file-a-bug) | | You have a Noir compiler or language bug | [Noir issues on `noir-lang/noir`](https://github.com/noir-lang/noir/issues) | | You have a feature request or enhancement idea | [File a feature request](#feature-requests) | | You are running a node, sequencer, or prover and hit an operator problem | [Operator support](#operator-and-node-issues) | | You want to suggest a documentation change | [File a docs issue](#documentation-issues) | ## Ask first: Forum and Discord[​](#ask-first-forum-and-discord "Direct link to Ask first: Forum and Discord") If you are not yet sure whether your problem is a real bug or a configuration issue, start in the community channels. You can often get a faster answer there, and the team can help you build a minimal reproduction before you open a GitHub issue. * [Noir Discord](https://discord.com/invite/JtqzkdeQ6G): the developer-focused channel for syntax issues, compiler questions, and language-level help with Aztec.nr contracts. * [Aztec Forum](https://forum.aztec.network): long-form Q\&A, best for design discussions, complex bug reports, and conversations you want indexed and searchable. * [Aztec Discord](https://discord.gg/aztec): the broader Aztec community space, and the live support channel for node operators (sequencers, provers, RPC nodes). Once you have a clear reproduction, the right next step is to file a GitHub issue using the form below. ## File a bug[​](#file-a-bug "Direct link to File a bug") Reproducible bugs in the Aztec stack belong on GitHub. Use the bug template, which automatically labels your issue and helps a maintainer triage it. [Open a new bug report](https://github.com/AztecProtocol/aztec-packages/issues/new?template=bug_report.yml) ### What a high-quality bug report includes[​](#what-a-high-quality-bug-report-includes "Direct link to What a high-quality bug report includes") The template asks for these, and your issue will be triaged faster if you provide all of them. 1. **Aztec version**, for example `0.85.0-alpha-testnet.2`. Use `aztec-up list` if you are not sure. 2. **What you were trying to do**, in one or two sentences. 3. **A minimal, runnable reproduction**. A code snippet that compiles, or a link to a public repo branch, is much more useful than prose. If your reproduction is large, please trim it before filing. 4. **Expected vs. actual behavior**. 5. **Environment**: operating system, Node.js version, and browser if relevant. 6. **Logs and errors**: the failing block, ideally with `LOG_LEVEL=debug` for the module that failed. 7. **What you already tried**: workarounds, version downgrades, related issues you read. ### Where bugs in specific components go[​](#where-bugs-in-specific-components-go "Direct link to Where bugs in specific components go") All of the components below live in [`AztecProtocol/aztec-packages`](https://github.com/AztecProtocol/aztec-packages), so use the bug template above and let triage attach the component label. * **PXE, wallet, CLI**: `aztec`, `aztec-wallet`, `aztec.js`, `bb.js`, local network. * **Aztec.nr framework**: the `aztec-nr` smart contract framework. * **Protocol circuits or protocol specs**: the rollup, kernels, and other circuits. * **Barretenberg, AVM, L1 contracts**: the prover backend, the Aztec Virtual Machine, and the Ethereum-side rollup contracts. For the **Noir compiler** or the Noir language itself, file on [`noir-lang/noir`](https://github.com/noir-lang/noir/issues) instead. ## Feature requests[​](#feature-requests "Direct link to Feature requests") Use the feature request template for new functionality, enhancements to existing components, or proposed changes to the developer experience. [Open a feature request](https://github.com/AztecProtocol/aztec-packages/issues/new?template=feature_request.yml) Include: * The problem you are trying to solve. * A concrete example or use case, if you have one. * Why this is impactful: what is unblocked or made easier if it ships. ## Documentation issues[​](#documentation-issues "Direct link to Documentation issues") If something in the docs is wrong, outdated, or missing, please **[open an issue](https://github.com/AztecProtocol/aztec-packages/issues/new?template=bug_report.yml)** with the bug template and quote the URL and paragraph that needs fixing. Docs issues are routed to [`@AztecProtocol/devrel`](https://github.com/orgs/AztecProtocol/teams/devrel) so a docs maintainer can pick them up. This includes typos and small text issues: please file them as issues rather than opening single-line PRs. A maintainer can fix several at once, which is faster to review than a stream of one-line pull requests. For larger restructures or new pages, open an issue first so a maintainer can confirm the direction before you write the change. Every docs page in this site has an "Edit this page" link at the bottom that takes you to the right file in [`docs-developers/`](https://github.com/AztecProtocol/aztec-packages/tree/next/docs/docs-developers) once the direction is agreed. ## Operator and node issues[​](#operator-and-node-issues "Direct link to Operator and node issues") For real-time help, the [Aztec Discord](https://discord.gg/aztec) is the fastest way to reach other operators and the Aztec team. Use it to sanity-check a failure mode before filing, or to coordinate with the team during an incident. If you are running a node, sequencer, or prover and you hit a reproducible operational problem (sync failures, missed proposals, prover crashes, deployment errors), [open a bug report](https://github.com/AztecProtocol/aztec-packages/issues/new?template=bug_report.yml) and include the following alongside the standard bug-report fields: * **Network** (mainnet, testnet, devnet, or local). * **Role** (sequencer, prover, RPC node). * **Block height at failure** and approximate UTC timestamp. * **Hardware**: CPU model, RAM, disk type and size. * **Container runtime and image version**. * **Configuration** (your `config.json` or environment block, with secrets redacted). * **Logs**: the last few hundred lines around the failure, with sensitive keys redacted. Operator-specific guides live in the [Operate section](/operate/operators.md). ## Security issues[​](#security-issues "Direct link to Security issues") **Do not open a public GitHub issue for a suspected vulnerability.** Public disclosure can put users at risk before a fix is available. Use one of the following, in order of preference: 1. **[Aztec Network Bug Bounty on Cantina](https://cantina.xyz/bounties/80e74370-10d8-4e52-8e4b-7294deb7c9ee)** if the issue is in scope of the bounty program. 2. **[GitHub Private Vulnerability Reporting (PVR)](https://github.com/AztecProtocol/aztec-packages/security/advisories/new)** for any other suspected vulnerability. Go to the "Security" tab of the repository and click "Report a vulnerability". 3. **Email `security@aztec.foundation`** if neither Cantina nor PVR is available to you. Send a brief impact summary first, without exploit details or reproduction steps, and wait for the team to confirm a secure channel before sharing them. If you believe a vulnerability is being actively exploited or has severe impact (loss of funds, key compromise, or broad user impact), mark the report as **CRITICAL** in the PVR or email subject. See the full [security policy](https://github.com/AztecProtocol/aztec-packages/blob/next/SECURITY.md) for more. ## What happens after you file[​](#what-happens-after-you-file "Direct link to What happens after you file") When you file a GitHub issue using one of the templates above, it is automatically tagged so the team can triage it. A maintainer will: 1. Confirm the component the issue belongs to. 2. Set a priority based on impact. 3. Ask follow-up questions if the report is missing a reproduction or context. 4. Route the issue to the owning team. The fastest way to a fix is a small, runnable reproduction. If you can attach one, please do. ## See also[​](#see-also "Direct link to See also") * [`CONTRIBUTING.md`](https://github.com/AztecProtocol/aztec-packages/blob/next/CONTRIBUTING.md) for contribution guidelines. * [`SECURITY.md`](https://github.com/AztecProtocol/aztec-packages/blob/next/SECURITY.md) for the full security disclosure policy. * The [Aztec project board](https://github.com/orgs/AztecProtocol/projects/22) for in-flight work. --- # AI Tooling Aztec is new, rapidly evolving, and spans novel concepts like private state, notes, and nullifiers. AI coding tools can accelerate your learning and development, but they need up-to-date context to be useful. This page shows you how to set that up. caution LLMs have limited training data for zero-knowledge circuit development. Noir and Aztec.nr are newer languages with smaller codebases than mainstream languages, so AI tools will make more mistakes than you might be used to. The tools on this page help by providing up-to-date context, but you should always verify generated code and test thoroughly. ## Project-level instructions (CLAUDE.md / AGENTS.md files)[​](#project-level-instructions-claudemd--agentsmd-files "Direct link to Project-level instructions (CLAUDE.md / AGENTS.md files)") MCP servers and skills provide context on demand, but AI tools don't always invoke them at the right time. The most reliable way to prevent common mistakes is to add **project-level instruction files** that your AI tool reads automatically at the start of every conversation. You can add to these files over time as you discover new gotchas or best practices. They ensure your AI tool always has the critical context it needs, without relying on you to remember to invoke the right skills or MCP servers. For Claude Code, create a `CLAUDE.md` file in your project root. For Codex, create an `AGENTS.md` file in your project root. For other tools, check their documentation for equivalent configuration. ### Recommended CLAUDE.md / AGENTS.md[​](#recommended-claudemd--agentsmd "Direct link to Recommended CLAUDE.md / AGENTS.md") ``` # Aztec Project ## Critical: Use `aztec` CLI, not `nargo` directly This is an Aztec smart contract project. Always use the `aztec` CLI wrapper instead of calling `nargo` directly: - **Compile**: `aztec compile` (NOT `nargo compile`). Using `nargo compile` alone produces incomplete artifacts. - **Test**: `aztec test` (NOT `nargo test`). - **Other nargo commands** like `aztec-nargo fmt` and `aztec-nargo doc` are fine to use directly. The Aztec installer exposes the bundled `nargo` as `aztec-nargo`; bare `nargo` resolves to your own install (if any), not the bundled one. ## Error Handling - NEVER silently swallow errors or fall back to default values. If a value is required, throw if it's missing. - NEVER use fallback values like `AztecAddress.ZERO`, `"unknown"`, `0`, or `null` to mask missing data. These hide bugs and cause failures elsewhere that are harder to trace. - NEVER add retry/polling logic unless explicitly asked. Retry loops with long timeouts may brick application loops and mask the real error. - NEVER wrap calls in try/catch that returns null or a default. Let errors propagate. - If a precondition isn't met, throw immediately with a descriptive message — don't try to "work around" it. - Prefer `T` return types over `T | null` when null would indicate a bug rather than a valid state. - Do not add `.catch(() => defaultValue)` to promises. If something fails, the caller needs to know. ## Hashing: Default to Poseidon2 When writing Aztec.nr contract code that requires hashing, **always use Poseidon2** unless a specific protocol or interoperability requirement calls for a different hash. - **Default**: `use aztec::protocol::hash::poseidon2_hash;` - **Do NOT** default to Pedersen (`pedersen_hash`). Pedersen is available but Poseidon2 is cheaper in circuits and is the standard across Aztec. - If you are unsure which hash to use, use Poseidon2. ``` This prevents the two most common AI mistakes: using `nargo compile`/`nargo test` instead of their Aztec wrappers, and defaulting to Pedersen hashes instead of Poseidon2. ### Why this matters[​](#why-this-matters "Direct link to Why this matters") LLMs have extensive training data for `nargo` (the standalone Noir compiler) but limited exposure to the `aztec` CLI wrapper. Without explicit instructions, they default to `nargo compile`, which produces artifacts missing the AVM transpilation step. ## MCP servers[​](#mcp-servers "Direct link to MCP servers") The highest-leverage tools are the Aztec and Noir MCP servers. They clone reference repositories locally and give your AI tool code search, documentation search, and example discovery across the Aztec and Noir ecosystems. They work with any AI coding tool that supports MCP (Claude Code, Cursor, Windsurf, Codex, and others). The MCP servers help manage the problem of focusing LLMs on the correct Aztec versions for your project. Aztec is under active development and there may be multiple versions in use at any given time (e.g. mainnet, devnet and testnet may be on different versions). They make it easy to switch between versions if needed, and to keep your context up to date as the repos evolve. Start here if you're unsure what to set up. ### Claude Code[​](#claude-code "Direct link to Claude Code") Add the MCP servers: ``` claude mcp add aztec -- npx @aztec/mcp-server@latest claude mcp add noir -- npx noir-mcp-server@latest ``` ### Cursor / Windsurf / other MCP clients[​](#cursor--windsurf--other-mcp-clients "Direct link to Cursor / Windsurf / other MCP clients") Add the servers to your MCP configuration JSON: ``` { "mcpServers": { "aztec": { "command": "npx", "args": ["-y", "@aztec/mcp-server@latest"] }, "noir": { "command": "npx", "args": ["-y", "noir-mcp-server@latest"] } } } ``` ### OpenAI Codex[​](#openai-codex "Direct link to OpenAI Codex") Use the same MCP configuration format, pointing at `@aztec/mcp-server` and `noir-mcp-server`. ## For learning and exploration[​](#for-learning-and-exploration "Direct link to For learning and exploration") These resources help you understand Aztec concepts, read docs, or provide additional context to your AI tool. * **API reference docs** - The docs site publishes auto-generated API references that are useful to feed to AI tools: * [Aztec.nr API reference](/developers/docs/aztec-nr/api.md) - generated from aztec-nr source with `nargo doc` * [TypeScript API reference](/developers/docs/aztec-js/typescript_api_reference.md) - generated from yarn-project packages with TypeDoc These are especially useful as context for code generation since they reflect the current API surface. * **llms.txt** - The docs site publishes `llms.txt` and `llms-full.txt` at [docs.aztec.network/llms.txt](https://docs.aztec.network/llms.txt) for automatic LLM discovery. Many AI tools can consume these files directly to index documentation. * **Reference repositories** - Point your AI tool at these repos for additional context: * [AztecProtocol/aztec-packages](https://github.com/AztecProtocol/aztec-packages) - main monorepo, best general reference * [AztecProtocol/aztec-starter](https://github.com/AztecProtocol/aztec-starter) - smaller starter project, easier for onboarding * [AztecProtocol/aztec-examples](https://github.com/AztecProtocol/aztec-examples) - official contract examples * [noir-lang/noir](https://github.com/noir-lang/noir) - Noir language source of truth * [noir-lang/noir-examples](https://github.com/noir-lang/noir-examples) - common Noir patterns * [awesome-noir](https://github.com/noir-lang/awesome-noir) - community Noir resources * [awesome-aztec](https://github.com/AztecProtocol/awesome-aztec) - community Aztec resources * **Copy docs into context** - Copy docs pages directly into your AI tool's context or conversation using the "Copy page" button at the top of each page. * **Context7** - [Context7](https://context7.com) is a generic MCP server with Aztec docs available at [context7.com/aztecprotocol/aztec-packages](https://context7.com/aztecprotocol/aztec-packages). Note that it may be less current than the MCP servers above. ## Aztec and Noir tool reference[​](#aztec-and-noir-tool-reference "Direct link to Aztec and Noir tool reference") | Tool | Works with | Description | | --------------------------------------------------------------------------- | ----------------------------------------------------- | -------------------------------------------------------------------------------------- | | [aztec-claude-plugin](https://github.com/critesjosh/aztec-claude-plugin) | Claude Code | Skills, commands, agents, and MCP server for Aztec contract and TypeScript development | | [@aztec/mcp-server](https://github.com/AztecProtocol/mcp-server) | Any MCP client (Claude Code, Cursor, Windsurf, Codex) | Clones Aztec repos locally, provides code search, doc search, and example discovery | | [noir-claude-plugin](https://github.com/critesjosh/noir-claude-plugin) | Claude Code | Skills and commands for Noir circuit development | | [noir-mcp-server](https://github.com/critesjosh/noir-mcp-server) | Any MCP client | Clones Noir repos, stdlib, and community libraries; provides search and examples | | [aztec-skills](https://github.com/NethermindEth/aztec-skills) | Claude Code, Codex | Installable skills for Aztec contracts, deployment, Aztec.js, and testing | | [noir skills](https://github.com/noir-lang/noir/tree/master/.claude/skills) | Claude Code, Codex | Skills for Noir compiler development, SSA debugging, fuzzing, and ACIR optimization | --- # Aztec.js Aztec.js is a library that provides APIs for managing accounts and interacting with contracts on the Aztec network. It communicates with the [Private eXecution Environment (PXE)](/developers/docs/foundational-topics/pxe.md) through a `PXE` implementation, allowing developers to easily register new accounts, deploy contracts, view functions, and send transactions. ## Installing[​](#installing "Direct link to Installing") ``` npm install @aztec/aztec.js@4.3.1 ``` ## Common Dependencies[​](#common-dependencies "Direct link to Common Dependencies") Most applications will need additional packages alongside `@aztec/aztec.js`, e.g.: ``` npm install @aztec/aztec.js@4.3.1 \ @aztec/accounts@4.3.1 \ @aztec/wallets@4.3.1 \ @aztec/noir-contracts.js@4.3.1 ``` | Package | Description | | -------------------------- | ------------------------------------------------------------- | | `@aztec/aztec.js` | Core SDK for contracts, transactions, and network interaction | | `@aztec/accounts` | Account contract implementations (Schnorr, ECDSA) | | `@aztec/wallets` | Simplified wallets for local development and scripting | | `@aztec/noir-contracts.js` | Pre-compiled contract interfaces (Token, NFT, etc.) | ## Package Structure[​](#package-structure "Direct link to Package Structure") `@aztec/aztec.js` uses subpath exports. You must import from specific subpaths rather than the package root: ``` import { createAztecNodeClient, waitForNode } from "@aztec/aztec.js/node"; import { Fr } from "@aztec/aztec.js/fields"; import { AztecAddress } from "@aztec/aztec.js/addresses"; ``` ## AI-Friendly Reference[​](#ai-friendly-reference "Direct link to AI-Friendly Reference") The [TypeScript API reference](/developers/docs/aztec-js/typescript_api_reference.md) links to markdown interface files for common packages for easy use with AI coding assistants. Copy relevant sections to give your AI tool accurate context about Aztec.js APIs. ## Guides[​](#guides "Direct link to Guides") ## [📄️Connect to Local Network](/developers/docs/aztec-js/how_to_connect_to_local_network.md) [Connect your application to the Aztec local network and interact with accounts.](/developers/docs/aztec-js/how_to_connect_to_local_network.md) ## [📄️Creating Accounts](/developers/docs/aztec-js/how_to_create_account.md) [Step-by-step guide to creating and deploying new user accounts in Aztec.js applications.](/developers/docs/aztec-js/how_to_create_account.md) ## [📄️Deploying Contracts](/developers/docs/aztec-js/how_to_deploy_contract.md) [Deploy smart contracts to Aztec using generated TypeScript classes.](/developers/docs/aztec-js/how_to_deploy_contract.md) ## [📄️Sending Transactions](/developers/docs/aztec-js/how_to_send_transaction.md) [Send transactions to Aztec contracts using Aztec.js with various options and error handling](/developers/docs/aztec-js/how_to_send_transaction.md) ## [📄️Reading Contract Data](/developers/docs/aztec-js/how_to_read_data.md) [How to read data from contracts including simulating functions, reading logs, and retrieving events.](/developers/docs/aztec-js/how_to_read_data.md) ## [📄️Using Authentication Witnesses](/developers/docs/aztec-js/how_to_use_authwit.md) [Step-by-step guide to implementing authentication witnesses in Aztec.js for delegated transactions.](/developers/docs/aztec-js/how_to_use_authwit.md) ## [📄️Paying Fees](/developers/docs/aztec-js/how_to_pay_fees.md) [Pay transaction fees on Aztec, understand mana costs, estimate gas, and retrieve fees from receipts.](/developers/docs/aztec-js/how_to_pay_fees.md) ## [📄️Testing Smart Contracts](/developers/docs/aztec-js/how_to_test.md) [Learn how to write and run tests for your Aztec smart contracts using Aztec.js and a local network.](/developers/docs/aztec-js/how_to_test.md) ## [📄️Pay Fees Privately](/developers/docs/aztec-js/how_to_use_private_fee_juice.md) [Learn how private fee payment works on Aztec and walk through an example using a community-built fully private Fee Payment Contract.](/developers/docs/aztec-js/how_to_use_private_fee_juice.md) ## [🗃Wallet SDK](/developers/docs/aztec-js/wallet-sdk.md) [2 items](/developers/docs/aztec-js/wallet-sdk.md) ## [📄️Reference](/developers/docs/aztec-js/aztec_js_reference.md) [Comprehensive auto-generated reference for the Aztec.js TypeScript library with all classes, interfaces, types, and functions.](/developers/docs/aztec-js/aztec_js_reference.md) ## [📄️TypeScript API Reference](/developers/docs/aztec-js/typescript_api_reference.md) [API reference documentation for Aztec TypeScript packages including aztec.js, accounts, PXE, and core libraries.](/developers/docs/aztec-js/typescript_api_reference.md) --- # Reference *This documentation is auto-generated from the Aztec.js TypeScript source code.* info This is an auto-generated reference. For tutorials and guides, see the [Aztec.js Guide](/developers/docs/aztec-js.md). *Package: @aztec/aztec.js* *Generated: 2025-12-10T22:27:41.987Z* This document provides a comprehensive reference for all public APIs in the Aztec.js library. Each section is organized by module, with classes, interfaces, types, and functions documented with their full signatures, parameters, and return types. ## Table of Contents[​](#table-of-contents "Direct link to Table of Contents") * [Account](#account) * [AccountContract](#accountcontract) * [getAccountContractAddress](#getaccountcontractaddress) * [AccountWithSecretKey](#accountwithsecretkey) * [Account](#account) * [BaseAccount](#baseaccount) * [AccountInterface](#accountinterface) * [SignerlessAccount](#signerlessaccount) * [Authorization](#authorization) * [CallAuthorizationRequest](#callauthorizationrequest) * [Contract](#contract) * [BaseContractInteraction](#basecontractinteraction) * [BatchCall](#batchcall) * [abiChecker](#abichecker) * [ContractMethod](#contractmethod) * [ContractStorageLayout](#contractstoragelayout) * [ContractBase](#contractbase) * [ContractFunctionInteraction](#contractfunctioninteraction) * [Contract](#contract) * [RequestDeployOptions](#requestdeployoptions) * [DeployOptions](#deployoptions) * [SimulateDeployOptions](#simulatedeployoptions) * [DeployMethod](#deploymethod) * [DeployedWaitOpts](#deployedwaitopts) * [DeployTxReceipt](#deploytxreceipt) * [DeploySentTx](#deploysenttx) * [getGasLimits](#getgaslimits) * [FeeEstimationOptions](#feeestimationoptions) * [FeePaymentMethodOption](#feepaymentmethodoption) * [GasSettingsOption](#gassettingsoption) * [InteractionFeeOptions](#interactionfeeoptions) * [SimulationInteractionFeeOptions](#simulationinteractionfeeoptions) * [RequestInteractionOptions](#requestinteractionoptions) * [SendInteractionOptions](#sendinteractionoptions) * [SimulateInteractionOptions](#simulateinteractionoptions) * [ProfileInteractionOptions](#profileinteractionoptions) * [SimulationReturn](#simulationreturn) * [toSendOptions](#tosendoptions) * [toSimulateOptions](#tosimulateoptions) * [toProfileOptions](#toprofileoptions) * [getClassRegistryContract](#getclassregistrycontract) * [getInstanceRegistryContract](#getinstanceregistrycontract) * [getFeeJuice](#getfeejuice) * [WaitOpts](#waitopts) * [DefaultWaitOpts](#defaultwaitopts) * [SentTx](#senttx) * [UnsafeContract](#unsafecontract) * [WaitForProvenOpts](#waitforprovenopts) * [DefaultWaitForProvenOpts](#defaultwaitforprovenopts) * [waitForProven](#waitforproven) * [Deployment](#deployment) * [broadcastPrivateFunction](#broadcastprivatefunction) * [broadcastUtilityFunction](#broadcastutilityfunction) * [ContractDeployer](#contractdeployer) * [publishContractClass](#publishcontractclass) * [publishInstance](#publishinstance) * [Ethereum](#ethereum) * [L2Claim](#l2claim) * [L2AmountClaim](#l2amountclaim) * [L2AmountClaimWithRecipient](#l2amountclaimwithrecipient) * [generateClaimSecret](#generateclaimsecret) * [L1TokenManager](#l1tokenmanager) * [L1FeeJuicePortalManager](#l1feejuiceportalmanager) * [L1ToL2TokenPortalManager](#l1tol2tokenportalmanager) * [L1TokenPortalManager](#l1tokenportalmanager) * [Fee](#fee) * [FeeJuicePaymentMethodWithClaim](#feejuicepaymentmethodwithclaim) * [FeePaymentMethod](#feepaymentmethod) * [PrivateFeePaymentMethod](#privatefeepaymentmethod) * [PublicFeePaymentMethod](#publicfeepaymentmethod) * [SponsoredFeePaymentMethod](#sponsoredfeepaymentmethod) * [Utils](#utils) * [FieldLike](#fieldlike) * [EthAddressLike](#ethaddresslike) * [AztecAddressLike](#aztecaddresslike) * [FunctionSelectorLike](#functionselectorlike) * [EventSelectorLike](#eventselectorlike) * [U128Like](#u128like) * [WrappedFieldLike](#wrappedfieldlike) * [IntentInnerHash](#intentinnerhash) * [CallIntent](#callintent) * [ContractFunctionInteractionCallIntent](#contractfunctioninteractioncallintent) * [computeAuthWitMessageHash](#computeauthwitmessagehash) * [getMessageHashFromIntent](#getmessagehashfromintent) * [computeInnerAuthWitHashFromAction](#computeinnerauthwithashfromaction) * [lookupValidity](#lookupvalidity) * [SetPublicAuthwitContractInteraction](#setpublicauthwitcontractinteraction) * [waitForL1ToL2MessageReady](#waitforl1tol2messageready) * [isL1ToL2MessageReady](#isl1tol2messageready) * [getFeeJuiceBalance](#getfeejuicebalance) * [readFieldCompressedString](#readfieldcompressedstring) * [waitForNode](#waitfornode) * [createAztecNodeClient](#createaztecnodeclient) * [AztecNode](#aztecnode) * [generatePublicKey](#generatepublickey) * [Wallet](#wallet) * [AccountEntrypointMetaPaymentMethod](#accountentrypointmetapaymentmethod) * [AccountManager](#accountmanager) * [RequestDeployAccountOptions](#requestdeployaccountoptions) * [DeployAccountOptions](#deployaccountoptions) * [SimulateDeployAccountOptions](#simulatedeployaccountoptions) * [DeployAccountMethod](#deployaccountmethod) * [Aliased](#aliased) * [SimulateOptions](#simulateoptions) * [ProfileOptions](#profileoptions) * [SendOptions](#sendoptions) * [BatchableMethods](#batchablemethods) * [BatchedMethod](#batchedmethod) * [BatchedMethodResult](#batchedmethodresult) * [BatchedMethodResultWrapper](#batchedmethodresultwrapper) * [BatchResults](#batchresults) * [PrivateEventFilter](#privateeventfilter) * [PrivateEvent](#privateevent) * [Wallet](#wallet) * [FunctionCallSchema](#functioncallschema) * [ExecutionPayloadSchema](#executionpayloadschema) * [GasSettingsOptionSchema](#gassettingsoptionschema) * [WalletSimulationFeeOptionSchema](#walletsimulationfeeoptionschema) * [SendOptionsSchema](#sendoptionsschema) * [SimulateOptionsSchema](#simulateoptionsschema) * [ProfileOptionsSchema](#profileoptionsschema) * [MessageHashOrIntentSchema](#messagehashorintentschema) * [BatchedMethodSchema](#batchedmethodschema) * [ContractMetadataSchema](#contractmetadataschema) * [ContractClassMetadataSchema](#contractclassmetadataschema) * [EventMetadataDefinitionSchema](#eventmetadatadefinitionschema) * [PrivateEventSchema](#privateeventschema) * [PrivateEventFilterSchema](#privateeventfilterschema) * [WalletSchema](#walletschema) *** ## Account[​](#account "Direct link to Account") *** ### `account/account_contract.ts`[​](#accountaccount_contractts "Direct link to accountaccount_contractts") #### AccountContract[​](#accountcontract "Direct link to AccountContract") **Type:** Interface An account contract instance. Knows its artifact, deployment arguments, how to create transaction execution requests out of function calls, and how to authorize actions. #### Methods[​](#methods "Direct link to Methods") ##### getContractArtifact[​](#getcontractartifact "Direct link to getContractArtifact") Returns the artifact of this account contract. **Signature:** ``` getContractArtifact(): Promise ``` **Returns:** `Promise` ##### getInitializationFunctionAndArgs[​](#getinitializationfunctionandargs "Direct link to getInitializationFunctionAndArgs") Returns the initializer function name and arguments for this instance, or undefined if this contract does not require initialization. **Signature:** ``` getInitializationFunctionAndArgs(): Promise<{ constructorName: string; constructorArgs: any[]; } | undefined> ``` **Returns:** ``` Promise< | { /** The name of the function used to initialize the contract */ constructorName: string; /** The args to the function used to initialize the contract */ constructorArgs: any[]; } | undefined > ``` ##### getInterface[​](#getinterface "Direct link to getInterface") Returns the account interface for this account contract given an instance at the provided address. The account interface is responsible for assembling tx requests given requested function calls, and for creating signed auth witnesses given action identifiers (message hashes). **Signature:** ``` getInterface( address: CompleteAddress, chainInfo: ChainInfo ): AccountInterface ``` **Parameters:** * `address`: `CompleteAddress` * Address of this account contract. * `chainInfo`: `ChainInfo` * Chain id and version of the rollup where the account contract is initialized / published. **Returns:** `AccountInterface` - An account interface instance for creating tx requests and authorizing actions. ##### getAuthWitnessProvider[​](#getauthwitnessprovider "Direct link to getAuthWitnessProvider") Returns the auth witness provider for the given address. **Signature:** ``` getAuthWitnessProvider(address: CompleteAddress): AuthWitnessProvider ``` **Parameters:** * `address`: `CompleteAddress` * Address for which to create auth witnesses. **Returns:** `AuthWitnessProvider` #### getAccountContractAddress[​](#getaccountcontractaddress "Direct link to getAccountContractAddress") **Type:** Function Compute the address of an account contract from secret and salt. **Signature:** ``` export async getAccountContractAddress( accountContract: AccountContract, secret: Fr, salt: Fr ) ``` **Parameters:** * `accountContract`: `AccountContract` * `secret`: `Fr` * `salt`: `Fr` **Returns:** `Promise` *** ### `account/account_with_secret_key.ts`[​](#accountaccount_with_secret_keyts "Direct link to accountaccount_with_secret_keyts") #### AccountWithSecretKey[​](#accountwithsecretkey "Direct link to AccountWithSecretKey") **Type:** Class Extends Account with the encryption private key. Not required for implementing the wallet interface but useful for testing purposes or exporting an account to another pxe. **Extends:** `BaseAccount` #### Constructor[​](#constructor "Direct link to Constructor") **Signature:** ``` constructor( account: AccountInterface, private secretKey: Fr, public readonly salt: Salt ) ``` **Parameters:** * `account`: `AccountInterface` * `secretKey`: `Fr` * `salt`: `Salt` * Deployment salt for this account contract. #### Methods[​](#methods-1 "Direct link to Methods") ##### getSecretKey[​](#getsecretkey "Direct link to getSecretKey") Returns the encryption private key associated with this account. **Signature:** ``` public getSecretKey() ``` **Returns:** `Fr` ##### getEncryptionSecret[​](#getencryptionsecret "Direct link to getEncryptionSecret") Returns the encryption secret, the secret of the encryption point—the point that others use to encrypt messages to this account note - this ensures that the address secret always corresponds to an address point with y being positive dev - this is also referred to as the address secret, which decrypts payloads encrypted to an address point **Signature:** ``` public async getEncryptionSecret() ``` **Returns:** `Promise` *** ### `account/account.ts`[​](#accountaccountts "Direct link to accountaccountts") #### Account[​](#account-1 "Direct link to Account") **Type:** Type Alias A type defining an account, capable of both creating authwits and using them to authenticate transaction execution requests. **Signature:** ``` export type Account = AccountInterface & AuthwitnessIntentProvider; ``` #### BaseAccount[​](#baseaccount "Direct link to BaseAccount") **Type:** Class An account implementation that uses authwits as an authentication mechanism and can assemble transaction execution requests for an entrypoint. **Implements:** `Account` #### Constructor[​](#constructor-1 "Direct link to Constructor") **Signature:** ``` constructor(protected account: AccountInterface) ``` **Parameters:** * `account`: `AccountInterface` #### Methods[​](#methods-2 "Direct link to Methods") ##### createTxExecutionRequest[​](#createtxexecutionrequest "Direct link to createTxExecutionRequest") **Signature:** ``` createTxExecutionRequest( exec: ExecutionPayload, gasSettings: GasSettings, options: DefaultAccountEntrypointOptions ): Promise ``` **Parameters:** * `exec`: `ExecutionPayload` * `gasSettings`: `GasSettings` * `options`: `DefaultAccountEntrypointOptions` **Returns:** `Promise` ##### getChainId[​](#getchainid "Direct link to getChainId") **Signature:** ``` getChainId(): Fr ``` **Returns:** `Fr` ##### getVersion[​](#getversion "Direct link to getVersion") **Signature:** ``` getVersion(): Fr ``` **Returns:** `Fr` ##### getCompleteAddress[​](#getcompleteaddress "Direct link to getCompleteAddress") Returns the complete address of the account that implements this wallet. **Signature:** ``` public getCompleteAddress() ``` **Returns:** `CompleteAddress` ##### getAddress[​](#getaddress "Direct link to getAddress") Returns the address of the account that implements this wallet. **Signature:** ``` public getAddress() ``` **Returns:** `any` ##### createAuthWit[​](#createauthwit "Direct link to createAuthWit") Computes an authentication witness from either a message hash or an intent. If a message hash is provided, it will create a witness for the hash directly. Otherwise, it will compute the message hash using the intent, along with the chain id and the version values provided by the wallet. **Signature:** ``` async createAuthWit(messageHashOrIntent: Fr | Buffer | CallIntent | IntentInnerHash): Promise ``` **Parameters:** * `messageHashOrIntent`: `Fr | Buffer | CallIntent | IntentInnerHash` * The message hash of the intent to approve **Returns:** `Promise` - The authentication witness *** ### `account/interface.ts`[​](#accountinterfacets "Direct link to accountinterfacets") #### AccountInterface[​](#accountinterface "Direct link to AccountInterface") **Type:** Interface Handler for interfacing with an account. Knows how to create transaction execution requests and authorize actions for its corresponding account. **Extends:** `EntrypointInterface`, `AuthWitnessProvider` #### Methods[​](#methods-3 "Direct link to Methods") ##### getCompleteAddress[​](#getcompleteaddress-1 "Direct link to getCompleteAddress") Returns the complete address for this account. **Signature:** ``` getCompleteAddress(): CompleteAddress ``` **Returns:** `CompleteAddress` ##### getAddress[​](#getaddress-1 "Direct link to getAddress") Returns the address for this account. **Signature:** ``` getAddress(): AztecAddress ``` **Returns:** `AztecAddress` ##### getChainId[​](#getchainid-1 "Direct link to getChainId") Returns the chain id for this account **Signature:** ``` getChainId(): Fr ``` **Returns:** `Fr` ##### getVersion[​](#getversion-1 "Direct link to getVersion") Returns the rollup version for this account **Signature:** ``` getVersion(): Fr ``` **Returns:** `Fr` *** ### `account/signerless_account.ts`[​](#accountsignerless_accountts "Direct link to accountsignerless_accountts") #### SignerlessAccount[​](#signerlessaccount "Direct link to SignerlessAccount") **Type:** Class Account implementation which creates a transaction using the multicall protocol contract as entrypoint. **Implements:** `Account` #### Constructor[​](#constructor-2 "Direct link to Constructor") **Signature:** ``` constructor(chainInfo: ChainInfo) ``` **Parameters:** * `chainInfo`: `ChainInfo` #### Methods[​](#methods-4 "Direct link to Methods") ##### createTxExecutionRequest[​](#createtxexecutionrequest-1 "Direct link to createTxExecutionRequest") **Signature:** ``` createTxExecutionRequest( exec: ExecutionPayload, gasSettings: GasSettings ): Promise ``` **Parameters:** * `exec`: `ExecutionPayload` * `gasSettings`: `GasSettings` **Returns:** `Promise` ##### getChainId[​](#getchainid-2 "Direct link to getChainId") **Signature:** ``` getChainId(): Fr ``` **Returns:** `Fr` ##### getVersion[​](#getversion-2 "Direct link to getVersion") **Signature:** ``` getVersion(): Fr ``` **Returns:** `Fr` ##### getCompleteAddress[​](#getcompleteaddress-2 "Direct link to getCompleteAddress") **Signature:** ``` getCompleteAddress(): CompleteAddress ``` **Returns:** `CompleteAddress` ##### getAddress[​](#getaddress-2 "Direct link to getAddress") **Signature:** ``` getAddress(): AztecAddress ``` **Returns:** `AztecAddress` ##### createAuthWit[​](#createauthwit-1 "Direct link to createAuthWit") **Signature:** ``` createAuthWit(_intent: Fr | Buffer | IntentInnerHash | CallIntent): Promise ``` **Parameters:** * `_intent`: `Fr | Buffer | IntentInnerHash | CallIntent` **Returns:** `Promise` ## Authorization[​](#authorization "Direct link to Authorization") *** ### `authorization/call_authorization_request.ts`[​](#authorizationcall_authorization_requestts "Direct link to authorizationcall_authorization_requestts") #### CallAuthorizationRequest[​](#callauthorizationrequest "Direct link to CallAuthorizationRequest") **Type:** Class An authwit request for a function call. Includes the preimage of the data to be signed, as opposed of just the inner hash. #### Constructor[​](#constructor-3 "Direct link to Constructor") **Signature:** ``` constructor( public selector: AuthorizationSelector, public innerHash: Fr, public msgSender: AztecAddress, public functionSelector: FunctionSelector, public argsHash: Fr, public args: Fr[] ) ``` **Parameters:** * `selector`: `AuthorizationSelector` * The selector of the authwit type, used to identify it when emitted from `emit_offchain_effect`oracle. Computed as poseidon2("CallAuthwit((Field),(u32),Field)".to\_bytes()) * `innerHash`: `Fr` * The inner hash of the authwit, computed as poseidon2(\[msg\_sender, selector, args\_hash]) * `msgSender`: `AztecAddress` * The address performing the call * `functionSelector`: `FunctionSelector` * The selector of the function that is to be authorized * `argsHash`: `Fr` * The hash of the arguments to the function call, * `args`: `Fr[]` * The arguments to the function call. #### Methods[​](#methods-5 "Direct link to Methods") ##### getSelector[​](#getselector "Direct link to getSelector") **Signature:** ``` static getSelector(): Promise ``` **Returns:** `Promise` ##### fromFields[​](#fromfields "Direct link to fromFields") **Signature:** ``` static async fromFields(fields: Fr[]): Promise ``` **Parameters:** * `fields`: `Fr[]` **Returns:** `Promise` ## Contract[​](#contract "Direct link to Contract") *** ### `contract/base_contract_interaction.ts`[​](#contractbase_contract_interactionts "Direct link to contractbase_contract_interactionts") #### BaseContractInteraction[​](#basecontractinteraction "Direct link to BaseContractInteraction") **Type:** Class Base class for an interaction with a contract, be it a deployment, a function call, or a batch. Implements the sequence create/simulate/send. #### Constructor[​](#constructor-4 "Direct link to Constructor") **Signature:** ``` constructor( protected wallet: Wallet, protected authWitnesses: AuthWitness[] = [], protected capsules: Capsule[] = [] ) ``` **Parameters:** * `wallet`: `Wallet` * `authWitnesses` (optional): `AuthWitness[]` * `capsules` (optional): `Capsule[]` #### Properties[​](#properties "Direct link to Properties") ##### log[​](#log "Direct link to log") **Type:** `any` #### Methods[​](#methods-6 "Direct link to Methods") ##### request[​](#request "Direct link to request") Returns an execution request that represents this operation. Can be used as a building block for constructing batch requests. **Signature:** ``` public abstract request(options?: RequestInteractionOptions): Promise ``` **Parameters:** * `options` (optional): `RequestInteractionOptions` * An optional object containing additional configuration for the transaction. **Returns:** `Promise` - An execution request wrapped in promise. ##### send[​](#send "Direct link to send") Sends a transaction to the contract function with the specified options. This function throws an error if called on a utility function. It creates and signs the transaction if necessary, and returns a SentTx instance, which can be used to track the transaction status, receipt, and events. **Signature:** ``` public send(options: SendInteractionOptions): SentTx ``` **Parameters:** * `options`: `SendInteractionOptions` * An object containing 'from' property representing the AztecAddress of the sender and optional fee configuration **Returns:** `SentTx` - A SentTx instance for tracking the transaction status and information. *** ### `contract/batch_call.ts`[​](#contractbatch_callts "Direct link to contractbatch_callts") #### BatchCall[​](#batchcall "Direct link to BatchCall") **Type:** Class A batch of function calls to be sent as a single transaction through a wallet. **Extends:** `BaseContractInteraction` #### Constructor[​](#constructor-5 "Direct link to Constructor") **Signature:** ``` constructor( wallet: Wallet, protected interactions: (BaseContractInteraction | ExecutionPayload)[] ) ``` **Parameters:** * `wallet`: `Wallet` * `interactions`: `(BaseContractInteraction | ExecutionPayload)[]` #### Methods[​](#methods-7 "Direct link to Methods") ##### request[​](#request-1 "Direct link to request") Returns an execution request that represents this operation. **Signature:** ``` public async request(options: RequestInteractionOptions = {}): Promise ``` **Parameters:** * `options` (optional): `RequestInteractionOptions` * An optional object containing additional configuration for the request generation. **Returns:** `Promise` - An execution payload wrapped in promise. ##### simulate[​](#simulate "Direct link to simulate") Simulates the batch, supporting private, public and utility functions. Although this is a single interaction with the wallet, private and public functions will be grouped into a single ExecutionPayload that the wallet will simulate as a single transaction. Utility function calls will simply be executed one by one. **Signature:** ``` public async simulate(options: SimulateInteractionOptions): Promise ``` **Parameters:** * `options`: `SimulateInteractionOptions` * An optional object containing additional configuration for the interaction. **Returns:** `Promise` - The results of all the interactions that make up the batch ##### getExecutionPayloads[​](#getexecutionpayloads "Direct link to getExecutionPayloads") **Signature:** ``` protected async getExecutionPayloads(): Promise ``` **Returns:** `Promise` *** ### `contract/checker.ts`[​](#contractcheckerts "Direct link to contractcheckerts") #### abiChecker[​](#abichecker "Direct link to abiChecker") **Type:** Function Validates the given ContractArtifact object by checking its functions and their parameters. Ensures that the ABI has at least one function, a constructor, valid bytecode, and correct parameter types. Throws an error if any inconsistency is detected during the validation process. **Signature:** ``` export abiChecker(artifact: ContractArtifact) ``` **Parameters:** * `artifact`: `ContractArtifact` * The ContractArtifact object to be validated. **Returns:** `boolean` - A boolean value indicating whether the artifact is valid or not. *** ### `contract/contract_base.ts`[​](#contractcontract_basets "Direct link to contractcontract_basets") #### ContractMethod[​](#contractmethod "Direct link to ContractMethod") **Type:** Type Alias Type representing a contract method that returns a ContractFunctionInteraction instance and has a readonly 'selector' property of type Buffer. Takes any number of arguments. **Signature:** ``` export type ContractMethod = ((...args: any[]) => ContractFunctionInteraction) & { selector: () => Promise; }; ``` **Type Members:** ##### selector[​](#selector "Direct link to selector") The unique identifier for a contract function in bytecode. **Type:** `() => Promise` #### ContractStorageLayout[​](#contractstoragelayout "Direct link to ContractStorageLayout") **Type:** Type Alias Type representing the storage layout of a contract. **Signature:** ``` export type ContractStorageLayout = { [K in T]: FieldLayout; }; ``` **Type Members:** ##### \[K in T][​](#k-in-t "Direct link to \[K in T]") **Signature:** `[K in T]: FieldLayout` **Key Type:** `T` **Value Type:** `FieldLayout` #### ContractBase[​](#contractbase "Direct link to ContractBase") **Type:** Class Abstract implementation of a contract extended by the Contract class and generated contract types. #### Constructor[​](#constructor-6 "Direct link to Constructor") **Signature:** ``` protected constructor( public readonly address: AztecAddress, public readonly artifact: ContractArtifact, public wallet: Wallet ) ``` **Parameters:** * `address`: `AztecAddress` * The contract's address. * `artifact`: `ContractArtifact` * The Application Binary Interface for the contract. * `wallet`: `Wallet` * The wallet used for interacting with this contract. #### Properties[​](#properties-1 "Direct link to Properties") ##### methods[​](#methods-8 "Direct link to methods") An object containing contract methods mapped to their respective names. **Type:** `{ [name: string]: ContractMethod }` #### Methods[​](#methods-9 "Direct link to Methods") ##### withWallet[​](#withwallet "Direct link to withWallet") Creates a new instance of the contract wrapper attached to a different wallet. **Signature:** ``` public withWallet(wallet: Wallet): this ``` **Parameters:** * `wallet`: `Wallet` * Wallet to use for sending txs. **Returns:** `this` - A new contract instance. *** ### `contract/contract_function_interaction.ts`[​](#contractcontract_function_interactionts "Direct link to contractcontract_function_interactionts") #### ContractFunctionInteraction[​](#contractfunctioninteraction "Direct link to ContractFunctionInteraction") **Type:** Class This is the class that is returned when calling e.g. `contract.methods.myMethod(arg0, arg1)`. It contains available interactions one can call on a method, including view. **Extends:** `BaseContractInteraction` #### Constructor[​](#constructor-7 "Direct link to Constructor") **Signature:** ``` constructor( wallet: Wallet, protected contractAddress: AztecAddress, protected functionDao: FunctionAbi, protected args: any[], authWitnesses: AuthWitness[] = [], capsules: Capsule[] = [], private extraHashedArgs: HashedValues[] = [] ) ``` **Parameters:** * `wallet`: `Wallet` * `contractAddress`: `AztecAddress` * `functionDao`: `FunctionAbi` * `args`: `any[]` * `authWitnesses` (optional): `AuthWitness[]` * `capsules` (optional): `Capsule[]` * `extraHashedArgs` (optional): `HashedValues[]` #### Methods[​](#methods-10 "Direct link to Methods") ##### getFunctionCall[​](#getfunctioncall "Direct link to getFunctionCall") Returns the encoded function call wrapped by this interaction Useful when generating authwits **Signature:** ``` public async getFunctionCall() ``` **Returns:** `Promise<{ name: any; args: any; selector: any; type: any; to: AztecAddress; isStatic: any; hideMsgSender: boolean; returnTypes: any; }>` - An encoded function call ##### request[​](#request-2 "Direct link to request") Returns the execution payload that allows this operation to happen on chain. **Signature:** ``` public override async request(options: RequestInteractionOptions = {}): Promise ``` **Parameters:** * `options` (optional): `RequestInteractionOptions` * Configuration options. **Returns:** `Promise` - The execution payload for this operation ##### simulate[​](#simulate-1 "Direct link to simulate") Simulate a transaction and get information from its execution. Differs from prove in a few important ways: 1. It returns the values of the function execution, plus additional metadata if requested 2. It supports `utility`, `private` and `public` functions **Signature:** ``` public async simulate(options: T): Promise['estimateGas']>> ``` **Parameters:** * `options`: `T` * An optional object containing additional configuration for the simulation. **Returns:** `Promise['estimateGas']>>` - Depending on the simulation options, this method directly returns the result value of the executed function or a rich object containing extra metadata, such as estimated gas costs (if requested via options), execution statistics and emitted offchain effects ##### simulate[​](#simulate-2 "Direct link to simulate") **Signature:** ``` public async simulate(options: T): Promise> ``` **Parameters:** * `options`: `T` **Returns:** `Promise>` ##### simulate[​](#simulate-3 "Direct link to simulate") **Signature:** ``` public async simulate(options: SimulateInteractionOptions): Promise> ``` **Parameters:** * `options`: `SimulateInteractionOptions` **Returns:** `Promise>` ##### profile[​](#profile "Direct link to profile") Simulate a transaction and profile the gate count for each function in the transaction. **Signature:** ``` public async profile(options: ProfileInteractionOptions): Promise ``` **Parameters:** * `options`: `ProfileInteractionOptions` * Same options as `simulate`, plus profiling method **Returns:** `Promise` - An object containing the function return value and profile result. ##### with[​](#with "Direct link to with") Augments this ContractFunctionInteraction with additional metadata, such as authWitnesses, capsules, and extraHashedArgs. This is useful when creating a "batteries included" interaction, such as registering a contract class with its associated capsule instead of having the user provide them externally. **Signature:** ``` public with({ authWitnesses = [], capsules = [], extraHashedArgs = [], }: { authWitnesses?: AuthWitness[]; capsules?: Capsule[]; extraHashedArgs?: HashedValues[]; }): ContractFunctionInteraction ``` **Parameters:** * `{ authWitnesses = [], capsules = [], extraHashedArgs = [], }`: `{ /** The authWitnesses to add to the interaction */ authWitnesses?: AuthWitness[]; /** The capsules to add to the interaction */ capsules?: Capsule[]; /** The extra hashed args to add to the interaction */ extraHashedArgs?: HashedValues[]; }` **Returns:** `ContractFunctionInteraction` - A new ContractFunctionInteraction with the added metadata, but calling the same original function in the same manner *** ### `contract/contract.ts`[​](#contractcontractts "Direct link to contractcontractts") #### Contract[​](#contract-1 "Direct link to Contract") **Type:** Class The Contract class represents a contract and provides utility methods for interacting with it. It enables the creation of ContractFunctionInteraction instances for each function in the contract's ABI, allowing users to call or send transactions to these functions. Additionally, the Contract class can be used to attach the contract instance to a deployed contract onchain through the PXE, which facilitates interaction with Aztec's privacy protocol. **Extends:** `ContractBase` #### Methods[​](#methods-11 "Direct link to Methods") ##### at[​](#at "Direct link to at") Gets a contract instance. **Signature:** ``` public static at( address: AztecAddress, artifact: ContractArtifact, wallet: Wallet ): Contract ``` **Parameters:** * `address`: `AztecAddress` * The address of the contract instance. * `artifact`: `ContractArtifact` * Build artifact of the contract. * `wallet`: `Wallet` * The wallet to use when interacting with the contract. **Returns:** `Contract` - A promise that resolves to a new Contract instance. ##### deploy[​](#deploy "Direct link to deploy") Creates a tx to deploy (initialize and/or publish) a new instance of a contract. **Signature:** ``` public static deploy( wallet: Wallet, artifact: ContractArtifact, args: any[], constructorName?: string ) ``` **Parameters:** * `wallet`: `Wallet` * The wallet for executing the deployment. * `artifact`: `ContractArtifact` * Build artifact of the contract to deploy * `args`: `any[]` * Arguments for the constructor. * `constructorName` (optional): `string` * The name of the constructor function to call. **Returns:** `DeployMethod` ##### deployWithPublicKeys[​](#deploywithpublickeys "Direct link to deployWithPublicKeys") Creates a tx to deploy (initialize and/or publish) a new instance of a contract using the specified public keys hash to derive the address. **Signature:** ``` public static deployWithPublicKeys( publicKeys: PublicKeys, wallet: Wallet, artifact: ContractArtifact, args: any[], constructorName?: string ) ``` **Parameters:** * `publicKeys`: `PublicKeys` * Hash of public keys to use for deriving the address. * `wallet`: `Wallet` * The wallet for executing the deployment. * `artifact`: `ContractArtifact` * Build artifact of the contract. * `args`: `any[]` * Arguments for the constructor. * `constructorName` (optional): `string` * The name of the constructor function to call. **Returns:** `DeployMethod` *** ### `contract/deploy_method.ts`[​](#contractdeploy_methodts "Direct link to contractdeploy_methodts") #### RequestDeployOptions[​](#requestdeployoptions "Direct link to RequestDeployOptions") **Type:** Type Alias Options for deploying a contract on the Aztec network. Allows specifying a contract address salt and different options to tweak contract publication and initialization **Signature:** ``` export type RequestDeployOptions = RequestInteractionOptions & { contractAddressSalt?: Fr; deployer?: AztecAddress; skipClassPublication?: boolean; skipInstancePublication?: boolean; skipInitialization?: boolean; skipRegistration?: boolean; }; ``` **Type Members:** ##### contractAddressSalt[​](#contractaddresssalt "Direct link to contractAddressSalt") An optional salt value used to deterministically calculate the contract address. **Type:** `Fr` ##### deployer[​](#deployer "Direct link to deployer") Deployer address that will be used for the deployed contract's address computation. If set to 0, the sender's address won't be mixed in **Type:** `AztecAddress` ##### skipClassPublication[​](#skipclasspublication "Direct link to skipClassPublication") Skip contract class publication. **Type:** `boolean` ##### skipInstancePublication[​](#skipinstancepublication "Direct link to skipInstancePublication") Skip publication, instead just privately initialize the contract. **Type:** `boolean` ##### skipInitialization[​](#skipinitialization "Direct link to skipInitialization") Skip contract initialization. **Type:** `boolean` ##### skipRegistration[​](#skipregistration "Direct link to skipRegistration") Skip contract registration in the wallet **Type:** `boolean` #### DeployOptions[​](#deployoptions "Direct link to DeployOptions") **Type:** Type Alias Extends the deployment options with the required parameters to send the transaction **Signature:** ``` export type DeployOptions = Omit & { universalDeploy?: boolean; } & Pick; ``` **Type Members:** ##### universalDeploy[​](#universaldeploy "Direct link to universalDeploy") Set to true to *not* include the sender in the address computation. This option is mutually exclusive with "deployer" **Type:** `boolean` #### SimulateDeployOptions[​](#simulatedeployoptions "Direct link to SimulateDeployOptions") **Type:** Type Alias Options for simulating the deployment of a contract Allows skipping certain validations and computing gas estimations **Signature:** ``` export type SimulateDeployOptions = Omit & { fee?: SimulationInteractionFeeOptions; skipTxValidation?: boolean; skipFeeEnforcement?: boolean; includeMetadata?: boolean; }; ``` **Type Members:** ##### fee[​](#fee "Direct link to fee") The fee options for the transaction. **Type:** `SimulationInteractionFeeOptions` ##### skipTxValidation[​](#skiptxvalidation "Direct link to skipTxValidation") Simulate without checking for the validity of the resulting transaction, e.g. whether it emits any existing nullifiers. **Type:** `boolean` ##### skipFeeEnforcement[​](#skipfeeenforcement "Direct link to skipFeeEnforcement") Whether to ensure the fee payer is not empty and has enough balance to pay for the fee. **Type:** `boolean` ##### includeMetadata[​](#includemetadata "Direct link to includeMetadata") Whether to include metadata such as offchain effects and performance statistics (e.g. timing information of the different circuits and oracles) in the simulation result, instead of just the return value of the function **Type:** `boolean` #### DeployMethod[​](#deploymethod "Direct link to DeployMethod") **Type:** Class Contract interaction for deployment. Handles class publication, instance publication, and initialization of the contract. Note that for some contracts, a tx is not required as part of its "creation": If there are no public functions, and if there are no initialization functions, then technically the contract has already been "created", and all of the contract's functions (private and utility) can be interacted-with immediately, without any "deployment tx". Extends the BaseContractInteraction class. **Extends:** `BaseContractInteraction` #### Constructor[​](#constructor-8 "Direct link to Constructor") **Signature:** ``` constructor( private publicKeys: PublicKeys, wallet: Wallet, protected artifact: ContractArtifact, protected postDeployCtor: (instance: ContractInstanceWithAddress, wallet: Wallet) => TContract, private args: any[] = [], constructorNameOrArtifact?: string | FunctionArtifact, authWitnesses: AuthWitness[] = [], capsules: Capsule[] = [] ) ``` **Parameters:** * `publicKeys`: `PublicKeys` * `wallet`: `Wallet` * `artifact`: `ContractArtifact` * `postDeployCtor`: `(instance: ContractInstanceWithAddress, wallet: Wallet) => TContract` * `args` (optional): `any[]` * `constructorNameOrArtifact` (optional): `string | FunctionArtifact` * `authWitnesses` (optional): `AuthWitness[]` * `capsules` (optional): `Capsule[]` #### Methods[​](#methods-12 "Direct link to Methods") ##### request[​](#request-3 "Direct link to request") Returns the execution payload that allows this operation to happen on chain. **Signature:** ``` public async request(options?: RequestDeployOptions): Promise ``` **Parameters:** * `options` (optional): `RequestDeployOptions` * Configuration options. **Returns:** `Promise` - The execution payload for this operation ##### convertDeployOptionsToRequestOptions[​](#convertdeployoptionstorequestoptions "Direct link to convertDeployOptionsToRequestOptions") **Signature:** ``` convertDeployOptionsToRequestOptions(options: DeployOptions): RequestDeployOptions ``` **Parameters:** * `options`: `DeployOptions` **Returns:** `RequestDeployOptions` ##### register[​](#register "Direct link to register") Adds this contract to the wallet and returns the Contract object. **Signature:** ``` public async register(options?: RequestDeployOptions): Promise ``` **Parameters:** * `options` (optional): `RequestDeployOptions` * Deployment options. **Returns:** `Promise` ##### getPublicationExecutionPayload[​](#getpublicationexecutionpayload "Direct link to getPublicationExecutionPayload") Returns an execution payload for: - publication of the contract class and - publication of the contract instance to enable public execution depending on the provided options. **Signature:** ``` protected async getPublicationExecutionPayload(options?: RequestDeployOptions): Promise ``` **Parameters:** * `options` (optional): `RequestDeployOptions` * Contract creation options. **Returns:** `Promise` - An execution payload with potentially calls (and bytecode capsule) to the class registry and instance registry. ##### getInitializationExecutionPayload[​](#getinitializationexecutionpayload "Direct link to getInitializationExecutionPayload") Returns the calls necessary to initialize the contract. **Signature:** ``` protected async getInitializationExecutionPayload(options?: RequestDeployOptions): Promise ``` **Parameters:** * `options` (optional): `RequestDeployOptions` * Deployment options. **Returns:** `Promise` - An array of function calls. ##### send[​](#send-1 "Direct link to send") Send a contract deployment transaction (initialize and/or publish) using the provided options. This function extends the 'send' method from the ContractFunctionInteraction class, allowing us to send a transaction specifically for contract deployment. **Signature:** ``` public override send(options: DeployOptions): DeploySentTx ``` **Parameters:** * `options`: `DeployOptions` * An object containing various deployment options such as contractAddressSalt and from. **Returns:** `DeploySentTx` - A SentTx object that returns the receipt and the deployed contract instance. ##### getInstance[​](#getinstance "Direct link to getInstance") Builds the contract instance and returns it. **Signature:** ``` public async getInstance(options?: RequestDeployOptions): Promise ``` **Parameters:** * `options` (optional): `RequestDeployOptions` * An object containing various initialization and publication options. **Returns:** `Promise` - An instance object. ##### simulate[​](#simulate-4 "Direct link to simulate") Simulate the deployment **Signature:** ``` public async simulate(options: SimulateDeployOptions): Promise> ``` **Parameters:** * `options`: `SimulateDeployOptions` * An optional object containing additional configuration for the simulation. **Returns:** `Promise>` - A simulation result object containing metadata of the execution, including gas estimations (if requested via options), execution statistics and emitted offchain effects ##### profile[​](#profile-1 "Direct link to profile") Simulate a deployment and profile the gate count for each function in the transaction. **Signature:** ``` public async profile(options: DeployOptions & ProfileInteractionOptions): Promise ``` **Parameters:** * `options`: `DeployOptions & ProfileInteractionOptions` * Same options as `send`, plus extra profiling options. **Returns:** `Promise` - An object containing the function return value and profile result. ##### with[​](#with-1 "Direct link to with") Augments this DeployMethod with additional metadata, such as authWitnesses and capsules. **Signature:** ``` public with({ authWitnesses = [], capsules = [], }: { authWitnesses?: AuthWitness[]; capsules?: Capsule[]; }): DeployMethod ``` **Parameters:** * `{ authWitnesses = [], capsules = [], }`: `{ /** The authWitnesses to add to the deployment */ authWitnesses?: AuthWitness[]; /** The capsules to add to the deployment */ capsules?: Capsule[]; }` **Returns:** `DeployMethod` - A new DeployMethod with the added metadata, but calling the same original function in the same manner #### Getters[​](#getters "Direct link to Getters") ##### address (getter)[​](#address-getter "Direct link to address (getter)") Return this deployment address. **Signature:** ``` public get address() { ``` **Returns:** `any` ##### partialAddress (getter)[​](#partialaddress-getter "Direct link to partialAddress (getter)") Returns the partial address for this deployment. **Signature:** ``` public get partialAddress() { ``` **Returns:** `any` *** ### `contract/deploy_sent_tx.ts`[​](#contractdeploy_sent_txts "Direct link to contractdeploy_sent_txts") #### DeployedWaitOpts[​](#deployedwaitopts "Direct link to DeployedWaitOpts") **Type:** Type Alias Options related to waiting for a deployment tx. **Signature:** ``` export type DeployedWaitOpts = WaitOpts & { wallet?: Wallet; }; ``` **Type Members:** ##### wallet[​](#wallet "Direct link to wallet") Wallet to use for creating a contract instance. Uses the one set in the deployer constructor if not set. **Type:** `Wallet` #### DeployTxReceipt[​](#deploytxreceipt "Direct link to DeployTxReceipt") **Type:** Type Alias Extends a transaction receipt with a contract instance that represents the newly deployed contract. **Signature:** ``` export type DeployTxReceipt = FieldsOf & { contract: TContract; instance: ContractInstanceWithAddress; }; ``` **Type Members:** ##### contract[​](#contract-2 "Direct link to contract") Instance of the newly deployed contract. **Type:** `TContract` ##### instance[​](#instance "Direct link to instance") The deployed contract instance with address and metadata. **Type:** `ContractInstanceWithAddress` #### DeploySentTx[​](#deploysenttx "Direct link to DeploySentTx") **Type:** Class A contract deployment transaction sent to the network, extending SentTx with methods to publish a contract instance. **Extends:** `SentTx` #### Constructor[​](#constructor-9 "Direct link to Constructor") **Signature:** ``` constructor( wallet: Wallet, sendTx: () => Promise, private postDeployCtor: (instance: ContractInstanceWithAddress, wallet: Wallet) => TContract, private instanceGetter: () => Promise ) ``` **Parameters:** * `wallet`: `Wallet` * `sendTx`: `() => Promise` * `postDeployCtor`: `(instance: ContractInstanceWithAddress, wallet: Wallet) => TContract` * `instanceGetter`: `() => Promise` * A getter for the deployed contract instance #### Methods[​](#methods-13 "Direct link to Methods") ##### getInstance[​](#getinstance-1 "Direct link to getInstance") Returns the contract instance for this deployment. **Signature:** ``` public async getInstance(): Promise ``` **Returns:** `Promise` - The deployed contract instance with address and metadata. ##### deployed[​](#deployed "Direct link to deployed") Awaits for the tx to be mined and returns the contract instance. Throws if tx is not mined. **Signature:** ``` public async deployed(opts?: DeployedWaitOpts): Promise ``` **Parameters:** * `opts` (optional): `DeployedWaitOpts` * Options for configuring the waiting for the tx to be mined. **Returns:** `Promise` - The deployed contract instance. ##### wait[​](#wait "Direct link to wait") Awaits for the tx to be mined and returns the receipt along with a contract instance. Throws if tx is not mined. **Signature:** ``` public override async wait(opts?: DeployedWaitOpts): Promise> ``` **Parameters:** * `opts` (optional): `DeployedWaitOpts` * Options for configuring the waiting for the tx to be mined. **Returns:** `Promise>` - The transaction receipt with the deployed contract instance. *** ### `contract/get_gas_limits.ts`[​](#contractget_gas_limitsts "Direct link to contractget_gas_limitsts") #### getGasLimits[​](#getgaslimits "Direct link to getGasLimits") **Type:** Function Returns suggested total and teardown gas limits for a simulated tx. **Signature:** ``` export getGasLimits( simulationResult: TxSimulationResult, pad = 0.1 ): { gasLimits: Gas; teardownGasLimits: Gas; } ``` **Parameters:** * `simulationResult`: `TxSimulationResult` * `pad` (optional): `any` * Percentage to pad the suggested gas limits by, (as decimal, e.g., 0.10 for 10%). **Returns:** ``` { /** * Gas limit for the tx, excluding teardown gas */ gasLimits: Gas; /** * Gas limit for the teardown phase */ teardownGasLimits: Gas; } ``` *** ### `contract/interaction_options.ts`[​](#contractinteraction_optionsts "Direct link to contractinteraction_optionsts") #### FeeEstimationOptions[​](#feeestimationoptions "Direct link to FeeEstimationOptions") **Type:** Type Alias Options used to tweak the simulation and add gas estimation capabilities **Signature:** ``` export type FeeEstimationOptions = { estimateGas?: boolean; estimatedGasPadding?: number; }; ``` **Type Members:** ##### estimateGas[​](#estimategas "Direct link to estimateGas") Whether to modify the fee settings of the simulation with high gas limit to figure out actual gas settings. **Type:** `boolean` ##### estimatedGasPadding[​](#estimatedgaspadding "Direct link to estimatedGasPadding") Percentage to pad the estimated gas limits by, if empty, defaults to 0.1. Only relevant if estimateGas is set. **Type:** `number` #### FeePaymentMethodOption[​](#feepaymentmethodoption "Direct link to FeePaymentMethodOption") **Type:** Type Alias Interactions allow configuring a custom fee payment method that gets bundled with the transaction before sending it to the wallet **Signature:** ``` export type FeePaymentMethodOption = { paymentMethod?: FeePaymentMethod; }; ``` **Type Members:** ##### paymentMethod[​](#paymentmethod "Direct link to paymentMethod") Fee payment method to embed in the interaction **Type:** `FeePaymentMethod` #### GasSettingsOption[​](#gassettingsoption "Direct link to GasSettingsOption") **Type:** Type Alias User-defined partial gas settings for the interaction. This type is completely optional since the wallet will fill in the missing options **Signature:** ``` export type GasSettingsOption = { gasSettings?: Partial>; }; ``` **Type Members:** ##### gasSettings[​](#gassettings "Direct link to gasSettings") The gas settings **Type:** `Partial>` #### InteractionFeeOptions[​](#interactionfeeoptions "Direct link to InteractionFeeOptions") **Type:** Type Alias Fee options as set by a user. **Signature:** ``` export type InteractionFeeOptions = GasSettingsOption & FeePaymentMethodOption; ``` #### SimulationInteractionFeeOptions[​](#simulationinteractionfeeoptions "Direct link to SimulationInteractionFeeOptions") **Type:** Type Alias Fee options that can be set for simulation *only* **Signature:** ``` export type SimulationInteractionFeeOptions = InteractionFeeOptions & FeeEstimationOptions; ``` #### RequestInteractionOptions[​](#requestinteractionoptions "Direct link to RequestInteractionOptions") **Type:** Type Alias Represents the options to configure a request from a contract interaction. Allows specifying additional auth witnesses and capsules to use during execution **Signature:** ``` export type RequestInteractionOptions = { authWitnesses?: AuthWitness[]; capsules?: Capsule[]; fee?: FeePaymentMethodOption; }; ``` **Type Members:** ##### authWitnesses[​](#authwitnesses "Direct link to authWitnesses") Extra authwits to use during execution **Type:** `AuthWitness[]` ##### capsules[​](#capsules "Direct link to capsules") Extra capsules to use during execution **Type:** `Capsule[]` ##### fee[​](#fee-1 "Direct link to fee") Fee payment method to embed in the interaction request **Type:** `FeePaymentMethodOption` #### SendInteractionOptions[​](#sendinteractionoptions "Direct link to SendInteractionOptions") **Type:** Type Alias Represents options for calling a (constrained) function in a contract. **Signature:** ``` export type SendInteractionOptions = RequestInteractionOptions & { from: AztecAddress; fee?: InteractionFeeOptions; }; ``` **Type Members:** ##### from[​](#from "Direct link to from") The sender's Aztec address. **Type:** `AztecAddress` ##### fee[​](#fee-2 "Direct link to fee") The fee options for the transaction. **Type:** `InteractionFeeOptions` #### SimulateInteractionOptions[​](#simulateinteractionoptions "Direct link to SimulateInteractionOptions") **Type:** Type Alias Represents the options for simulating a contract function interaction. Allows specifying the address from which the method should be called. Disregarded for simulation of public functions **Signature:** ``` export type SimulateInteractionOptions = Omit & { fee?: SimulationInteractionFeeOptions; skipTxValidation?: boolean; skipFeeEnforcement?: boolean; includeMetadata?: boolean; }; ``` **Type Members:** ##### fee[​](#fee-3 "Direct link to fee") The fee options for the transaction. **Type:** `SimulationInteractionFeeOptions` ##### skipTxValidation[​](#skiptxvalidation-1 "Direct link to skipTxValidation") Simulate without checking for the validity of the resulting transaction, e.g. whether it emits any existing nullifiers. **Type:** `boolean` ##### skipFeeEnforcement[​](#skipfeeenforcement-1 "Direct link to skipFeeEnforcement") Whether to ensure the fee payer is not empty and has enough balance to pay for the fee. **Type:** `boolean` ##### includeMetadata[​](#includemetadata-1 "Direct link to includeMetadata") Whether to include metadata such as offchain effects and performance statistics (e.g. timing information of the different circuits and oracles) in the simulation result, instead of just the return value of the function **Type:** `boolean` #### ProfileInteractionOptions[​](#profileinteractionoptions "Direct link to ProfileInteractionOptions") **Type:** Type Alias Represents the options for profiling an interaction. **Signature:** ``` export type ProfileInteractionOptions = SimulateInteractionOptions & { profileMode: 'gates' | 'execution-steps' | 'full'; skipProofGeneration?: boolean; }; ``` **Type Members:** ##### profileMode[​](#profilemode "Direct link to profileMode") Whether to return gates information or the bytecode/witnesses. **Type:** `'gates' | 'execution-steps' | 'full'` ##### skipProofGeneration[​](#skipproofgeneration "Direct link to skipProofGeneration") Whether to generate a Chonk proof or not **Type:** `boolean` #### SimulationReturn[​](#simulationreturn "Direct link to SimulationReturn") **Type:** Type Alias Represents the result type of a simulation. By default, it will just be the return value of the simulated function If `includeMetadata` is set to true in `SimulateInteractionOptions` on the input of `simulate(...)`, it will provide extra information. **Signature:** ``` export type SimulationReturn = T extends true ? { stats: SimulationStats; offchainEffects: OffchainEffect[]; result: any; estimatedGas: Pick; } : any; ``` #### toSendOptions[​](#tosendoptions "Direct link to toSendOptions") **Type:** Function Transforms and cleans up the higher level SendInteractionOptions defined by the interaction into SendOptions, which are the ones that can be serialized and forwarded to the wallet **Signature:** ``` export toSendOptions(options: SendInteractionOptions): SendOptions ``` **Parameters:** * `options`: `SendInteractionOptions` **Returns:** `SendOptions` #### toSimulateOptions[​](#tosimulateoptions "Direct link to toSimulateOptions") **Type:** Function Transforms and cleans up the higher level SimulateInteractionOptions defined by the interaction into SimulateOptions, which are the ones that can be serialized and forwarded to the wallet **Signature:** ``` export toSimulateOptions(options: SimulateInteractionOptions): SimulateOptions ``` **Parameters:** * `options`: `SimulateInteractionOptions` **Returns:** `SimulateOptions` #### toProfileOptions[​](#toprofileoptions "Direct link to toProfileOptions") **Type:** Function Transforms and cleans up the higher level ProfileInteractionOptions defined by the interaction into ProfileOptions, which are the ones that can be serialized and forwarded to the wallet **Signature:** ``` export toProfileOptions(options: ProfileInteractionOptions): ProfileOptions ``` **Parameters:** * `options`: `ProfileInteractionOptions` **Returns:** `ProfileOptions` *** ### `contract/protocol_contracts.ts`[​](#contractprotocol_contractsts "Direct link to contractprotocol_contractsts") #### getClassRegistryContract[​](#getclassregistrycontract "Direct link to getClassRegistryContract") **Type:** Function Returns a Contract wrapper for the contract class registry. **Signature:** ``` export async getClassRegistryContract(wallet: Wallet) ``` **Parameters:** * `wallet`: `Wallet` **Returns:** `Promise` #### getInstanceRegistryContract[​](#getinstanceregistrycontract "Direct link to getInstanceRegistryContract") **Type:** Function Returns a Contract wrapper for the contract instance registry. **Signature:** ``` export async getInstanceRegistryContract(wallet: Wallet) ``` **Parameters:** * `wallet`: `Wallet` **Returns:** `Promise` #### getFeeJuice[​](#getfeejuice "Direct link to getFeeJuice") **Type:** Function Returns a Contract wrapper for the fee juice contract **Signature:** ``` export async getFeeJuice(wallet: Wallet) ``` **Parameters:** * `wallet`: `Wallet` **Returns:** `Promise` *** ### `contract/sent_tx.ts`[​](#contractsent_txts "Direct link to contractsent_txts") #### WaitOpts[​](#waitopts "Direct link to WaitOpts") **Type:** Type Alias Options related to waiting for a tx. **Signature:** ``` export type WaitOpts = { ignoreDroppedReceiptsFor?: number; timeout?: number; interval?: number; dontThrowOnRevert?: boolean; }; ``` **Type Members:** ##### ignoreDroppedReceiptsFor[​](#ignoredroppedreceiptsfor "Direct link to ignoreDroppedReceiptsFor") The amount of time to ignore TxStatus.DROPPED receipts (in seconds) due to the presumption that it is being propagated by the p2p network. Defaults to 5. **Type:** `number` ##### timeout[​](#timeout "Direct link to timeout") The maximum time (in seconds) to wait for the transaction to be mined. Defaults to 60. **Type:** `number` ##### interval[​](#interval "Direct link to interval") The time interval (in seconds) between retries to fetch the transaction receipt. Defaults to 1. **Type:** `number` ##### dontThrowOnRevert[​](#dontthrowonrevert "Direct link to dontThrowOnRevert") Whether to accept a revert as a status code for the tx when waiting for it. If false, will throw if the tx reverts. **Type:** `boolean` #### DefaultWaitOpts[​](#defaultwaitopts "Direct link to DefaultWaitOpts") **Type:** Constant **Value Type:** `WaitOpts` #### SentTx[​](#senttx "Direct link to SentTx") **Type:** Class The SentTx class represents a sent transaction through the PXE (or directly to a node) providing methods to fetch its hash, receipt, and mining status. #### Constructor[​](#constructor-10 "Direct link to Constructor") **Signature:** ``` constructor( protected walletOrNode: Wallet | AztecNode, sendTx: () => Promise ) ``` **Parameters:** * `walletOrNode`: `Wallet | AztecNode` * `sendTx`: `() => Promise` #### Properties[​](#properties-2 "Direct link to Properties") ##### sendTxPromise[​](#sendtxpromise "Direct link to sendTxPromise") **Type:** `Promise` ##### sendTxError[​](#sendtxerror "Direct link to sendTxError") **Type:** `Error` ##### txHash[​](#txhash "Direct link to txHash") **Type:** `TxHash` #### Methods[​](#methods-14 "Direct link to Methods") ##### getTxHash[​](#gettxhash "Direct link to getTxHash") Retrieves the transaction hash of the SentTx instance. The function internally awaits for the 'txHashPromise' to resolve, and then returns the resolved transaction hash. **Signature:** ``` public async getTxHash(): Promise ``` **Returns:** `Promise` - A promise that resolves to the transaction hash of the SentTx instance. TODO(#7717): Don't throw here. ##### getReceipt[​](#getreceipt "Direct link to getReceipt") Retrieve the transaction receipt associated with the current SentTx instance. The function fetches the transaction hash using 'getTxHash' and then queries the PXE to get the corresponding transaction receipt. **Signature:** ``` public async getReceipt(): Promise ``` **Returns:** `Promise` - A promise that resolves to a TxReceipt object representing the fetched transaction receipt. ##### wait[​](#wait-1 "Direct link to wait") Awaits for a tx to be mined and returns the receipt. Throws if tx is not mined. **Signature:** ``` public async wait(opts?: WaitOpts): Promise> ``` **Parameters:** * `opts` (optional): `WaitOpts` * Options for configuring the waiting for the tx to be mined. **Returns:** `Promise>` - The transaction receipt. ##### waitForReceipt[​](#waitforreceipt "Direct link to waitForReceipt") **Signature:** ``` protected async waitForReceipt(opts?: WaitOpts): Promise ``` **Parameters:** * `opts` (optional): `WaitOpts` **Returns:** `Promise` *** ### `contract/unsafe_contract.ts`[​](#contractunsafe_contractts "Direct link to contractunsafe_contractts") #### UnsafeContract[​](#unsafecontract "Direct link to UnsafeContract") **Type:** Class Unsafe constructor for ContractBase that bypasses the check that the instance is registered in the wallet. **Extends:** `ContractBase` #### Constructor[​](#constructor-11 "Direct link to Constructor") **Signature:** ``` constructor( instance: ContractInstanceWithAddress, artifact: ContractArtifact, wallet: Wallet ) ``` **Parameters:** * `instance`: `ContractInstanceWithAddress` * The deployed contract instance definition. * `artifact`: `ContractArtifact` * The Application Binary Interface for the contract. * `wallet`: `Wallet` * The wallet used for interacting with this contract. *** ### `contract/wait_for_proven.ts`[​](#contractwait_for_provents "Direct link to contractwait_for_provents") #### WaitForProvenOpts[​](#waitforprovenopts "Direct link to WaitForProvenOpts") **Type:** Type Alias Options for waiting for a transaction to be proven. **Signature:** ``` export type WaitForProvenOpts = { provenTimeout?: number; interval?: number; }; ``` **Type Members:** ##### provenTimeout[​](#proventimeout "Direct link to provenTimeout") Time to wait for the tx to be proven before timing out **Type:** `number` ##### interval[​](#interval-1 "Direct link to interval") Elapsed time between polls to the node **Type:** `number` #### DefaultWaitForProvenOpts[​](#defaultwaitforprovenopts "Direct link to DefaultWaitForProvenOpts") **Type:** Constant **Value Type:** `WaitForProvenOpts` #### waitForProven[​](#waitforproven "Direct link to waitForProven") **Type:** Function Wait for a transaction to be proven by polling the node **Signature:** ``` export async waitForProven( node: AztecNode, receipt: TxReceipt, opts?: WaitForProvenOpts ) ``` **Parameters:** * `node`: `AztecNode` * `receipt`: `TxReceipt` * `opts` (optional): `WaitForProvenOpts` **Returns:** `Promise` ## Deployment[​](#deployment "Direct link to Deployment") *** ### `deployment/broadcast_function.ts`[​](#deploymentbroadcast_functionts "Direct link to deploymentbroadcast_functionts") #### broadcastPrivateFunction[​](#broadcastprivatefunction "Direct link to broadcastPrivateFunction") **Type:** Function Sets up a call to broadcast a private function's bytecode via the ClassRegistry contract. Note that this is not required for users to call the function, but is rather a convenience to make this code publicly available so dapps or wallets do not need to redistribute it. **Signature:** ``` export async broadcastPrivateFunction( wallet: Wallet, artifact: ContractArtifact, selector: FunctionSelector ): Promise ``` **Parameters:** * `wallet`: `Wallet` * Wallet to send the transaction. * `artifact`: `ContractArtifact` * Contract artifact that contains the function to be broadcast. * `selector`: `FunctionSelector` * Selector of the function to be broadcast. **Returns:** `Promise` - A ContractFunctionInteraction object that can be used to send the transaction. #### broadcastUtilityFunction[​](#broadcastutilityfunction "Direct link to broadcastUtilityFunction") **Type:** Function Sets up a call to broadcast a utility function's bytecode via the ClassRegistry contract. Note that this is not required for users to call the function, but is rather a convenience to make this code publicly available so dapps or wallets do not need to redistribute it. **Signature:** ``` export async broadcastUtilityFunction( wallet: Wallet, artifact: ContractArtifact, selector: FunctionSelector ): Promise ``` **Parameters:** * `wallet`: `Wallet` * Wallet to send the transaction. * `artifact`: `ContractArtifact` * Contract artifact that contains the function to be broadcast. * `selector`: `FunctionSelector` * Selector of the function to be broadcast. **Returns:** `Promise` - A ContractFunctionInteraction object that can be used to send the transaction. *** ### `deployment/contract_deployer.ts`[​](#deploymentcontract_deployerts "Direct link to deploymentcontract_deployerts") #### ContractDeployer[​](#contractdeployer "Direct link to ContractDeployer") **Type:** Class A class for deploying contract. #### Constructor[​](#constructor-12 "Direct link to Constructor") **Signature:** ``` constructor( private artifact: ContractArtifact, private wallet: Wallet, private publicKeys?: PublicKeys, private constructorName?: string ) ``` **Parameters:** * `artifact`: `ContractArtifact` * `wallet`: `Wallet` * `publicKeys` (optional): `PublicKeys` * `constructorName` (optional): `string` #### Methods[​](#methods-15 "Direct link to Methods") ##### deploy[​](#deploy-1 "Direct link to deploy") Deploy a contract using the provided ABI and constructor arguments. This function creates a new DeployMethod instance that can be used to send deployment transactions and query deployment status. The method accepts any number of constructor arguments, which will be passed to the contract's constructor during deployment. **Signature:** ``` public deploy(...args: any[]) ``` **Parameters:** * `args`: `any[]` * The constructor arguments for the contract being deployed. **Returns:** `DeployMethod` - A DeployMethod instance configured with the ABI, PXE, and constructor arguments. *** ### `deployment/publish_class.ts`[​](#deploymentpublish_classts "Direct link to deploymentpublish_classts") #### publishContractClass[​](#publishcontractclass "Direct link to publishContractClass") **Type:** Function Sets up a call to publish a contract class given its artifact. **Signature:** ``` export async publishContractClass( wallet: Wallet, artifact: ContractArtifact ): Promise ``` **Parameters:** * `wallet`: `Wallet` * `artifact`: `ContractArtifact` **Returns:** `Promise` *** ### `deployment/publish_instance.ts`[​](#deploymentpublish_instancets "Direct link to deploymentpublish_instancets") #### publishInstance[​](#publishinstance "Direct link to publishInstance") **Type:** Function Sets up a call to the canonical contract instance registry to publish a contract instance. **Signature:** ``` export async publishInstance( wallet: Wallet, instance: ContractInstanceWithAddress ): Promise ``` **Parameters:** * `wallet`: `Wallet` * The wallet to use for the publication (setup) tx. * `instance`: `ContractInstanceWithAddress` * The instance to publish. **Returns:** `Promise` ## Ethereum[​](#ethereum "Direct link to Ethereum") *** ### `ethereum/portal_manager.ts`[​](#ethereumportal_managerts "Direct link to ethereumportal_managerts") #### L2Claim[​](#l2claim "Direct link to L2Claim") **Type:** Type Alias L1 to L2 message info to claim it on L2. **Signature:** ``` export type L2Claim = { claimSecret: Fr; claimSecretHash: Fr; messageHash: Hex; messageLeafIndex: bigint; }; ``` **Type Members:** ##### claimSecret[​](#claimsecret "Direct link to claimSecret") Secret for claiming. **Type:** `Fr` ##### claimSecretHash[​](#claimsecrethash "Direct link to claimSecretHash") Hash of the secret for claiming. **Type:** `Fr` ##### messageHash[​](#messagehash "Direct link to messageHash") Hash of the message. **Type:** `Hex` ##### messageLeafIndex[​](#messageleafindex "Direct link to messageLeafIndex") Leaf index in the L1 to L2 message tree. **Type:** `bigint` #### L2AmountClaim[​](#l2amountclaim "Direct link to L2AmountClaim") **Type:** Type Alias L1 to L2 message info that corresponds to an amount to claim. **Signature:** ``` export type L2AmountClaim = L2Claim & { claimAmount: bigint }; ``` **Type Members:** ##### claimAmount[​](#claimamount "Direct link to claimAmount") **Type:** `bigint` #### L2AmountClaimWithRecipient[​](#l2amountclaimwithrecipient "Direct link to L2AmountClaimWithRecipient") **Type:** Type Alias L1 to L2 message info that corresponds to an amount to claim with associated recipient. **Signature:** ``` export type L2AmountClaimWithRecipient = L2AmountClaim & { recipient: AztecAddress; }; ``` **Type Members:** ##### recipient[​](#recipient "Direct link to recipient") Address that will receive the newly minted notes. **Type:** `AztecAddress` #### generateClaimSecret[​](#generateclaimsecret "Direct link to generateClaimSecret") **Type:** Function Generates a pair secret and secret hash **Signature:** ``` export async generateClaimSecret(logger?: Logger): Promise<[ Fr, Fr ]> ``` **Parameters:** * `logger` (optional): `Logger` **Returns:** `Promise<[Fr, Fr]>` #### L1TokenManager[​](#l1tokenmanager "Direct link to L1TokenManager") **Type:** Class Helper for managing an ERC20 on L1. #### Constructor[​](#constructor-13 "Direct link to Constructor") **Signature:** ``` public constructor( public readonly tokenAddress: EthAddress, public readonly handlerAddress: EthAddress | undefined, private readonly extendedClient: ExtendedViemWalletClient, private logger: Logger ) ``` **Parameters:** * `tokenAddress`: `EthAddress` * Address of the ERC20 contract. * `handlerAddress`: `EthAddress | undefined` * Address of the handler/faucet contract. * `extendedClient`: `ExtendedViemWalletClient` * `logger`: `Logger` #### Methods[​](#methods-16 "Direct link to Methods") ##### getMintAmount[​](#getmintamount "Direct link to getMintAmount") Returns the amount of tokens available to mint via the handler. **Signature:** ``` public async getMintAmount() ``` **Returns:** `Promise` ##### getL1TokenBalance[​](#getl1tokenbalance "Direct link to getL1TokenBalance") Returns the balance of the given address. **Signature:** ``` public async getL1TokenBalance(address: Hex) ``` **Parameters:** * `address`: `Hex` * Address to get the balance of. **Returns:** `Promise` ##### mint[​](#mint "Direct link to mint") Mints a fixed amount of tokens for the given address. Returns once the tx has been mined. **Signature:** ``` public async mint( address: Hex, addressName?: string ) ``` **Parameters:** * `address`: `Hex` * Address to mint the tokens for. * `addressName` (optional): `string` * Optional name of the address for logging. **Returns:** `Promise` ##### approve[​](#approve "Direct link to approve") Approves tokens for the given address. Returns once the tx has been mined. **Signature:** ``` public async approve( amount: bigint, address: Hex, addressName = '' ) ``` **Parameters:** * `amount`: `bigint` * Amount to approve. * `address`: `Hex` * Address to approve the tokens for. * `addressName` (optional): `any` * Optional name of the address for logging. **Returns:** `Promise` #### L1FeeJuicePortalManager[​](#l1feejuiceportalmanager "Direct link to L1FeeJuicePortalManager") **Type:** Class Helper for interacting with the FeeJuicePortal on L1. #### Constructor[​](#constructor-14 "Direct link to Constructor") **Signature:** ``` constructor( portalAddress: EthAddress, tokenAddress: EthAddress, handlerAddress: EthAddress, private readonly extendedClient: ExtendedViemWalletClient, private readonly logger: Logger ) ``` **Parameters:** * `portalAddress`: `EthAddress` * `tokenAddress`: `EthAddress` * `handlerAddress`: `EthAddress` * `extendedClient`: `ExtendedViemWalletClient` * `logger`: `Logger` #### Methods[​](#methods-17 "Direct link to Methods") ##### getTokenManager[​](#gettokenmanager "Direct link to getTokenManager") Returns the associated token manager for the L1 ERC20. **Signature:** ``` public getTokenManager() ``` **Returns:** `L1TokenManager` ##### bridgeTokensPublic[​](#bridgetokenspublic "Direct link to bridgeTokensPublic") Bridges fee juice from L1 to L2 publicly. Handles L1 ERC20 approvals. Returns once the tx has been mined. **Signature:** ``` public async bridgeTokensPublic( to: AztecAddress, amount: bigint | undefined, mint = false ): Promise ``` **Parameters:** * `to`: `AztecAddress` * Address to send the tokens to on L2. * `amount`: `bigint | undefined` * Amount of tokens to send. * `mint` (optional): `any` * Whether to mint the tokens before sending (only during testing). **Returns:** `Promise` ##### new[​](#new "Direct link to new") Creates a new instance **Signature:** ``` public static async new( node: AztecNode, extendedClient: ExtendedViemWalletClient, logger: Logger ): Promise ``` **Parameters:** * `node`: `AztecNode` * Aztec node client used for retrieving the L1 contract addresses. * `extendedClient`: `ExtendedViemWalletClient` * Wallet client, extended with public actions. * `logger`: `Logger` * Logger. **Returns:** `Promise` #### L1ToL2TokenPortalManager[​](#l1tol2tokenportalmanager "Direct link to L1ToL2TokenPortalManager") **Type:** Class Helper for interacting with a test TokenPortal on L1 for sending tokens to L2. #### Constructor[​](#constructor-15 "Direct link to Constructor") **Signature:** ``` constructor( portalAddress: EthAddress, tokenAddress: EthAddress, handlerAddress: EthAddress | undefined, protected extendedClient: ExtendedViemWalletClient, protected logger: Logger ) ``` **Parameters:** * `portalAddress`: `EthAddress` * `tokenAddress`: `EthAddress` * `handlerAddress`: `EthAddress | undefined` * `extendedClient`: `ExtendedViemWalletClient` * `logger`: `Logger` #### Properties[​](#properties-3 "Direct link to Properties") ##### portal[​](#portal "Direct link to portal") **Type:** `ViemContract` ##### tokenManager[​](#tokenmanager "Direct link to tokenManager") **Type:** `L1TokenManager` #### Methods[​](#methods-18 "Direct link to Methods") ##### getTokenManager[​](#gettokenmanager-1 "Direct link to getTokenManager") Returns the token manager for the underlying L1 token. **Signature:** ``` public getTokenManager() ``` **Returns:** `L1TokenManager` ##### bridgeTokensPublic[​](#bridgetokenspublic-1 "Direct link to bridgeTokensPublic") Bridges tokens from L1 to L2. Handles token approvals. Returns once the tx has been mined. **Signature:** ``` public async bridgeTokensPublic( to: AztecAddress, amount: bigint, mint = false ): Promise ``` **Parameters:** * `to`: `AztecAddress` * Address to send the tokens to on L2. * `amount`: `bigint` * Amount of tokens to send. * `mint` (optional): `any` * Whether to mint the tokens before sending (only during testing). **Returns:** `Promise` ##### bridgeTokensPrivate[​](#bridgetokensprivate "Direct link to bridgeTokensPrivate") Bridges tokens from L1 to L2 privately. Handles token approvals. Returns once the tx has been mined. **Signature:** ``` public async bridgeTokensPrivate( to: AztecAddress, amount: bigint, mint = false ): Promise ``` **Parameters:** * `to`: `AztecAddress` * Address to send the tokens to on L2. * `amount`: `bigint` * Amount of tokens to send. * `mint` (optional): `any` * Whether to mint the tokens before sending (only during testing). **Returns:** `Promise` #### L1TokenPortalManager[​](#l1tokenportalmanager "Direct link to L1TokenPortalManager") **Type:** Class Helper for interacting with a test TokenPortal on L1 for both withdrawing from and bridging to L2. **Extends:** `L1ToL2TokenPortalManager` #### Constructor[​](#constructor-16 "Direct link to Constructor") **Signature:** ``` constructor( portalAddress: EthAddress, tokenAddress: EthAddress, handlerAddress: EthAddress | undefined, outboxAddress: EthAddress, extendedClient: ExtendedViemWalletClient, logger: Logger ) ``` **Parameters:** * `portalAddress`: `EthAddress` * `tokenAddress`: `EthAddress` * `handlerAddress`: `EthAddress | undefined` * `outboxAddress`: `EthAddress` * `extendedClient`: `ExtendedViemWalletClient` * `logger`: `Logger` #### Methods[​](#methods-19 "Direct link to Methods") ##### withdrawFunds[​](#withdrawfunds "Direct link to withdrawFunds") Withdraws funds from the portal by consuming an L2 to L1 message. Returns once the tx is mined on L1. **Signature:** ``` public async withdrawFunds( amount: bigint, recipient: EthAddress, blockNumber: bigint, messageIndex: bigint, siblingPath: SiblingPath ) ``` **Parameters:** * `amount`: `bigint` * Amount to withdraw. * `recipient`: `EthAddress` * Who will receive the funds. * `blockNumber`: `bigint` * L2 block number of the message. * `messageIndex`: `bigint` * Index of the message. * `siblingPath`: `SiblingPath` * Sibling path of the message. **Returns:** `Promise` ##### getL2ToL1MessageLeaf[​](#getl2tol1messageleaf "Direct link to getL2ToL1MessageLeaf") Computes the L2 to L1 message leaf for the given parameters. **Signature:** ``` public async getL2ToL1MessageLeaf( amount: bigint, recipient: EthAddress, l2Bridge: AztecAddress, callerOnL1: EthAddress = EthAddress.ZERO ): Promise ``` **Parameters:** * `amount`: `bigint` * Amount to bridge. * `recipient`: `EthAddress` * Recipient on L1. * `l2Bridge`: `AztecAddress` * Address of the L2 bridge. * `callerOnL1` (optional): `EthAddress` * Caller address on L1. **Returns:** `Promise` ## Fee[​](#fee-4 "Direct link to Fee") *** ### `fee/fee_juice_payment_method_with_claim.ts`[​](#feefee_juice_payment_method_with_claimts "Direct link to feefee_juice_payment_method_with_claimts") #### FeeJuicePaymentMethodWithClaim[​](#feejuicepaymentmethodwithclaim "Direct link to FeeJuicePaymentMethodWithClaim") **Type:** Class Pay fee directly with Fee Juice claimed in the same tx. Claiming consumes an L1 to L2 message that "contains" the fee juice bridged from L1. **Implements:** `FeePaymentMethod` #### Constructor[​](#constructor-17 "Direct link to Constructor") **Signature:** ``` constructor( private sender: AztecAddress, private claim: Pick ) ``` **Parameters:** * `sender`: `AztecAddress` * `claim`: `Pick` #### Methods[​](#methods-20 "Direct link to Methods") ##### getExecutionPayload[​](#getexecutionpayload "Direct link to getExecutionPayload") Creates an execution payload to pay the fee in Fee Juice. **Signature:** ``` async getExecutionPayload(): Promise ``` **Returns:** `Promise` - An execution payload that just contains the `claim_and_end_setup` function call. ##### getAsset[​](#getasset "Direct link to getAsset") **Signature:** ``` getAsset() ``` **Returns:** `Promise` ##### getFeePayer[​](#getfeepayer "Direct link to getFeePayer") **Signature:** ``` getFeePayer(): Promise ``` **Returns:** `Promise` ##### getGasSettings[​](#getgassettings "Direct link to getGasSettings") **Signature:** ``` getGasSettings(): GasSettings | undefined ``` **Returns:** `GasSettings | undefined` *** ### `fee/fee_payment_method.ts`[​](#feefee_payment_methodts "Direct link to feefee_payment_methodts") #### FeePaymentMethod[​](#feepaymentmethod "Direct link to FeePaymentMethod") **Type:** Interface Holds information about how the fee for a transaction is to be paid. #### Methods[​](#methods-21 "Direct link to Methods") ##### getAsset[​](#getasset-1 "Direct link to getAsset") The asset used to pay the fee. **Signature:** ``` getAsset(): Promise ``` **Returns:** `Promise` ##### getExecutionPayload[​](#getexecutionpayload-1 "Direct link to getExecutionPayload") Returns the data to be added to the final execution request to pay the fee in the given asset **Signature:** ``` getExecutionPayload(): Promise ``` **Returns:** `Promise` - The function calls to pay the fee. ##### getFeePayer[​](#getfeepayer-1 "Direct link to getFeePayer") The expected fee payer for this tx. **Signature:** ``` getFeePayer(): Promise ``` **Returns:** `Promise` ##### getGasSettings[​](#getgassettings-1 "Direct link to getGasSettings") The gas settings (if any) used to compute the execution payload of the payment method **Signature:** ``` getGasSettings(): GasSettings | undefined ``` **Returns:** `GasSettings | undefined` *** ### `fee/private_fee_payment_method.ts`[​](#feeprivate_fee_payment_methodts "Direct link to feeprivate_fee_payment_methodts") #### PrivateFeePaymentMethod[​](#privatefeepaymentmethod "Direct link to PrivateFeePaymentMethod") **Type:** Class Holds information about how the fee for a transaction is to be paid. **Implements:** `FeePaymentMethod` #### Constructor[​](#constructor-18 "Direct link to Constructor") **Signature:** ``` constructor( private paymentContract: AztecAddress, private sender: AztecAddress, private wallet: Wallet, protected gasSettings: GasSettings, private setMaxFeeToOne = false ) ``` **Parameters:** * `paymentContract`: `AztecAddress` * Address which will hold the fee payment. * `sender`: `AztecAddress` * Address of the account that will pay the fee * `wallet`: `Wallet` * A wallet to perform the simulation to get the accepted asset * `gasSettings`: `GasSettings` * Gas settings used to compute the maximum fee the user is willing to pay * `setMaxFeeToOne` (optional): `any` * If true, the max fee will be set to 1. TODO(#7694): Remove this param once the lacking feature in TXE is implemented. #### Methods[​](#methods-22 "Direct link to Methods") ##### getAsset[​](#getasset-2 "Direct link to getAsset") The asset used to pay the fee. **Signature:** ``` async getAsset(): Promise ``` **Returns:** `Promise` - The asset used to pay the fee. ##### getFeePayer[​](#getfeepayer-2 "Direct link to getFeePayer") **Signature:** ``` getFeePayer(): Promise ``` **Returns:** `Promise` ##### getExecutionPayload[​](#getexecutionpayload-2 "Direct link to getExecutionPayload") Creates an execution payload to pay the fee using a private function through an FPC in the desired asset **Signature:** ``` async getExecutionPayload(): Promise ``` **Returns:** `Promise` - An execution payload that contains the required function calls and auth witnesses. ##### getGasSettings[​](#getgassettings-2 "Direct link to getGasSettings") **Signature:** ``` getGasSettings(): GasSettings | undefined ``` **Returns:** `GasSettings | undefined` *** ### `fee/public_fee_payment_method.ts`[​](#feepublic_fee_payment_methodts "Direct link to feepublic_fee_payment_methodts") #### PublicFeePaymentMethod[​](#publicfeepaymentmethod "Direct link to PublicFeePaymentMethod") **Type:** Class Holds information about how the fee for a transaction is to be paid. **Implements:** `FeePaymentMethod` #### Constructor[​](#constructor-19 "Direct link to Constructor") **Signature:** ``` constructor( protected paymentContract: AztecAddress, protected sender: AztecAddress, protected wallet: Wallet, protected gasSettings: GasSettings ) ``` **Parameters:** * `paymentContract`: `AztecAddress` * Address which will hold the fee payment. * `sender`: `AztecAddress` * An auth witness provider to authorize fee payments * `wallet`: `Wallet` * A wallet to perform the simulation to get the accepted asset * `gasSettings`: `GasSettings` * Gas settings used to compute the maximum fee the user is willing to pay #### Methods[​](#methods-23 "Direct link to Methods") ##### getAsset[​](#getasset-3 "Direct link to getAsset") The asset used to pay the fee. **Signature:** ``` async getAsset(): Promise ``` **Returns:** `Promise` - The asset used to pay the fee. ##### getFeePayer[​](#getfeepayer-3 "Direct link to getFeePayer") **Signature:** ``` getFeePayer(): Promise ``` **Returns:** `Promise` ##### getExecutionPayload[​](#getexecutionpayload-3 "Direct link to getExecutionPayload") Creates an execution payload to pay the fee using a public function through an FPC in the desired asset **Signature:** ``` async getExecutionPayload(): Promise ``` **Returns:** `Promise` - An execution payload that contains the required function calls. ##### getGasSettings[​](#getgassettings-3 "Direct link to getGasSettings") **Signature:** ``` getGasSettings(): GasSettings | undefined ``` **Returns:** `GasSettings | undefined` *** ### `fee/sponsored_fee_payment.ts`[​](#feesponsored_fee_paymentts "Direct link to feesponsored_fee_paymentts") #### SponsoredFeePaymentMethod[​](#sponsoredfeepaymentmethod "Direct link to SponsoredFeePaymentMethod") **Type:** Class A fee payment method that uses a contract that blindly sponsors transactions. This contract is expected to be prefunded in testing environments. **Implements:** `FeePaymentMethod` #### Constructor[​](#constructor-20 "Direct link to Constructor") **Signature:** ``` constructor(private paymentContract: AztecAddress) ``` **Parameters:** * `paymentContract`: `AztecAddress` #### Methods[​](#methods-24 "Direct link to Methods") ##### getAsset[​](#getasset-4 "Direct link to getAsset") **Signature:** ``` getAsset(): Promise ``` **Returns:** `Promise` ##### getFeePayer[​](#getfeepayer-4 "Direct link to getFeePayer") **Signature:** ``` getFeePayer() ``` **Returns:** `Promise` ##### getExecutionPayload[​](#getexecutionpayload-4 "Direct link to getExecutionPayload") **Signature:** ``` async getExecutionPayload(): Promise ``` **Returns:** `Promise` ##### getGasSettings[​](#getgassettings-4 "Direct link to getGasSettings") **Signature:** ``` getGasSettings(): GasSettings | undefined ``` **Returns:** `GasSettings | undefined` ## Utils[​](#utils "Direct link to Utils") *** ### `utils/abi_types.ts`[​](#utilsabi_typests "Direct link to utilsabi_typests") #### FieldLike[​](#fieldlike "Direct link to FieldLike") **Type:** Type Alias Any type that can be converted into a field for a contract call. **Signature:** ``` export type FieldLike = Fr | Buffer | bigint | number | { toField: () => Fr }; ``` #### EthAddressLike[​](#ethaddresslike "Direct link to EthAddressLike") **Type:** Type Alias Any type that can be converted into an EthAddress Aztec.nr struct. **Signature:** ``` export type EthAddressLike = { address: FieldLike } | EthAddress; ``` #### AztecAddressLike[​](#aztecaddresslike "Direct link to AztecAddressLike") **Type:** Type Alias Any type that can be converted into an AztecAddress Aztec.nr struct. **Signature:** ``` export type AztecAddressLike = { address: FieldLike } | AztecAddress; ``` #### FunctionSelectorLike[​](#functionselectorlike "Direct link to FunctionSelectorLike") **Type:** Type Alias Any type that can be converted into a FunctionSelector Aztec.nr struct. **Signature:** ``` export type FunctionSelectorLike = FieldLike | FunctionSelector; ``` #### EventSelectorLike[​](#eventselectorlike "Direct link to EventSelectorLike") **Type:** Type Alias Any type that can be converted into an EventSelector Aztec.nr struct. **Signature:** ``` export type EventSelectorLike = FieldLike | EventSelector; ``` #### U128Like[​](#u128like "Direct link to U128Like") **Type:** Type Alias Any type that can be converted into a U128. **Signature:** ``` export type U128Like = bigint | number; ``` #### WrappedFieldLike[​](#wrappedfieldlike "Direct link to WrappedFieldLike") **Type:** Type Alias Any type that can be converted into a struct with a single `inner` field. **Signature:** ``` export type WrappedFieldLike = { inner: FieldLike } | FieldLike; ``` *** ### `utils/authwit.ts`[​](#utilsauthwitts "Direct link to utilsauthwitts") #### IntentInnerHash[​](#intentinnerhash "Direct link to IntentInnerHash") **Type:** Type Alias Intent with an inner hash **Signature:** ``` export type IntentInnerHash = { consumer: AztecAddress; innerHash: Fr; }; ``` **Type Members:** ##### consumer[​](#consumer "Direct link to consumer") The consumer **Type:** `AztecAddress` ##### innerHash[​](#innerhash "Direct link to innerHash") The action to approve **Type:** `Fr` #### CallIntent[​](#callintent "Direct link to CallIntent") **Type:** Type Alias Intent with a call **Signature:** ``` export type CallIntent = { caller: AztecAddress; call: FunctionCall; }; ``` **Type Members:** ##### caller[​](#caller "Direct link to caller") The caller to approve **Type:** `AztecAddress` ##### call[​](#call "Direct link to call") The call to approve **Type:** `FunctionCall` #### ContractFunctionInteractionCallIntent[​](#contractfunctioninteractioncallintent "Direct link to ContractFunctionInteractionCallIntent") **Type:** Type Alias Intent with a ContractFunctionInteraction **Signature:** ``` export type ContractFunctionInteractionCallIntent = { caller: AztecAddress; action: ContractFunctionInteraction; }; ``` **Type Members:** ##### caller[​](#caller-1 "Direct link to caller") The caller to approve **Type:** `AztecAddress` ##### action[​](#action "Direct link to action") The action to approve **Type:** `ContractFunctionInteraction` #### computeAuthWitMessageHash[​](#computeauthwitmessagehash "Direct link to computeAuthWitMessageHash") **Type:** Constant Compute an authentication witness message hash from an intent and metadata If using the `IntentInnerHash`, the consumer is the address that can "consume" the authwit, for token approvals it is the token contract itself. The `innerHash` itself will be the message that a contract is allowed to execute. At the point of "approval checking", the validating contract (account for private and registry for public) will be computing the message hash (`H(consumer, chainid, version, inner_hash)`) where the all but the `inner_hash` is injected from the context (consumer = msg\_sender), and use it for the authentication check. Therefore, any allowed `innerHash` will therefore also have information around where it can be spent (version, chainId) and who can spend it (consumer). If using the `CallIntent`, the caller is the address that is making the call, for a token approval from Alice to Bob, this would be Bob. The action is then used along with the `caller` to compute the `innerHash` and the consumer. **Value Type:** `any` #### getMessageHashFromIntent[​](#getmessagehashfromintent "Direct link to getMessageHashFromIntent") **Type:** Function Compute an authentication witness message hash from an intent and metadata. This is just a wrapper around computeAuthwitMessageHash that allows receiving an already computed messageHash as input **Signature:** ``` export async getMessageHashFromIntent( messageHashOrIntent: Fr | IntentInnerHash | CallIntent | ContractFunctionInteractionCallIntent, chainInfo: ChainInfo ) ``` **Parameters:** * `messageHashOrIntent`: `Fr | IntentInnerHash | CallIntent | ContractFunctionInteractionCallIntent` * The precomputed messageHash or intent to approve (consumer and innerHash or caller and call/action) * `chainInfo`: `ChainInfo` **Returns:** `Promise` - The message hash for the intent #### computeInnerAuthWitHashFromAction[​](#computeinnerauthwithashfromaction "Direct link to computeInnerAuthWitHashFromAction") **Type:** Constant Computes the inner authwitness hash for either a function call or an action, for it to later be combined with the metadata required for the outer hash and eventually the full AuthWitness. **Value Type:** `any` #### lookupValidity[​](#lookupvalidity "Direct link to lookupValidity") **Type:** Function Lookup the validity of an authwit in private and public contexts. Uses the chain id and version of the wallet. **Signature:** ``` export async lookupValidity( wallet: Wallet, onBehalfOf: AztecAddress, intent: IntentInnerHash | CallIntent | ContractFunctionInteractionCallIntent, witness: AuthWitness ): Promise<{ isValidInPrivate: boolean; isValidInPublic: boolean; }> ``` **Parameters:** * `wallet`: `Wallet` * The wallet use to simulate and read the public data * `onBehalfOf`: `AztecAddress` * The address of the "approver" * `intent`: `IntentInnerHash | CallIntent | ContractFunctionInteractionCallIntent` * The consumer and inner hash or the caller and action to lookup * `witness`: `AuthWitness` * The computed authentication witness to check **Returns:** ``` Promise<{ /** boolean flag indicating if the authwit is valid in private context */ isValidInPrivate: boolean; /** boolean flag indicating if the authwit is valid in public context */ isValidInPublic: boolean; }> ``` A struct containing the validity of the authwit in private and public contexts. #### SetPublicAuthwitContractInteraction[​](#setpublicauthwitcontractinteraction "Direct link to SetPublicAuthwitContractInteraction") **Type:** Class Convenience class designed to wrap the very common interaction of setting a public authwit in the AuthRegistry contract **Extends:** `ContractFunctionInteraction` #### Constructor[​](#constructor-21 "Direct link to Constructor") **Signature:** ``` private constructor( wallet: Wallet, private from: AztecAddress, messageHash: Fr, authorized: boolean ) ``` **Parameters:** * `wallet`: `Wallet` * `from`: `AztecAddress` * `messageHash`: `Fr` * `authorized`: `boolean` #### Methods[​](#methods-25 "Direct link to Methods") ##### create[​](#create "Direct link to create") **Signature:** ``` static async create( wallet: Wallet, from: AztecAddress, messageHashOrIntent: Fr | IntentInnerHash | CallIntent | ContractFunctionInteractionCallIntent, authorized: boolean ) ``` **Parameters:** * `wallet`: `Wallet` * `from`: `AztecAddress` * `messageHashOrIntent`: `Fr | IntentInnerHash | CallIntent | ContractFunctionInteractionCallIntent` * `authorized`: `boolean` **Returns:** `Promise` ##### simulate[​](#simulate-5 "Direct link to simulate") Overrides the simulate method, adding the sender of the authwit (authorizer) as from and preventing misuse **Signature:** ``` public override simulate(options: Omit): Promise> ``` **Parameters:** * `options`: `Omit` * An optional object containing additional configuration for the transaction. **Returns:** `Promise>` - The result of the transaction as returned by the contract function. ##### simulate[​](#simulate-6 "Direct link to simulate") **Signature:** ``` public override simulate(options: Omit = {}): Promise> ``` **Parameters:** * `options` (optional): `Omit` **Returns:** `Promise>` ##### profile[​](#profile-2 "Direct link to profile") Overrides the profile method, adding the sender of the authwit (authorizer) as from and preventing misuse **Signature:** ``` public override profile(options: Omit = { profileMode: 'gates' }): Promise ``` **Parameters:** * `options` (optional): `Omit` * Same options as `simulate`, plus profiling method **Returns:** `Promise` - An object containing the function return value and profile result. ##### send[​](#send-2 "Direct link to send") Overrides the send method, adding the sender of the authwit (authorizer) as from and preventing misuse **Signature:** ``` public override send(options: Omit = {}): SentTx ``` **Parameters:** * `options` (optional): `Omit` * An optional object containing 'fee' options information **Returns:** `SentTx` - A SentTx instance for tracking the transaction status and information. *** ### `utils/cross_chain.ts`[​](#utilscross_chaints "Direct link to utilscross_chaints") #### waitForL1ToL2MessageReady[​](#waitforl1tol2messageready "Direct link to waitForL1ToL2MessageReady") **Type:** Function Waits for the L1 to L2 message to be ready to be consumed. **Signature:** ``` export async waitForL1ToL2MessageReady( node: Pick, l1ToL2MessageHash: Fr, opts: { timeoutSeconds: number; forPublicConsumption: boolean; } ) ``` **Parameters:** * `node`: `Pick` * Aztec node instance used to obtain the information about the message * `l1ToL2MessageHash`: `Fr` * Hash of the L1 to L2 message * `opts`: `{ /** Timeout for the operation in seconds */ timeoutSeconds: number; /** True if the message is meant to be consumed from a public function */ forPublicConsumption: boolean; }` * Options **Returns:** `Promise` #### isL1ToL2MessageReady[​](#isl1tol2messageready "Direct link to isL1ToL2MessageReady") **Type:** Function Returns whether the L1 to L2 message is ready to be consumed. **Signature:** ``` export async isL1ToL2MessageReady( node: Pick, l1ToL2MessageHash: Fr, opts: { forPublicConsumption: boolean; messageBlockNumber?: number; } ): Promise ``` **Parameters:** * `node`: `Pick` * Aztec node instance used to obtain the information about the message * `l1ToL2MessageHash`: `Fr` * Hash of the L1 to L2 message * `opts`: `{ /** True if the message is meant to be consumed from a public function */ forPublicConsumption: boolean; /** Cached synced block number for the message (will be fetched from PXE otherwise) */ messageBlockNumber?: number; }` * Options **Returns:** `Promise` - True if the message is ready to be consumed, false otherwise *** ### `utils/fee_juice.ts`[​](#utilsfee_juicets "Direct link to utilsfee_juicets") #### getFeeJuiceBalance[​](#getfeejuicebalance "Direct link to getFeeJuiceBalance") **Type:** Function Returns the owner's fee juice balance. Note: This is used only e2e\_local\_network\_example test. TODO: Consider nuking. **Signature:** ``` export async getFeeJuiceBalance( owner: AztecAddress, node: AztecNode ): Promise ``` **Parameters:** * `owner`: `AztecAddress` * `node`: `AztecNode` **Returns:** `Promise` *** ### `utils/field_compressed_string.ts`[​](#utilsfield_compressed_stringts "Direct link to utilsfield_compressed_stringts") #### readFieldCompressedString[​](#readfieldcompressedstring "Direct link to readFieldCompressedString") **Type:** Constant This turns **Value Type:** `any` *** ### `utils/node.ts`[​](#utilsnodets "Direct link to utilsnodets") #### waitForNode[​](#waitfornode "Direct link to waitForNode") **Type:** Constant **Value Type:** `any` #### createAztecNodeClient[​](#createaztecnodeclient "Direct link to createAztecNodeClient") **Type:** Constant This is re-exported from `@aztec/stdlib/interfaces/client`. See the source module for full documentation. **Value Type:** `Re-export` #### AztecNode[​](#aztecnode "Direct link to AztecNode") **Type:** Type Alias This is a type re-exported from `@aztec/stdlib/interfaces/client`. See the source module for full type definition and documentation. **Signature:** ``` export type { AztecNode } from '@aztec/stdlib/interfaces/client' ``` *** ### `utils/pub_key.ts`[​](#utilspub_keyts "Direct link to utilspub_keyts") #### generatePublicKey[​](#generatepublickey "Direct link to generatePublicKey") **Type:** Function Method for generating a public grumpkin key from a private key. **Signature:** ``` export generatePublicKey(privateKey: GrumpkinScalar): Promise ``` **Parameters:** * `privateKey`: `GrumpkinScalar` * The private key. **Returns:** `Promise` - The generated public key. ## Wallet[​](#wallet-1 "Direct link to Wallet") *** ### `wallet/account_entrypoint_meta_payment_method.ts`[​](#walletaccount_entrypoint_meta_payment_methodts "Direct link to walletaccount_entrypoint_meta_payment_methodts") #### AccountEntrypointMetaPaymentMethod[​](#accountentrypointmetapaymentmethod "Direct link to AccountEntrypointMetaPaymentMethod") **Type:** Class Fee payment method that allows an account contract to pay for its own deployment It works by rerouting the provided fee payment method through the account's entrypoint, which sets itself as fee payer. If no payment method is provided, it is assumed the account will pay with its own fee juice balance. Usually, in order to pay fees it is necessary to obtain an ExecutionPayload that encodes the necessary information that is sent to the user's account entrypoint, that has plumbing to handle it. If there's no account contract yet (it's being deployed) a MultiCallContract is used, which doesn't have a concept of fees or how to handle this payload. HOWEVER, the account contract's entrypoint does, so this method reshapes that fee payload into a call to the account contract entrypoint being deployed with the original fee payload. This class can be seen in action in DeployAccountMethod.ts#getSelfPaymentMethod **Implements:** `FeePaymentMethod` #### Constructor[​](#constructor-22 "Direct link to Constructor") **Signature:** ``` constructor( private wallet: Wallet, private artifact: ContractArtifact, private feePaymentNameOrArtifact: string | FunctionArtifact, private accountAddress: AztecAddress, private paymentMethod?: FeePaymentMethod ) ``` **Parameters:** * `wallet`: `Wallet` * `artifact`: `ContractArtifact` * `feePaymentNameOrArtifact`: `string | FunctionArtifact` * `accountAddress`: `AztecAddress` * `paymentMethod` (optional): `FeePaymentMethod` #### Methods[​](#methods-26 "Direct link to Methods") ##### getAsset[​](#getasset-5 "Direct link to getAsset") **Signature:** ``` getAsset(): Promise ``` **Returns:** `Promise` ##### getExecutionPayload[​](#getexecutionpayload-5 "Direct link to getExecutionPayload") **Signature:** ``` async getExecutionPayload(): Promise ``` **Returns:** `Promise` ##### getFeePayer[​](#getfeepayer-5 "Direct link to getFeePayer") **Signature:** ``` getFeePayer(): Promise ``` **Returns:** `Promise` ##### getGasSettings[​](#getgassettings-5 "Direct link to getGasSettings") **Signature:** ``` getGasSettings(): GasSettings | undefined ``` **Returns:** `GasSettings | undefined` *** ### `wallet/account_manager.ts`[​](#walletaccount_managerts "Direct link to walletaccount_managerts") #### AccountManager[​](#accountmanager "Direct link to AccountManager") **Type:** Class Manages a user account. Provides methods for calculating the account's address and other related data, plus a helper to return a preconfigured deploy method. #### Constructor[​](#constructor-23 "Direct link to Constructor") **Signature:** ``` private constructor( private wallet: Wallet, private secretKey: Fr, private accountContract: AccountContract, private instance: ContractInstanceWithAddress, public readonly salt: Salt ) ``` **Parameters:** * `wallet`: `Wallet` * `secretKey`: `Fr` * `accountContract`: `AccountContract` * `instance`: `ContractInstanceWithAddress` * `salt`: `Salt` * Contract instantiation salt for the account contract #### Methods[​](#methods-27 "Direct link to Methods") ##### create[​](#create-1 "Direct link to create") **Signature:** ``` static async create( wallet: Wallet, secretKey: Fr, accountContract: AccountContract, salt?: Salt ) ``` **Parameters:** * `wallet`: `Wallet` * `secretKey`: `Fr` * `accountContract`: `AccountContract` * `salt` (optional): `Salt` **Returns:** `Promise` ##### getPublicKeys[​](#getpublickeys "Direct link to getPublicKeys") **Signature:** ``` protected getPublicKeys() ``` **Returns:** `any` ##### getPublicKeysHash[​](#getpublickeyshash "Direct link to getPublicKeysHash") **Signature:** ``` protected getPublicKeysHash() ``` **Returns:** `any` ##### getAccountInterface[​](#getaccountinterface "Direct link to getAccountInterface") Returns the entrypoint for this account as defined by its account contract. **Signature:** ``` public async getAccountInterface(): Promise ``` **Returns:** `Promise` - An entrypoint. ##### getCompleteAddress[​](#getcompleteaddress-3 "Direct link to getCompleteAddress") Gets the calculated complete address associated with this account. Does not require the account to have been published for public execution. **Signature:** ``` public getCompleteAddress(): Promise ``` **Returns:** `Promise` - The address, partial address, and encryption public key. ##### getSecretKey[​](#getsecretkey-1 "Direct link to getSecretKey") Returns the secret key used to derive the rest of the privacy keys for this contract **Signature:** ``` public getSecretKey() ``` **Returns:** `Fr` ##### getInstance[​](#getinstance-2 "Direct link to getInstance") Returns the contract instance definition associated with this account. Does not require the account to have been published for public execution. **Signature:** ``` public getInstance(): ContractInstanceWithAddress ``` **Returns:** `ContractInstanceWithAddress` - ContractInstance instance. ##### getAccount[​](#getaccount "Direct link to getAccount") Returns a Wallet instance associated with this account. Use it to create Contract instances to be interacted with from this account. **Signature:** ``` public async getAccount(): Promise ``` **Returns:** `Promise` - A Wallet instance. ##### getAccountContract[​](#getaccountcontract "Direct link to getAccountContract") Returns the account contract that backs this account. **Signature:** ``` getAccountContract(): AccountContract ``` **Returns:** `AccountContract` - The account contract ##### getDeployMethod[​](#getdeploymethod "Direct link to getDeployMethod") Returns a preconfigured deploy method that contains all the necessary function calls to deploy the account contract. **Signature:** ``` public async getDeployMethod(): Promise ``` **Returns:** `Promise` ##### hasInitializer[​](#hasinitializer "Direct link to hasInitializer") Returns whether this account contract has an initializer function. **Signature:** ``` public async hasInitializer() ``` **Returns:** `Promise` #### Getters[​](#getters-1 "Direct link to Getters") ##### address (getter)[​](#address-getter-1 "Direct link to address (getter)") **Signature:** ``` get address() { ``` **Returns:** `any` *** ### `wallet/deploy_account_method.ts`[​](#walletdeploy_account_methodts "Direct link to walletdeploy_account_methodts") #### RequestDeployAccountOptions[​](#requestdeployaccountoptions "Direct link to RequestDeployAccountOptions") **Type:** Type Alias The configuration options for the request method. Omits the contractAddressSalt, since for account contracts that is fixed in the constructor **Signature:** ``` export type RequestDeployAccountOptions = Omit; ``` #### DeployAccountOptions[​](#deployaccountoptions "Direct link to DeployAccountOptions") **Type:** Type Alias The configuration options for the send/prove methods. Omits: - The contractAddressSalt, since for account contracts that is fixed in the constructor. - UniversalDeployment flag, since account contracts are always deployed with it set to true **Signature:** ``` export type DeployAccountOptions = Omit; ``` #### SimulateDeployAccountOptions[​](#simulatedeployaccountoptions "Direct link to SimulateDeployAccountOptions") **Type:** Type Alias The configuration options for the simulate method. Omits the contractAddressSalt, since for account contracts that is fixed in the constructor **Signature:** ``` export type SimulateDeployAccountOptions = Omit; ``` #### DeployAccountMethod[​](#deployaccountmethod "Direct link to DeployAccountMethod") **Type:** Class Modified version of the DeployMethod used to deploy account contracts. Supports deploying contracts that can pay for their own fee, plus some preconfigured options to avoid errors. **Extends:** `DeployMethod` #### Constructor[​](#constructor-24 "Direct link to Constructor") **Signature:** ``` constructor( publicKeys: PublicKeys, wallet: Wallet, artifact: ContractArtifact, postDeployCtor: (instance: ContractInstanceWithAddress, wallet: Wallet) => TContract, private salt: Fr, args: any[] = [], constructorNameOrArtifact?: string | FunctionArtifact ) ``` **Parameters:** * `publicKeys`: `PublicKeys` * `wallet`: `Wallet` * `artifact`: `ContractArtifact` * `postDeployCtor`: `(instance: ContractInstanceWithAddress, wallet: Wallet) => TContract` * `salt`: `Fr` * `args` (optional): `any[]` * `constructorNameOrArtifact` (optional): `string | FunctionArtifact` #### Methods[​](#methods-28 "Direct link to Methods") ##### request[​](#request-4 "Direct link to request") Returns the execution payload that allows this operation to happen on chain. **Signature:** ``` public override async request(opts?: RequestDeployAccountOptions): Promise ``` **Parameters:** * `opts` (optional): `RequestDeployAccountOptions` * Configuration options. **Returns:** `Promise` - The execution payload for this operation ##### convertDeployOptionsToRequestOptions[​](#convertdeployoptionstorequestoptions-1 "Direct link to convertDeployOptionsToRequestOptions") **Signature:** ``` override convertDeployOptionsToRequestOptions(options: DeployOptions): RequestDeployOptions ``` **Parameters:** * `options`: `DeployOptions` **Returns:** `RequestDeployOptions` *** ### `wallet/wallet.ts`[​](#walletwalletts "Direct link to walletwalletts") #### Aliased[​](#aliased "Direct link to Aliased") **Type:** Type Alias A wrapper type that allows any item to be associated with an alias. **Signature:** ``` export type Aliased = { alias: string; item: T; }; ``` **Type Members:** ##### alias[​](#alias "Direct link to alias") The alias **Type:** `string` ##### item[​](#item "Direct link to item") The item being aliased. **Type:** `T` #### SimulateOptions[​](#simulateoptions "Direct link to SimulateOptions") **Type:** Type Alias Options for simulating interactions with the wallet. Overrides the fee settings of an interaction with a simplified version that only hints at the wallet wether the interaction contains a fee payment method or not **Signature:** ``` export type SimulateOptions = Omit & { fee?: GasSettingsOption & FeeEstimationOptions; }; ``` **Type Members:** ##### fee[​](#fee-5 "Direct link to fee") The fee options **Type:** `GasSettingsOption & FeeEstimationOptions` #### ProfileOptions[​](#profileoptions "Direct link to ProfileOptions") **Type:** Type Alias Options for profiling interactions with the wallet. Overrides the fee settings of an interaction with a simplified version that only hints at the wallet wether the interaction contains a fee payment method or not **Signature:** ``` export type ProfileOptions = Omit & { fee?: GasSettingsOption; }; ``` **Type Members:** ##### fee[​](#fee-6 "Direct link to fee") The fee options **Type:** `GasSettingsOption` #### SendOptions[​](#sendoptions "Direct link to SendOptions") **Type:** Type Alias Options for sending/proving interactions with the wallet. Overrides the fee settings of an interaction with a simplified version that only hints at the wallet wether the interaction contains a fee payment method or not **Signature:** ``` export type SendOptions = Omit & { fee?: GasSettingsOption; }; ``` **Type Members:** ##### fee[​](#fee-7 "Direct link to fee") The fee options **Type:** `GasSettingsOption` #### BatchableMethods[​](#batchablemethods "Direct link to BatchableMethods") **Type:** Type Alias Helper type that represents all methods that can be batched. **Signature:** ``` export type BatchableMethods = Pick< Wallet, 'registerContract' | 'sendTx' | 'registerSender' | 'executeUtility' | 'simulateTx' >; ``` #### BatchedMethod[​](#batchedmethod "Direct link to BatchedMethod") **Type:** Type Alias From the batchable methods, we create a type that represents a method call with its name and arguments. This is what the wallet will accept as arguments to the `batch` method. **Signature:** ``` export type BatchedMethod = { name: T; args: Parameters; }; ``` **Type Members:** ##### name[​](#name "Direct link to name") The method name **Type:** `T` ##### args[​](#args "Direct link to args") The method arguments **Type:** `Parameters` #### BatchedMethodResult[​](#batchedmethodresult "Direct link to BatchedMethodResult") **Type:** Type Alias Helper type to extract the return type of a batched method **Signature:** ``` export type BatchedMethodResult = T extends BatchedMethod ? Awaited> : never; ``` #### BatchedMethodResultWrapper[​](#batchedmethodresultwrapper "Direct link to BatchedMethodResultWrapper") **Type:** Type Alias Wrapper type for batch results that includes the method name for discriminated union deserialization. Each result is wrapped as { name: 'methodName', result: ActualResult } to allow proper deserialization when AztecAddress and TxHash would otherwise be ambiguous (both are hex strings). **Signature:** ``` export type BatchedMethodResultWrapper> = { name: T['name']; result: BatchedMethodResult; }; ``` **Type Members:** ##### name[​](#name-1 "Direct link to name") The method name **Type:** `T['name']` ##### result[​](#result "Direct link to result") The method result **Type:** `BatchedMethodResult` #### BatchResults[​](#batchresults "Direct link to BatchResults") **Type:** Type Alias Maps a tuple of BatchedMethod to a tuple of their wrapped return types **Signature:** ``` export type BatchResults[]> = { [K in keyof T]: BatchedMethodResultWrapper; }; ``` **Type Members:** ##### \[K in keyof T][​](#k-in-keyof-t "Direct link to \[K in keyof T]") **Signature:** `[K in keyof T]: BatchedMethodResultWrapper` **Key Type:** `keyof T` **Value Type:** `BatchedMethodResultWrapper` #### PrivateEventFilter[​](#privateeventfilter "Direct link to PrivateEventFilter") **Type:** Type Alias Filter options when querying private events. **Signature:** ``` export type PrivateEventFilter = { contractAddress: AztecAddress; scopes: AztecAddress[]; txHash?: TxHash; fromBlock?: BlockNumber; toBlock?: BlockNumber; }; ``` **Type Members:** ##### contractAddress[​](#contractaddress "Direct link to contractAddress") The address of the contract that emitted the events. **Type:** `AztecAddress` ##### scopes[​](#scopes "Direct link to scopes") Addresses of accounts that are in scope for this filter. **Type:** `AztecAddress[]` ##### txHash[​](#txhash-1 "Direct link to txHash") Transaction in which the events were emitted. **Type:** `TxHash` ##### fromBlock[​](#fromblock "Direct link to fromBlock") The block number from which to start fetching events (inclusive). Optional. If provided, it must be greater or equal than 1. Defaults to the initial L2 block number (INITIAL\_L2\_BLOCK\_NUM). **Type:** `BlockNumber` ##### toBlock[​](#toblock "Direct link to toBlock") The block number until which to fetch logs (not inclusive). Optional. If provided, it must be greater than fromBlock. Defaults to the latest known block to PXE + 1. **Type:** `BlockNumber` #### PrivateEvent[​](#privateevent "Direct link to PrivateEvent") **Type:** Type Alias An ABI decoded private event with associated metadata. **Signature:** ``` export type PrivateEvent = { event: T; metadata: InTx; }; ``` **Type Members:** ##### event[​](#event "Direct link to event") The ABI decoded event **Type:** `T` ##### metadata[​](#metadata "Direct link to metadata") Metadata describing event context information such as tx and block **Type:** `InTx` #### Wallet[​](#wallet-2 "Direct link to Wallet") **Type:** Type Alias The wallet interface. **Signature:** ``` export type Wallet = { getContractClassMetadata(id: Fr, includeArtifact?: boolean): Promise; getContractMetadata(address: AztecAddress): Promise; getPrivateEvents( eventMetadata: EventMetadataDefinition, eventFilter: PrivateEventFilter, ): Promise[]>; getChainInfo(): Promise; getTxReceipt(txHash: TxHash): Promise; registerSender(address: AztecAddress, alias?: string): Promise; getAddressBook(): Promise[]>; getAccounts(): Promise[]>; registerContract( instance: ContractInstanceWithAddress, artifact?: ContractArtifact, secretKey?: Fr, ): Promise; simulateTx(exec: ExecutionPayload, opts: SimulateOptions): Promise; executeUtility(call: FunctionCall, authwits?: AuthWitness[]): Promise; profileTx(exec: ExecutionPayload, opts: ProfileOptions): Promise; sendTx(exec: ExecutionPayload, opts: SendOptions): Promise; createAuthWit(from: AztecAddress, messageHashOrIntent: Fr | IntentInnerHash | CallIntent): Promise; batch[]>(methods: T): Promise>; }; ``` **Type Members:** ##### getContractClassMetadata[​](#getcontractclassmetadata "Direct link to getContractClassMetadata") **Signature:** ``` getContractClassMetadata( id: Fr, includeArtifact?: boolean ): Promise ``` **Parameters:** * `id`: `Fr` * `includeArtifact` (optional): `boolean` **Returns:** `Promise` ##### getContractMetadata[​](#getcontractmetadata "Direct link to getContractMetadata") **Signature:** ``` getContractMetadata(address: AztecAddress): Promise ``` **Parameters:** * `address`: `AztecAddress` **Returns:** `Promise` ##### getPrivateEvents[​](#getprivateevents "Direct link to getPrivateEvents") **Signature:** ``` getPrivateEvents( eventMetadata: EventMetadataDefinition, eventFilter: PrivateEventFilter ): Promise[]> ``` **Parameters:** * `eventMetadata`: `EventMetadataDefinition` * `eventFilter`: `PrivateEventFilter` **Returns:** `Promise[]>` ##### getChainInfo[​](#getchaininfo "Direct link to getChainInfo") **Signature:** ``` getChainInfo(): Promise ``` **Returns:** `Promise` ##### getTxReceipt[​](#gettxreceipt "Direct link to getTxReceipt") **Signature:** ``` getTxReceipt(txHash: TxHash): Promise ``` **Parameters:** * `txHash`: `TxHash` **Returns:** `Promise` ##### registerSender[​](#registersender "Direct link to registerSender") **Signature:** ``` registerSender( address: AztecAddress, alias?: string ): Promise ``` **Parameters:** * `address`: `AztecAddress` * `alias` (optional): `string` **Returns:** `Promise` ##### getAddressBook[​](#getaddressbook "Direct link to getAddressBook") **Signature:** ``` getAddressBook(): Promise[]> ``` **Returns:** `Promise[]>` ##### getAccounts[​](#getaccounts "Direct link to getAccounts") **Signature:** ``` getAccounts(): Promise[]> ``` **Returns:** `Promise[]>` ##### registerContract[​](#registercontract "Direct link to registerContract") **Signature:** ``` registerContract( instance: ContractInstanceWithAddress, artifact?: ContractArtifact, secretKey?: Fr ): Promise ``` **Parameters:** * `instance`: `ContractInstanceWithAddress` * `artifact` (optional): `ContractArtifact` * `secretKey` (optional): `Fr` **Returns:** `Promise` ##### simulateTx[​](#simulatetx "Direct link to simulateTx") **Signature:** ``` simulateTx( exec: ExecutionPayload, opts: SimulateOptions ): Promise ``` **Parameters:** * `exec`: `ExecutionPayload` * `opts`: `SimulateOptions` **Returns:** `Promise` ##### executeUtility[​](#executeutility "Direct link to executeUtility") **Signature:** ``` executeUtility( call: FunctionCall, authwits?: AuthWitness[] ): Promise ``` **Parameters:** * `call`: `FunctionCall` * `authwits` (optional): `AuthWitness[]` **Returns:** `Promise` ##### profileTx[​](#profiletx "Direct link to profileTx") **Signature:** ``` profileTx( exec: ExecutionPayload, opts: ProfileOptions ): Promise ``` **Parameters:** * `exec`: `ExecutionPayload` * `opts`: `ProfileOptions` **Returns:** `Promise` ##### sendTx[​](#sendtx "Direct link to sendTx") **Signature:** ``` sendTx( exec: ExecutionPayload, opts: SendOptions ): Promise ``` **Parameters:** * `exec`: `ExecutionPayload` * `opts`: `SendOptions` **Returns:** `Promise` ##### createAuthWit[​](#createauthwit-2 "Direct link to createAuthWit") **Signature:** ``` createAuthWit( from: AztecAddress, messageHashOrIntent: Fr | IntentInnerHash | CallIntent ): Promise ``` **Parameters:** * `from`: `AztecAddress` * `messageHashOrIntent`: `Fr | IntentInnerHash | CallIntent` **Returns:** `Promise` ##### batch[​](#batch "Direct link to batch") **Signature:** ``` batch[]>(methods: T): Promise> ``` **Parameters:** * `methods`: `T` **Returns:** `Promise>` #### FunctionCallSchema[​](#functioncallschema "Direct link to FunctionCallSchema") **Type:** Constant **Value Type:** `any` #### ExecutionPayloadSchema[​](#executionpayloadschema "Direct link to ExecutionPayloadSchema") **Type:** Constant **Value Type:** `any` #### GasSettingsOptionSchema[​](#gassettingsoptionschema "Direct link to GasSettingsOptionSchema") **Type:** Constant **Value Type:** `any` #### WalletSimulationFeeOptionSchema[​](#walletsimulationfeeoptionschema "Direct link to WalletSimulationFeeOptionSchema") **Type:** Constant **Value Type:** `any` #### SendOptionsSchema[​](#sendoptionsschema "Direct link to SendOptionsSchema") **Type:** Constant **Value Type:** `any` #### SimulateOptionsSchema[​](#simulateoptionsschema "Direct link to SimulateOptionsSchema") **Type:** Constant **Value Type:** `any` #### ProfileOptionsSchema[​](#profileoptionsschema "Direct link to ProfileOptionsSchema") **Type:** Constant **Value Type:** `any` #### MessageHashOrIntentSchema[​](#messagehashorintentschema "Direct link to MessageHashOrIntentSchema") **Type:** Constant **Value Type:** `any` #### BatchedMethodSchema[​](#batchedmethodschema "Direct link to BatchedMethodSchema") **Type:** Constant **Value Type:** `any` #### ContractMetadataSchema[​](#contractmetadataschema "Direct link to ContractMetadataSchema") **Type:** Constant **Value Type:** `any` #### ContractClassMetadataSchema[​](#contractclassmetadataschema "Direct link to ContractClassMetadataSchema") **Type:** Constant **Value Type:** `any` #### EventMetadataDefinitionSchema[​](#eventmetadatadefinitionschema "Direct link to EventMetadataDefinitionSchema") **Type:** Constant **Value Type:** `any` #### PrivateEventSchema[​](#privateeventschema "Direct link to PrivateEventSchema") **Type:** Constant **Value Type:** `ZodFor>` #### PrivateEventFilterSchema[​](#privateeventfilterschema "Direct link to PrivateEventFilterSchema") **Type:** Constant **Value Type:** `any` #### WalletSchema[​](#walletschema "Direct link to WalletSchema") **Type:** Constant **Value Type:** `ApiSchemaFor` --- # Connect to Local Network This guide shows you how to connect your application to the Aztec local network and interact with the network. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Running Aztec local network (see [Quickstart](/developers/getting_started_on_local_network.md)) on port 8080 * Node.js installed * TypeScript project set up ## Install dependencies[​](#install-dependencies "Direct link to Install dependencies") ``` yarn add @aztec/aztec.js@4.3.1 @aztec/wallets@4.3.1 ``` ## Connect to the network[​](#connect-to-the-network "Direct link to Connect to the network") Create a node client and EmbeddedWallet to interact with the local network: connect\_to\_network ``` import { createAztecNodeClient, waitForNode } from "@aztec/aztec.js/node"; import { EmbeddedWallet } from "@aztec/wallets/embedded"; import { getInitialTestAccountsData } from "@aztec/accounts/testing"; const nodeUrl = process.env.AZTEC_NODE_URL ?? "http://localhost:8080"; const node = createAztecNodeClient(nodeUrl); // Wait for the network to be ready await waitForNode(node); // Create an EmbeddedWallet connected to the node const wallet = await EmbeddedWallet.create(node, { ephemeral: true }); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L1-L14](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_connection/index.ts#L1-L14) About EmbeddedWallet `EmbeddedWallet` is a simplified wallet for local development that implements the same `Wallet` interface used in production. It handles key management, transaction signing, and proof generation in-process without external dependencies. **Why use it for testing?** It starts instantly, requires no setup, and provides deterministic behavior—ideal for automated tests and rapid iteration. **Production wallets** (like browser extensions or mobile apps) implement the same interface but store keys securely, may require user confirmation for transactions, and typically run in a separate process. Code written against `EmbeddedWallet` works with any `Wallet` implementation, so your application logic transfers directly to production. ### Verify the connection[​](#verify-the-connection "Direct link to Verify the connection") Get node information to confirm your connection: verify\_connection ``` const nodeInfo = await node.getNodeInfo(); console.log("Connected to local network version:", nodeInfo.nodeVersion); console.log("Chain ID:", nodeInfo.l1ChainId); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L16-L20](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_connection/index.ts#L16-L20) ### Load pre-funded accounts[​](#load-pre-funded-accounts "Direct link to Load pre-funded accounts") The local network has accounts pre-funded with fee juice to pay for gas. Register them in your wallet: load\_accounts ``` const testAccounts = await getInitialTestAccountsData(); const [aliceAddress, bobAddress] = await Promise.all( testAccounts.slice(0, 2).map(async (account) => { return ( await wallet.createSchnorrAccount( account.secret, account.salt, account.signingKey, ) ).address; }), ); console.log(`Alice's address: ${aliceAddress.toString()}`); console.log(`Bob's address: ${bobAddress.toString()}`); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L22-L38](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_connection/index.ts#L22-L38) These accounts are pre-funded with fee juice (the native gas token) at genesis, so you can immediately send transactions without needing to bridge funds from L1. ### Check fee juice balance[​](#check-fee-juice-balance "Direct link to Check fee juice balance") Verify that an account has fee juice for transactions: check\_fee\_juice ``` import { getFeeJuiceBalance } from "@aztec/aztec.js/utils"; const aliceBalance = await getFeeJuiceBalance(aliceAddress, node); console.log(`Alice's fee juice balance: ${aliceBalance}`); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L40-L45](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_connection/index.ts#L40-L45) ## Next steps[​](#next-steps "Direct link to Next steps") * [Create an account](/developers/docs/aztec-js/how_to_create_account.md) - Deploy new accounts on the network * [Deploy a contract](/developers/docs/aztec-js/how_to_deploy_contract.md) - Deploy your smart contracts * [Send transactions](/developers/docs/aztec-js/how_to_send_transaction.md) - Execute contract functions --- # Creating Accounts This guide shows you how to create and deploy a new account on Aztec. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * [Connected to a network](/developers/docs/aztec-js/how_to_connect_to_local_network.md) with a `EmbeddedWallet` instance * Understanding of [account concepts](/developers/docs/foundational-topics/accounts.md) ## Install dependencies[​](#install-dependencies "Direct link to Install dependencies") ``` yarn add @aztec/aztec.js@4.3.1 @aztec/wallets@4.3.1 @aztec/noir-contracts.js@4.3.1 ``` ## Create a new account[​](#create-a-new-account "Direct link to Create a new account") Using the [`wallet` from the connection guide](/developers/docs/aztec-js/how_to_connect_to_local_network.md), call `createSchnorrAccount` to create a new account with a random secret and salt: create\_account ``` import { Fr } from "@aztec/aztec.js/fields"; const secret = Fr.random(); const salt = Fr.random(); const newAccount = await wallet.createSchnorrAccount(secret, salt); console.log("New account address:", newAccount.address.toString()); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L47-L54](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_connection/index.ts#L47-L54) The secret is used to derive the account's encryption keys, and the salt ensures address uniqueness. The signing key is automatically derived from the secret. Store your secret and salt Save the `secret` and `salt` values securely. You need both to recover access to your account. If you lose them, you will permanently lose access to the account and any assets it holds. ## Deploy the account[​](#deploy-the-account "Direct link to Deploy the account") New accounts must be deployed before they can send transactions. Deployment requires paying fees. ### Using the Sponsored FPC[​](#using-the-sponsored-fpc "Direct link to Using the Sponsored FPC") If your account doesn't have Fee Juice, use the [Sponsored FPC](/developers/docs/aztec-js/how_to_pay_fees.md#sponsored-fpc): deploy\_account\_sponsored\_fpc ``` // Additional imports needed for account deployment examples import { NO_FROM } from "@aztec/aztec.js/account"; import { SponsoredFeePaymentMethod } from "@aztec/aztec.js/fee/testing"; import { SponsoredFPCContract } from "@aztec/noir-contracts.js/SponsoredFPC"; import { getContractInstanceFromInstantiationParams } from "@aztec/stdlib/contract"; // Set up the Sponsored FPC payment method (see fees guide for details) const sponsoredFPCInstance = await getContractInstanceFromInstantiationParams( SponsoredFPCContract.artifact, { salt: new Fr(0) }, ); await wallet.registerContract( sponsoredFPCInstance, SponsoredFPCContract.artifact, ); const sponsoredPaymentMethod = new SponsoredFeePaymentMethod( sponsoredFPCInstance.address, ); // newAccount is the account created in the previous section const deployMethod = await newAccount.getDeployMethod(); await deployMethod.send({ from: NO_FROM, fee: { paymentMethod: sponsoredPaymentMethod }, }); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L56-L82](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_connection/index.ts#L56-L82) info See the [guide on fees](/developers/docs/aztec-js/how_to_pay_fees.md#sponsored-fpc) for more details on the Sponsored FPC and what this snippet means. ### Using Fee Juice[​](#using-fee-juice "Direct link to Using Fee Juice") If your account has Fee Juice from a [bridge from L1](/developers/docs/aztec-js/how_to_pay_fees.md#bridge-fee-juice-from-l1), you can claim it and deploy in one step using `FeeJuicePaymentMethodWithClaim`. Create a new Schnorr account for this path: create\_fee\_juice\_account ``` // `feeJuiceAccount` is just another Schnorr account, the same kind as // `newAccount` above. It gets its own name here so both deploy paths // can coexist in one example; in your own code, pick whichever name fits. const feeJuiceSecret = Fr.random(); const feeJuiceSalt = Fr.random(); const feeJuiceAccount = await wallet.createSchnorrAccount( feeJuiceSecret, feeJuiceSalt, ); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L84-L94](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_connection/index.ts#L84-L94) Claim the bridged Fee Juice and deploy in one step: bridge\_fee\_juice\_claim ``` import { FeeJuicePaymentMethodWithClaim } from "@aztec/aztec.js/fee"; // claim is from the bridgeTokensPublic step above // Create a payment method that claims the bridged Fee Juice and uses it to pay const bridgePaymentMethod = new FeeJuicePaymentMethodWithClaim(feeJuiceAccount.address, claim); // Use it to pay for any transaction; here we deploy the account in one step const deployMethodBridged = await feeJuiceAccount.getDeployMethod(); await deployMethodBridged.send({ from: NO_FROM, fee: { paymentMethod: bridgePaymentMethod }, }); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L156-L169](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_connection/index.ts#L156-L169) If the account already has Fee Juice on L2 (for example, from a faucet or a previously claimed bridge), no special payment method is needed — just call `send({ from: NO_FROM })` and Fee Juice is used automatically. The `from: NO_FROM` signals that this transaction should be executed without account contract mediation. The wallet will directly execute it via a default entrypoint with no authorization. ## Verify deployment[​](#verify-deployment "Direct link to Verify deployment") Confirm the account was deployed successfully. Substitute the account variable for whichever path you used above (`newAccount` for the Sponsored FPC path, `feeJuiceAccount` for the Fee Juice path): verify\_account\_deployment ``` // `newAccount` refers to whichever account you just deployed, // either the Sponsored FPC account or `feeJuiceAccount` from the Fee Juice path. const metadata = await wallet.getContractMetadata(newAccount.address); console.log("Account deployed:", metadata.initializationStatus); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L171-L176](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_connection/index.ts#L171-L176) ## Next steps[​](#next-steps "Direct link to Next steps") * [Deploy contracts](/developers/docs/aztec-js/how_to_deploy_contract.md) with your new account * [Send transactions](/developers/docs/aztec-js/how_to_send_transaction.md) from an account * Learn about [account abstraction](/developers/docs/foundational-topics/accounts.md) * Implement [authentication witnesses](/developers/docs/aztec-js/how_to_use_authwit.md) --- # Deploying Contracts This guide shows you how to deploy compiled contracts to Aztec using the generated TypeScript interfaces. ## Overview[​](#overview "Direct link to Overview") Deploying a contract to Aztec involves publishing the contract class (the bytecode) and creating a contract instance at a specific address. The generated TypeScript classes handle this process through an API: you call `deploy()` with constructor arguments and `send()` with transaction options to deploy and get the contract instance. The contract address is deterministically computed from the contract class, constructor arguments, salt, and deployer address. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Compiled contract artifacts (see [How to Compile](/developers/docs/aztec-nr/compiling_contracts.md)) * [Connected to a network](how_to_connect_to_local_network) with an `EmbeddedWallet` instance and funded accounts * TypeScript project set up ## Generate TypeScript bindings[​](#generate-typescript-bindings "Direct link to Generate TypeScript bindings") ### Compile and generate code[​](#compile-and-generate-code "Direct link to Compile and generate code") ``` # Compile the contract aztec compile # Generate TypeScript interface aztec codegen ./target/my_contract-MyContract.json -o src/artifacts ``` info The codegen command creates a TypeScript class with typed methods for deployment and interaction. This provides type safety and autocompletion in your IDE. ## Deploy a contract[​](#deploy-a-contract "Direct link to Deploy a contract") ### Step 1: Import and connect[​](#step-1-import-and-connect "Direct link to Step 1: Import and connect") ``` import { MyContract } from "./artifacts/MyContract"; ``` About wallets and accounts In the examples below, `wallet` refers to a `Wallet` instance that manages keys and signs transactions. See [Creating Accounts](/developers/docs/aztec-js/how_to_create_account.md) for how to set up a wallet. The `from` option in `send()` specifies which account pays for the transaction. This account must be registered in the wallet and have sufficient fee juice. On a local network, test accounts are pre-funded; on testnet, you typically use sponsored fees. ### Step 2: Deploy the contract[​](#step-2-deploy-the-contract "Direct link to Step 2: Deploy the contract") How you deploy depends on how you pay for it. When paying using an account's fee juice (like a test account on the local network): deploy\_basic\_local ``` // wallet and aliceAddress are from the connection guide // Deploy with constructor arguments const { contract: token } = await TokenContract.deploy( wallet, aliceAddress, "TestToken", "TST", 18, ).send({ from: aliceAddress }); // alice has fee juice and is registered in the wallet ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L38-L48](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L38-L48) On testnet, your account likely won't have Fee Juice. Instead, pay fees using the [Sponsored Fee Payment Contract method](/developers/docs/aztec-js/how_to_pay_fees.md): deploy\_sponsored\_fpc\_contract ``` // Set up the Sponsored FPC (see fees guide for full setup) const sponsoredFPCInstance = await getContractInstanceFromInstantiationParams( SponsoredFPCContract.artifact, { salt: new Fr(0) }, ); await wallet.registerContract( sponsoredFPCInstance, SponsoredFPCContract.artifact, ); const sponsoredPaymentMethod = new SponsoredFeePaymentMethod( sponsoredFPCInstance.address, ); // wallet is from the connection guide; sponsoredPaymentMethod is from the fees guide const { contract: sponsoredContract } = await TokenContract.deploy( wallet, aliceAddress, "SponsoredToken", "SPT", 18, ).send({ from: aliceAddress, fee: { paymentMethod: sponsoredPaymentMethod } }); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L50-L72](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L50-L72) Here's a complete example from the test suite: deploy\_basic ``` const { contract } = await StatefulTestContract.deploy(wallet, owner, 42).send({ from: defaultAccountAddress }); ``` > [Source code: yarn-project/end-to-end/src/e2e\_deploy\_contract/deploy\_method.test.ts#L43-L45](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/yarn-project/end-to-end/src/e2e_deploy_contract/deploy_method.test.ts#L43-L45) ## Use deployment options[​](#use-deployment-options "Direct link to Use deployment options") ### Deploy with custom salt[​](#deploy-with-custom-salt "Direct link to Deploy with custom salt") By default, the deployment's salt is random, but you can specify it (for example, if you want to get a deterministic address): deploy\_custom\_salt ``` // wallet and aliceAddress are from the connection guide const customSalt = Fr.random(); const { contract: saltedContract } = await TokenContract.deploy( wallet, aliceAddress, "SaltedToken", "SALT", 18, { salt: customSalt }, ).send({ from: aliceAddress, }); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L74-L88](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L74-L88) ### Deploy universally[​](#deploy-universally "Direct link to Deploy universally") Deploy to the same address across networks by setting `universalDeploy: true`: deploy\_universal ``` const opts = { universalDeploy: true, from: defaultAccountAddress }; const { contract } = await StatefulTestContract.deploy(wallet, owner, 42).send(opts); ``` > [Source code: yarn-project/end-to-end/src/e2e\_deploy\_contract/deploy\_method.test.ts#L62-L65](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/yarn-project/end-to-end/src/e2e_deploy_contract/deploy_method.test.ts#L62-L65) info Universal deployment excludes the sender from address computation, allowing the same address on any network with the same salt. ### Skip initialization[​](#skip-initialization "Direct link to Skip initialization") Deploy without running the constructor: skip\_initialization ``` // Deploy without running the constructor using skipInitialization const { contract: delayedToken } = await TokenContract.deploy( wallet, aliceAddress, "DelayedToken", "DLY", 18, ).send({ from: aliceAddress, skipInitialization: true, }); console.log(`Contract deployed at: ${delayedToken.address}`); // Initialize later by calling the constructor manually await delayedToken.methods .constructor(aliceAddress, "DelayedToken", "DLY", 18) .send({ from: aliceAddress }); console.log("Contract initialized"); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L274-L295](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L274-L295) ### Deploy with a specific initializer[​](#deploy-with-a-specific-initializer "Direct link to Deploy with a specific initializer") Some contracts have multiple initializer functions (e.g., both a private `constructor` and a `public_constructor`). By default, the generated `deploy()` method uses the default initializer (typically named `constructor`). To deploy using a different initializer, use `deployWithOpts`: deploy\_with\_opts ``` const { contract } = await StatefulTestContract.deployWithOpts( { wallet, method: 'public_constructor' }, owner, 42, ).send({ from: defaultAccountAddress, }); ``` > [Source code: yarn-project/end-to-end/src/e2e\_deploy\_contract/deploy\_method.test.ts#L86-L94](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/yarn-project/end-to-end/src/e2e_deploy_contract/deploy_method.test.ts#L86-L94) The `deployWithOpts` method accepts an options object as its first argument: * `wallet`: The wallet to use for deployment (required) * `method`: The name of the initializer function to call (optional, defaults to `constructor`) * `publicKeys`: Custom public keys for the contract instance (optional) The remaining arguments are the parameters for the chosen initializer function. tip This is useful for contracts that support multiple initialization patterns, such as token standards that allow both private and public minting during deployment. ## Calculate deployment address[​](#calculate-deployment-address "Direct link to Calculate deployment address") ### Get address before deployment[​](#get-address-before-deployment "Direct link to Get address before deployment") calculate\_address\_before\_deploy ``` // Calculate address without deploying // wallet is from the connection guide (see prerequisites) const deploymentSalt = Fr.random(); const deployMethod = TokenContract.deploy( wallet, aliceAddress, "PredictedToken", "PRED", 18, { salt: deploymentSalt, deployer: aliceAddress }, ); const instance = await deployMethod.getInstance(); const predictedAddress = instance.address; console.log(`Contract will deploy at: ${predictedAddress}`); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L90-L106](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L90-L106) warning This is an advanced pattern. For most use cases, deploy the contract directly and get the address from the deployed instance. ## Monitor deployment progress[​](#monitor-deployment-progress "Direct link to Monitor deployment progress") ### Track deployment transaction[​](#track-deployment-transaction "Direct link to Track deployment transaction") Use `NO_WAIT` to get the transaction hash immediately and track deployment: no\_wait\_deploy ``` // Use NO_WAIT to get the transaction hash immediately and track deployment const { txHash } = await TokenContract.deploy( wallet, aliceAddress, "AnotherToken", "ATK", 18, ).send({ from: aliceAddress, wait: NO_WAIT, }); console.log(`Deployment tx: ${txHash}`); // Wait for the transaction to be mined using the node const receipt = await waitForTx(node, txHash); console.log(`Deployed in block ${receipt.blockNumber}`); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L147-L165](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L147-L165) For most use cases, simply await the deployment to get the contract directly: deploy\_contract ``` import { TokenContract } from "@aztec/noir-contracts.js/Token"; const { contract: token } = await TokenContract.deploy( wallet, aliceAddress, "TestToken", "TST", 18, ).send({ from: aliceAddress }); console.log(`Token deployed at: ${token.address.toString()}`); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L125-L137](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_connection/index.ts#L125-L137) ## Deploy multiple contracts[​](#deploy-multiple-contracts "Direct link to Deploy multiple contracts") ### Deploy a token contract[​](#deploy-a-token-contract "Direct link to Deploy a token contract") Here's an example deploying a `TokenContract` with constructor arguments for admin, name, symbol, and decimals: deploy\_token ``` const { contract: token } = await TokenContract.deploy(wallet, owner, 'TOKEN', 'TKN', 18).send({ from: defaultAccountAddress, }); ``` > [Source code: yarn-project/end-to-end/src/e2e\_deploy\_contract/deploy\_method.test.ts#L75-L79](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/yarn-project/end-to-end/src/e2e_deploy_contract/deploy_method.test.ts#L75-L79) ### Deploy contracts with dependencies[​](#deploy-contracts-with-dependencies "Direct link to Deploy contracts with dependencies") When one contract depends on another, deploy them sequentially and pass the first contract's address: deploy\_with\_dependencies ``` // Deploy contracts with dependencies - deploy sequentially and pass addresses const { contract: baseToken } = await TokenContract.deploy( wallet, aliceAddress, "BaseToken", "BASE", 18, ).send({ from: aliceAddress }); // A second contract could reference the first (example pattern) const { contract: derivedToken } = await TokenContract.deploy( wallet, baseToken.address, // Use first contract's address as admin "DerivedToken", "DERIV", 18, ).send({ from: aliceAddress }); console.log(`Base token at: ${baseToken.address.toString()}`); console.log(`Derived token at: ${derivedToken.address.toString()}`); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L226-L247](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L226-L247) ### Deploy contracts in parallel[​](#deploy-contracts-in-parallel "Direct link to Deploy contracts in parallel") parallel\_deploy ``` // Deploy contracts in parallel using Promise.all const contracts = await Promise.all([ TokenContract.deploy(wallet, aliceAddress, "Token1", "T1", 18) .send({ from: aliceAddress, }) .then(({ contract }) => contract), TokenContract.deploy(wallet, aliceAddress, "Token2", "T2", 18) .send({ from: aliceAddress, }) .then(({ contract }) => contract), TokenContract.deploy(wallet, aliceAddress, "Token3", "T3", 18) .send({ from: aliceAddress, }) .then(({ contract }) => contract), ]); console.log(`Contract 1 at: ${contracts[0].address}`); console.log(`Contract 2 at: ${contracts[1].address}`); console.log(`Contract 3 at: ${contracts[2].address}`); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L249-L272](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L249-L272) Parallel deployment considerations Parallel deployment is faster, but transactions from the same account share a nonce sequence. The wallet handles nonce assignment automatically, but if one deployment fails, subsequent deployments may also fail due to nonce gaps. For reliable parallel deployments: * Use separate accounts for each deployment, or * Handle failures gracefully and retry with fresh nonces * Consider using `BatchCall` to bundle multiple operations into a single transaction (see below) ### Deploy with BatchCall[​](#deploy-with-batchcall "Direct link to Deploy with BatchCall") Use `BatchCall` to bundle a deployment with other calls into a single transaction. This is useful when you need to deploy a contract and immediately call methods on it: deploy\_batch ``` // Create a contract instance and make the PXE aware of it const deployMethod = StatefulTestContract.deploy(wallet, owner, 42, { deployer: defaultAccountAddress }); const contract = await deployMethod.register(); // Batch deployment and a public call into the same transaction const publicCall = contract.methods.increment_public_value(owner, 84); await new BatchCall(wallet, [deployMethod, publicCall]).send({ from: defaultAccountAddress }); ``` > [Source code: yarn-project/end-to-end/src/e2e\_deploy\_contract/deploy\_method.test.ts#L166-L174](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/yarn-project/end-to-end/src/e2e_deploy_contract/deploy_method.test.ts#L166-L174) ## Verify deployment[​](#verify-deployment "Direct link to Verify deployment") ### Check contract state[​](#check-contract-state "Direct link to Check contract state") Use `wallet.getContractMetadata()` to check your contract's current state: ``` const metadata = await wallet.getContractMetadata(contractAddress); // Check each state: metadata.instance; // Contract registered in your wallet? metadata.isContractClassPubliclyRegistered; // Class registered on the network? metadata.isContractPublished; // Instance registered on the network? metadata.initializationStatus; // Constructor has been called? ``` For a complete overview of what these states mean and when functions become callable, see [Contract Readiness States](/developers/docs/aztec-nr/contract_readiness_states.md). Here's a complete example: verify\_deployment ``` const metadata = await wallet.getContractMetadata(contract.address); const classMetadata = await wallet.getContractClassMetadata(metadata.instance!.currentContractClassId); const isPublished = classMetadata.isContractClassPubliclyRegistered; ``` > [Source code: yarn-project/end-to-end/src/e2e\_deploy\_contract/deploy\_method.test.ts#L52-L56](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/yarn-project/end-to-end/src/e2e_deploy_contract/deploy_method.test.ts#L52-L56) ### What the PXE checks automatically[​](#what-the-pxe-checks-automatically "Direct link to What the PXE checks automatically") When you simulate or send a transaction, the PXE automatically verifies: * Contract instance is registered in your wallet * Contract artifact is available locally * Contract class ID matches the network state The PXE does **not** automatically check: * Whether the contract is published on the network * Whether the contract is initialized * Whether the contract class is registered on the network If you call a public function on an unpublished contract, the transaction will fail at the network level, not during local simulation. Use `getContractMetadata()` to check these states before sending transactions if you want to provide better error messages to users. ### Verify contract is callable[​](#verify-contract-is-callable "Direct link to Verify contract is callable") verify\_contract\_callable ``` // token is from the deployment step above; aliceAddress is from the connection guide try { // Try calling a view function const { result: balance } = await token.methods .balance_of_public(aliceAddress) .simulate({ from: aliceAddress }); console.log("Contract is callable, balance:", balance); } catch (error) { console.error("Contract not accessible:", (error as Error).message); } ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L108-L119](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L108-L119) ## Register deployed contracts[​](#register-deployed-contracts "Direct link to Register deployed contracts") ### Add existing contract to wallet[​](#add-existing-contract-to-wallet "Direct link to Add existing contract to wallet") If a contract was deployed by another account: register\_external\_contract ``` // wallet is from the connection guide; contractAddress is the address of the deployed contract const contractAddress = token.address; // Get the contract metadata from the node (includes the instance) const metadata = await wallet.getContractMetadata(contractAddress); // Register the contract with the wallet // The registerContract method takes positional parameters: // - instance: ContractInstanceWithAddress (required) // - artifact: ContractArtifact (optional) // - secretKey: Fr (optional) await wallet.registerContract(metadata.instance!, TokenContract.artifact); // Now you can interact with the contract const externalContract = await TokenContract.at(contractAddress, wallet); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L121-L137](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L121-L137) warning You need the exact deployment parameters (salt, initialization hash, etc.) to correctly register an externally deployed contract. If you don't have access to the contract instance, you can reconstruct it: reconstruct\_contract\_instance ``` // Reconstruct a contract instance from deployment parameters // Use this when you need to register a contract deployed by someone else const reconstructedInstance = await getContractInstanceFromInstantiationParams( TokenContract.artifact, { publicKeys: PublicKeys.default(), constructorArtifact: "constructor", constructorArgs: [aliceAddress, "ReconstructedToken", "RTK", 18], deployer: aliceAddress, salt: new Fr(12345), // The original deployment salt }, ); // Register the reconstructed contract with the wallet await wallet.registerContract(reconstructedInstance, TokenContract.artifact); console.log( `Reconstructed contract address: ${reconstructedInstance.address.toString()}`, ); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L191-L210](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L191-L210) ## Next steps[​](#next-steps "Direct link to Next steps") * [Contract Readiness States](/developers/docs/aztec-nr/contract_readiness_states.md) - Understand the different states a contract progresses through * [Send transactions](/developers/docs/aztec-js/how_to_send_transaction.md) to interact with your contract * [Read contract data](/developers/docs/aztec-js/how_to_read_data.md) including simulating functions and reading events * [Use authentication witnesses](/developers/docs/aztec-js/how_to_use_authwit.md) for delegated calls --- # Paying Fees This guide walks you through paying transaction fees on Aztec using various payment methods. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * [Connected to a network](how_to_connect_to_local_network) with an `EmbeddedWallet` instance and funded accounts * Understanding of [fee concepts](/developers/docs/foundational-topics/fees.md) info The fee asset is only transferrable within a block to the current sequencer, as it powers the fee abstraction mechanism on Aztec. The asset is not transferable beyond this to ensure credible neutrality between all third party developer made asset portals and to ensure local compliance rules can be followed. ## Payment methods overview[​](#payment-methods-overview "Direct link to Payment methods overview") | Method | Use Case | Privacy | Requirements | | ------------------- | -------------------------------------- | ------------- | ----------------------------- | | Fee Juice (default) | Account already has Fee Juice | Public | Funded account | | Sponsored FPC | Testing, free transactions | Public | None (testnet, devnet, local) | | Private FPC | Privacy-preserving fees | Private | Bridged Fee Juice via FPC | | Third-party FPC | Pay in other tokens on testnet/mainnet | Varies by FPC | FPC provider's SDK | | Bridge + Claim | Bootstrap from L1 | Public | L1 ETH for gas | ## Mana and Fee Juice[​](#mana-and-fee-juice "Direct link to Mana and Fee Juice") Mana is Aztec's unit of computational effort (like gas on Ethereum), and Fee Juice is the native fee token used to pay for transactions. For a detailed explanation of these concepts, see [Fee Concepts](/developers/docs/foundational-topics/fees.md). ## Estimate mana costs[​](#estimate-mana-costs "Direct link to Estimate mana costs") Automatic estimation with EmbeddedWallet When using `EmbeddedWallet`, gas is estimated automatically on every `send()` call. You only need to manually estimate if you want to preview costs before sending, or if you're using a custom wallet implementation. Before sending a transaction, you can estimate the mana it will consume by simulating with `estimateGas: true`: estimate\_mana ``` const { estimatedGas } = await token.methods .transfer_in_public(aliceAddress, bobAddress, 1n, 0n) .simulate({ from: aliceAddress, fee: { estimateGas: true, estimatedGasPadding: 0.1 }, }); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L374-L381](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L374-L381) The `estimatedGas` object contains: * `gasLimits.daGas` - Estimated DA mana for main execution * `gasLimits.l2Gas` - Estimated L2 mana for main execution * `teardownGasLimits.daGas` - Estimated DA mana for teardown phase * `teardownGasLimits.l2Gas` - Estimated L2 mana for teardown phase ### Calculate expected fee from estimate[​](#calculate-expected-fee-from-estimate "Direct link to Calculate expected fee from estimate") To calculate the expected fee from estimated gas, use the `computeFee` method with current network fees: compute\_fee\_from\_estimate ``` const currentFees = await node.getCurrentMinFees(); const estimatedFee = estimatedGas.gasLimits.computeFee(currentFees).toBigInt(); console.log("Estimated fee:", estimatedFee); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L383-L387](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L383-L387) tip The `estimatedGasPadding` parameter adds a safety margin to the estimate. A value of `0.1` adds 10% padding. Use higher padding for transactions with variable gas costs. ## Get transaction fee from receipt[​](#get-transaction-fee-from-receipt "Direct link to Get transaction fee from receipt") After a transaction is mined, you can retrieve the fee paid from the receipt: get\_fee\_from\_receipt ``` const { receipt: feeReceipt } = await token.methods .mint_to_public(aliceAddress, 1n) .send({ from: aliceAddress }); console.log("Transaction fee:", feeReceipt.transactionFee); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L389-L394](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L389-L394) The `transactionFee` field is a `bigint` representing the total fee paid in the fee token (Fee Juice). You can also check execution status: check\_receipt\_status ``` console.log("Succeeded:", feeReceipt.hasExecutionSucceeded()); console.log("Block:", feeReceipt.blockNumber); console.log("Fee paid:", feeReceipt.transactionFee); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L396-L400](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L396-L400) ## Pay with Fee Juice[​](#pay-with-fee-juice "Direct link to Pay with Fee Juice") Fee Juice is the native fee token on Aztec. If your account has Fee Juice (for example, from a faucet), is [deployed](/developers/docs/aztec-js/how_to_create_account.md), and is registered in your wallet, it will be used automatically to pay for the fee of the transaction: pay\_with\_fee\_juice ``` // contract is a deployed contract instance; aliceAddress is from the connection guide const { receipt: feeJuiceReceipt } = await token.methods .mint_to_public(aliceAddress, 1n) .send({ from: aliceAddress, // no fee payment method needed; Fee Juice is used automatically }); console.log("Transaction fee:", feeJuiceReceipt.transactionFee); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L402-L411](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L402-L411) ## Use Fee Payment Contracts[​](#use-fee-payment-contracts "Direct link to Use Fee Payment Contracts") Fee Payment Contracts (FPCs) pay Fee Juice on your behalf. An FPC holds its own Fee Juice balance to pay the protocol and can accept other tokens from users in exchange. Some FPCs operate privately by design, routing fee payments through private notes rather than public function calls. note The SDK includes `PrivateFeePaymentMethod` and `PublicFeePaymentMethod` classes for the built-in reference FPC, but these are **deprecated** and do not work on mainnet alpha. For custom-token fee payment, use a third-party FPC with its own SDK (see [below](#third-party-fpcs-on-testnet-and-mainnet)). ### Sponsored FPC[​](#sponsored-fpc "Direct link to Sponsored FPC") note The Sponsored FPC is not deployed on mainnet. It is available on testnet, devnet, and local network. The Sponsored FPC pays fees unconditionally. It is available on testnet, devnet, and local network. You can derive the Sponsored FPC address from its deployment parameters, register it with your wallet, and use it to pay for transactions: deploy\_sponsored\_fpc\_contract ``` // Set up the Sponsored FPC (see fees guide for full setup) const sponsoredFPCInstance = await getContractInstanceFromInstantiationParams( SponsoredFPCContract.artifact, { salt: new Fr(0) }, ); await wallet.registerContract( sponsoredFPCInstance, SponsoredFPCContract.artifact, ); const sponsoredPaymentMethod = new SponsoredFeePaymentMethod( sponsoredFPCInstance.address, ); // wallet is from the connection guide; sponsoredPaymentMethod is from the fees guide const { contract: sponsoredContract } = await TokenContract.deploy( wallet, aliceAddress, "SponsoredToken", "SPT", 18, ).send({ from: aliceAddress, fee: { paymentMethod: sponsoredPaymentMethod } }); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L50-L72](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L50-L72) Here's a simpler example from the test suite: sponsored\_fpc\_simple ``` const bananasToSendToBob = 10n; const { receipt: tx } = await bananaCoin.methods .transfer_in_public(aliceAddress, bobAddress, bananasToSendToBob, 0) .send({ from: aliceAddress, fee: { gasSettings, paymentMethod: new SponsoredFeePaymentMethod(sponsoredFPC.address), }, }); ``` > [Source code: yarn-project/end-to-end/src/e2e\_fees/sponsored\_payments.test.ts#L57-L68](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/yarn-project/end-to-end/src/e2e_fees/sponsored_payments.test.ts#L57-L68) ### Private fee payment[​](#private-fee-payment "Direct link to Private fee payment") For transactions where the fee payment itself should be private, you can use a fully private FPC, one that holds Fee Juice claimed from L1 as an internal private balance, works on every network, and never needs an onchain deployment. See [Pay Fees Privately](/developers/docs/aztec-js/how_to_use_private_fee_juice.md) for how this pattern works and a walkthrough using a community-built example. Shared salt for privacy When multiple apps derive the same private FPC address (using the same artifact and salt), every private fee payment joins a single, larger privacy set. See [recommended salt](/developers/docs/aztec-js/how_to_use_private_fee_juice.md#recommended-salt-0) for details. ### Third-party FPCs on testnet and mainnet[​](#third-party-fpcs-on-testnet-and-mainnet "Direct link to Third-party FPCs on testnet and mainnet") On networks where the Sponsored FPC is unavailable, third-party FPCs deployed by ecosystem teams let you pay fees in tokens other than Fee Juice. Each FPC provider typically offers an SDK or API that handles payment method construction on the client side. This may include quote fetching and authwit creation, though the exact flow depends on the FPC design. For background on how FPCs work at the protocol level, see [how FPCs work](/developers/docs/foundational-topics/fees.md#how-fpcs-work). #### Example: Nethermind Private Multi Asset FPC[​](#example-nethermind-private-multi-asset-fpc "Direct link to Example: Nethermind Private Multi Asset FPC") To illustrate how a third-party FPC integration works, the following walkthrough uses Nethermind's [Private Multi Asset FPC](https://github.com/NethermindEth/aztec-fpc) as a reference. This is one implementation, other FPCs may differ in design and API. This FPC is quote-based and operates privately: * A single deployment accepts many tokens. The asset is selected per quote rather than hard-coded at deploy time. * Fee payments are transferred as private notes, so fee activity is not visible onchain. * An operator-run attestation service signs per-user quotes binding the FPC address, accepted asset, amounts, expiry, and user. * A cold-start entrypoint allows a brand-new account to bridge tokens from L1, claim on L2, and pay the fee in a single transaction. Note that the cold-start path calls `Token::mint_to_private`, which enqueues a public call to update the token's total supply, so the minted amount is visible onchain even though the user's identity and balances remain private. Third-party software This FPC is developed and maintained by Nethermind, not by Aztec Labs. The SDK (`@nethermindeth/aztec-fpc-sdk`) may not yet be published to npm; check the [repository README](https://github.com/NethermindEth/aztec-fpc/blob/main/sdk/README.md) for current install instructions. Review the [protocol spec](https://github.com/NethermindEth/aztec-fpc/blob/main/docs/spec/protocol-spec.md) and evaluate independently before integrating. The SDK wraps the quote-and-pay flow into a single call. The snippet below shows the general shape of the integration (illustrative; verify against the current SDK API before using): ``` import { FpcClient } from "@nethermindeth/aztec-fpc-sdk"; // Point the client at the FPC's attestation service const fpcClient = new FpcClient({ fpcAddress, // the deployed FPC contract address operator, // operator's Aztec address node, // PXE or node connection attestationBaseUrl: "https://...", // attestation service URL from the FPC provider }); // Estimate gas, fetch a signed quote, and build the payment method const payment = await fpcClient.createPaymentMethod({ wallet, user: aliceAddress, // the account paying the fee tokenAddress, // the token you want to pay in estimatedGas, // from a prior estimateGas call }); // Use it like any other payment method const tx = await myContract.methods.myMethod(args).send({ fee: payment.fee }); await tx.wait(); ``` For the cold-start flow, deployment addresses, and the full API, see the [`aztec-fpc` repository](https://github.com/NethermindEth/aztec-fpc). ## Bridge Fee Juice from L1[​](#bridge-fee-juice-from-l1 "Direct link to Bridge Fee Juice from L1") Fee Juice is non-transferable on L2, but you can bridge it from L1, claim it on L2, and use it. This involves a few components that are part of a running network's infrastructure: * An L1 fee juice contract * An L1 fee juice portal * An L2 fee juice portal * An L2 fee juice contract `aztec.js` provides helpers to simplify the process: bridge\_fee\_juice\_setup ``` import { createExtendedL1Client } from "@aztec/ethereum/client"; import { L1FeeJuicePortalManager } from "@aztec/aztec.js/ethereum"; import { createLogger } from "@aztec/aztec.js/log"; // Create an L1 client (accepts a mnemonic or 0x-prefixed private key) const l1RpcUrl = process.env.ETHEREUM_HOST ?? "http://localhost:8545"; const l1Mnemonic = "test test test test test test test test test test test junk"; const l1Client = createExtendedL1Client([l1RpcUrl], l1Mnemonic); // Create a portal manager to interact with the L1 fee juice portal const logger = createLogger("docs:fee-juice-bridge"); const portalManager = await L1FeeJuicePortalManager.new(node, l1Client, logger); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L96-L110](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_connection/index.ts#L96-L110) Under the hood, `L1FeeJuicePortalManager` gets the L1 addresses from the node `node_getNodeInfo` endpoint. It then exposes an easy method `bridgeTokensPublic` which mints fee juice on L1 and sends it to an L2 address via the L1 portal: bridge\_fee\_juice\_execute ``` // portalManager is from the L1FeeJuicePortalManager setup above // feeJuiceAccount.address is an Aztec address from createSchnorrAccount const claim = await portalManager.bridgeTokensPublic( feeJuiceAccount.address, // the L2 address 1000000000000000000000n, // the amount to send to the L1 portal true, // whether to mint or not (set to false if your L1 account already has fee juice!) ); console.log("Claim secret:", claim.claimSecret); console.log("Claim amount:", claim.claimAmount); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L112-L123](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_connection/index.ts#L112-L123) After this transaction is minted on L1 and a few blocks pass, you can claim the message on L2 and use it directly to pay for fees: bridge\_fee\_juice\_claim ``` import { FeeJuicePaymentMethodWithClaim } from "@aztec/aztec.js/fee"; // claim is from the bridgeTokensPublic step above // Create a payment method that claims the bridged Fee Juice and uses it to pay const bridgePaymentMethod = new FeeJuicePaymentMethodWithClaim(feeJuiceAccount.address, claim); // Use it to pay for any transaction; here we deploy the account in one step const deployMethodBridged = await feeJuiceAccount.getDeployMethod(); await deployMethodBridged.send({ from: NO_FROM, fee: { paymentMethod: bridgePaymentMethod }, }); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L156-L169](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_connection/index.ts#L156-L169) ## Configure gas settings[​](#configure-gas-settings "Direct link to Configure gas settings") ### Understanding gas dimensions[​](#understanding-gas-dimensions "Direct link to Understanding gas dimensions") Gas settings specify limits and fees for both DA and L2 dimensions: * **gasLimits**: Maximum mana for main execution phase * **teardownGasLimits**: Maximum mana for teardown phase (used by FPCs for refunds) * **maxFeesPerGas**: Maximum price you're willing to pay per mana unit * **maxPriorityFeesPerGas**: Priority fee for faster inclusion The fee limit is calculated as `gasLimits × maxFeesPerGas` for each dimension. ### Set custom gas limits[​](#set-custom-gas-limits "Direct link to Set custom gas limits") Set custom gas limits by importing from `stdlib`: custom\_gas\_settings ``` // Query current network fees to set realistic limits const networkFees = await node.getCurrentMinFees(); const gasSettings = GasSettings.from({ gasLimits: { daGas: 100_000, l2Gas: 2_000_000 }, teardownGasLimits: { daGas: 100_000, l2Gas: 2_000_000 }, maxFeesPerGas: { feePerDaGas: networkFees.feePerDaGas * 2n, feePerL2Gas: networkFees.feePerL2Gas * 2n, }, maxPriorityFeesPerGas: { feePerDaGas: 0n, feePerL2Gas: 0n }, }); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L413-L425](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L413-L425) Then pass the settings when sending: send\_with\_gas\_settings ``` const { receipt: gsReceipt } = await token.methods .mint_to_public(aliceAddress, 1n) .send({ from: aliceAddress, fee: { gasSettings }, }); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L427-L434](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L427-L434) Note that `gasLimits` and `teardownGasLimits` use `daGas`/`l2Gas` field names, while `maxFeesPerGas` and `maxPriorityFeesPerGas` use `feePerDaGas`/`feePerL2Gas`. ### Use automatic gas estimation[​](#use-automatic-gas-estimation "Direct link to Use automatic gas estimation") note When using `EmbeddedWallet`, gas estimation happens automatically on every `send()`; you don't need to pass `estimateGas`. This option is useful for custom wallet implementations or when you want to estimate gas during a `simulate()` call. auto\_gas\_estimation ``` // Estimate gas for a transaction before sending const { estimatedGas: autoEstimate } = await token.methods .mint_to_public(aliceAddress, 1n) .simulate({ from: aliceAddress, fee: { estimateGas: true, estimatedGasPadding: 0.2, // 20% padding }, }); console.log("Auto-estimated L2 gas:", autoEstimate.gasLimits.l2Gas); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L447-L459](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L447-L459) tip Gas estimation runs a simulation first to determine actual gas usage, then adds padding for safety. This works with all payment methods, including FPCs. ## Next steps[​](#next-steps "Direct link to Next steps") * Learn about [fee concepts](/developers/docs/foundational-topics/fees.md) in detail * Explore [authentication witnesses](/developers/docs/aztec-js/how_to_use_authwit.md) for delegated payments * See [testing guide](/developers/docs/aztec-js/how_to_test.md) for fee testing strategies --- # Reading Contract Data This guide shows you how to read data from Aztec contracts in TypeScript, including simulating function calls, reading raw logs, and retrieving typed events. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * [Connected to a network](how_to_connect_to_local_network) with an `EmbeddedWallet` instance and funded accounts * A deployed contract instance (see [How to Deploy a Contract](/developers/docs/aztec-js/how_to_deploy_contract.md)) ## Simulating functions[​](#simulating-functions "Direct link to Simulating functions") The `simulate` method executes a contract function locally and returns its result. It works with private, public, and utility functions. No transaction is created and no gas is spent. simulate\_function ``` const { result: balance } = await token.methods .balance_of_public(aliceAddress) .simulate({ from: aliceAddress }); console.log(`Alice's token balance: ${balance}`); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L148-L154](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_connection/index.ts#L148-L154) The `from` option specifies which account context to use for the simulation. This is required for all simulations. For private functions, it determines which account's private state is accessed. For public functions, it sets the `msg_sender` context. ### Handling return values[​](#handling-return-values "Direct link to Handling return values") For functions returning multiple values, destructure the result: ``` // contract and callerAddress are from the example above const { result: [value1, value2] } = await contract.methods .get_multiple_values() .simulate({ from: callerAddress }); ``` ### Including metadata[​](#including-metadata "Direct link to Including metadata") Set `includeMetadata: true` to get additional information about the simulation: simulate\_with\_metadata ``` const metaResult = await token.methods .balance_of_public(aliceAddress) .simulate({ from: aliceAddress, includeMetadata: true }); console.log("Balance:", metaResult.result); console.log("L2 gas limit:", metaResult.estimatedGas.gasLimits.l2Gas); console.log("DA gas limit:", metaResult.estimatedGas.gasLimits.daGas); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L354-L361](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L354-L361) The result includes `result` (the function return value), `stats` (execution statistics), `offchainEffects`, and `estimatedGas` (with `gasLimits` and `teardownGasLimits`). ### Private function considerations[​](#private-function-considerations "Direct link to Private function considerations") When simulating private functions, the caller must have access to any private state being read. The PXE only has visibility into notes belonging to registered accounts. simulate\_private\_access ``` // This works if aliceAddress owns the notes const { result: privateBalance } = await token.methods .balance_of_private(aliceAddress) .simulate({ from: aliceAddress }); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L470-L475](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L470-L475) If the caller doesn't have access to another address's notes, the simulation will fail with an error. warning Simulation runs locally without generating proofs. No correctness guarantees are provided on the result. See [Call Types](/developers/docs/foundational-topics/call_types.md#simulate) for more details. ## Reading logs vs events[​](#reading-logs-vs-events "Direct link to Reading logs vs events") Contracts emit data in two forms you can read: | Aspect | Logs | Events | | ------------------ | --------------------------- | -------------------------------------------------- | | **What** | Raw field arrays (untyped) | Decoded domain objects with type info | | **Storage** | Archiver (node-level) | PXE (client-level) for private events | | **API** | `aztecNode.getPublicLogs()` | `wallet.getPrivateEvents()` or `getPublicEvents()` | | **Type awareness** | None - raw `Fr[]` data | Requires ABI metadata to decode | **Logs** are the low-level transport layer, while **events** are the semantic application layer decoded using ABI metadata from your contract. ## Reading raw public logs[​](#reading-raw-public-logs "Direct link to Reading raw public logs") Use `aztecNode.getPublicLogs()` to retrieve raw log data: read\_public\_logs ``` const publicLogs = await node.getPublicLogs({ fromBlock: 1, toBlock: (await node.getBlockNumber()) + 1, }); if (publicLogs.logs.length > 0) { const rawFields = publicLogs.logs[0].log.getEmittedFields(); // Fr[] console.log("Raw log fields:", rawFields.length); } ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L363-L372](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L363-L372) You can also filter by transaction hash or block range: read\_logs\_by\_filter ``` // Get logs for a specific transaction const txLogs = await node.getPublicLogs({ txHash: gsReceipt.txHash }); // Get logs for a block range const rangeLogs = await node.getPublicLogs({ fromBlock: 1, toBlock: (await node.getBlockNumber()) + 1, }); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L436-L445](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L436-L445) ## Reading events[​](#reading-events "Direct link to Reading events") Events provide typed access to contract emissions. The event metadata from your contract artifact (`Contract.events.EventName`) contains the ABI type information needed for decoding. ### Reading public events[​](#reading-public-events "Direct link to Reading public events") Use the `getPublicEvents` helper to retrieve typed public events: import\_get\_public\_events ``` import { getPublicEvents as _importCheck } from "@aztec/aztec.js/events"; ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L461-L463](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L461-L463) get\_public\_events ``` const publicEventFilter: PublicEventFilter = { fromBlock: BlockNumber(firstTx.blockNumber!), toBlock: BlockNumber(lastTx.blockNumber! + 1), }; const { events: collectedEvent0s } = await getPublicEvents( aztecNode, TestLogContract.events.ExampleEvent0, publicEventFilter, ); const { events: collectedEvent1s } = await getPublicEvents( aztecNode, TestLogContract.events.ExampleEvent1, publicEventFilter, ); ``` > [Source code: yarn-project/end-to-end/src/e2e\_event\_logs.test.ts#L140-L157](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/yarn-project/end-to-end/src/e2e_event_logs.test.ts#L140-L157) The function parameters are: * `aztecNode` - The node to query * `Contract.events.EventName` - Event metadata from the contract artifact (contains the event selector) * `filter` - An object with optional fields: * `fromBlock` - Starting block number (inclusive) * `toBlock` - Ending block number (exclusive) * `contractAddress` - Filter to a specific contract * `txHash` - Filter to a specific transaction Each returned event includes both the decoded `event` data and `metadata` (block number, block hash, tx hash, contract address). ### Reading private events[​](#reading-private-events "Direct link to Reading private events") Private events are stored in the PXE with privacy scoping. Use `wallet.getPrivateEvents()` to retrieve them: import\_private\_event\_types ``` import type { PrivateEventFilter } from "@aztec/aztec.js/wallet"; import { BlockNumber } from "@aztec/aztec.js/fields"; ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L465-L468](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L465-L468) The `BlockNumber` type is a branded type that wraps raw numbers for type safety. Use it when setting `fromBlock` and `toBlock` in filters. get\_private\_events ``` const eventFilter: PrivateEventFilter = { contractAddress: testLogContract.address, fromBlock: BlockNumber(firstBlockNumber), toBlock: BlockNumber(lastBlockNumber + 1), scopes: [account1Address, account2Address], }; // Each emit_encrypted_events call emits 2 ExampleEvent0s and 1 ExampleEvent1 // So with 5 calls we expect 10 ExampleEvent0s and 5 ExampleEvent1s const collectedEvent0s = await wallet.getPrivateEvents( TestLogContract.events.ExampleEvent0, eventFilter, ); const collectedEvent1s = await wallet.getPrivateEvents( TestLogContract.events.ExampleEvent1, eventFilter, ); ``` > [Source code: yarn-project/end-to-end/src/e2e\_event\_logs.test.ts#L71-L90](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/yarn-project/end-to-end/src/e2e_event_logs.test.ts#L71-L90) The `PrivateEventFilter` includes: * `contractAddress` - The contract that emitted the events * `fromBlock` / `toBlock` - Block range to search * `scopes` - Array of account addresses whose private state is being queried * `txHash` (optional) - Filter to a specific transaction Private events return objects with an `event` property containing the decoded data: ``` collectedEvents.forEach((ev) => { console.log(ev.event.value0); // Access event fields via .event }); ``` ## Polling for events[​](#polling-for-events "Direct link to Polling for events") To continuously monitor for new events, poll at regular intervals while tracking the last processed block: poll\_for\_events ``` // Poll for new events at regular intervals let lastProcessedBlock = await node.getBlockNumber(); async function pollForTransferEvents() { const currentBlock = await node.getBlockNumber(); if (currentBlock > lastProcessedBlock) { const { events } = await getPublicEvents( node, TokenContract.events.Transfer, { fromBlock: BlockNumber(lastProcessedBlock + 1), toBlock: BlockNumber(currentBlock + 1), // toBlock is exclusive }, ); for (const { event, metadata } of events) { // Process each transfer event console.log( `Transfer: ${event.amount} from ${event.from} to ${event.to}`, ); console.log( ` in block ${metadata.l2BlockNumber}, tx ${metadata.txHash}`, ); } lastProcessedBlock = currentBlock; } } // Example: poll once (in production, use setInterval) await pollForTransferEvents(); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L297-L330](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L297-L330) For private events, use the same pattern with `wallet.getPrivateEvents()` and update the `fromBlock` in your filter accordingly. ## Next steps[​](#next-steps "Direct link to Next steps") * [Send transactions](/developers/docs/aztec-js/how_to_send_transaction.md) to modify contract state * Learn about [call types](/developers/docs/foundational-topics/call_types.md) and when to use simulation vs transactions * Explore [testing patterns](/developers/docs/aztec-js/how_to_test.md) that use simulation --- # Sending Transactions This guide shows you how to send transactions to smart contracts on Aztec. ## Overview[​](#overview "Direct link to Overview") Transactions on Aztec execute contract functions that modify state. Unlike simple reads, transactions go through private execution on your device, proving, and then submission to the network for inclusion in a block. You can send single transactions, batch multiple calls atomically, and query transaction status after submission. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * [Connected to a network](how_to_connect_to_local_network) with an `EmbeddedWallet` instance and funded accounts * Deployed contract with its address and ABI (see [How to Deploy](/developers/docs/aztec-js/how_to_deploy_contract.md)) * Understanding of [contract interactions](/developers/docs/aztec-nr/framework-description/calling_contracts.md) ## Send a transaction[​](#send-a-transaction "Direct link to Send a transaction") After connecting to a contract: connect\_to\_contract ``` // wallet is from the connection guide; token is the contract deployed in the deploy guide const contract = await Contract.at( token.address, TokenContract.artifact, wallet, ); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L332-L339](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L332-L339) Call a function and wait for it to be mined: basic\_send\_transaction ``` // contract is from the step above; aliceAddress is from the connection guide const { receipt: sendReceipt } = await contract.methods .transfer_in_public(aliceAddress, bobAddress, 100n, 0n) .send({ from: aliceAddress }); console.log(`Transaction mined in block ${sendReceipt.blockNumber}`); console.log(`Transaction fee: ${sendReceipt.transactionFee}`); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L341-L348](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L341-L348) The `from` field specifies which account sends the transaction. If that account has Fee Juice, it pays for the transaction automatically. For other fee payment options, see [paying fees](/developers/docs/aztec-js/how_to_pay_fees.md). ### What happens behind the scenes[​](#what-happens-behind-the-scenes "Direct link to What happens behind the scenes") When using `EmbeddedWallet`, calling `send()` triggers a **simulation** step before the transaction is actually sent. This simulation: 1. **Estimates gas limits** based on actual execution, with a configurable padding (default 10%) to avoid reverts. If you provide explicit gas limits via `fee.gasSettings`, they take precedence. 2. **Generates private authwits automatically**. If the contract you're calling requires a private [authentication witness](/developers/docs/aztec-js/how_to_use_authwit.md) (e.g., a token transfer on behalf of the sender), the wallet detects this during simulation and creates the authwit on the fly — no manual setup needed. This means a simple `.send()` is all most apps need. You can adjust the gas padding if desired: set\_gas\_padding ``` wallet.setEstimatedGasPadding(0.2); // 20% padding instead of the default 10% ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L350-L352](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L350-L352) note Public authwits still need to be set explicitly before the transaction, as they require a separate onchain transaction. See [Using Authentication Witnesses](/developers/docs/aztec-js/how_to_use_authwit.md) for details. ### Send without waiting[​](#send-without-waiting "Direct link to Send without waiting") Use the `NO_WAIT` option to get the transaction hash immediately without waiting for inclusion: no\_wait\_transaction ``` // Use NO_WAIT for regular transactions too const { txHash: transferTxHash } = await token.methods .transfer(bobAddress, 100n) .send({ from: aliceAddress, wait: NO_WAIT }); console.log(`Transaction sent: ${transferTxHash.toString()}`); // Wait for inclusion later using the node const transferReceipt = await waitForTx(node, transferTxHash); console.log(`Transaction mined in block ${transferReceipt.blockNumber}`); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L167-L178](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L167-L178) ## Send batch transactions[​](#send-batch-transactions "Direct link to Send batch transactions") Execute multiple calls atomically using `BatchCall`: batch\_call ``` // Execute multiple calls atomically using BatchCall const batch = new BatchCall(wallet, [ token.methods.mint_to_public(aliceAddress, 500n), token.methods.transfer(bobAddress, 200n), ]); const { receipt: batchReceipt } = await batch.send({ from: aliceAddress }); console.log(`Batch executed in block ${batchReceipt.blockNumber}`); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L180-L189](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L180-L189) warning All calls in a batch must succeed or the entire batch reverts. Use batch transactions when you need atomic execution of multiple operations. ## Query transaction status[​](#query-transaction-status "Direct link to Query transaction status") After sending a transaction without waiting, you can query its receipt using the node: query\_tx\_status ``` // Query transaction status after sending without waiting const { txHash: statusTxHash } = await token.methods .transfer(bobAddress, 10n) .send({ from: aliceAddress, wait: NO_WAIT }); // Check status using the node const txReceipt = await node.getTxReceipt(statusTxHash); console.log(`Status: ${txReceipt.status}`); console.log(`Block number: ${txReceipt.blockNumber}`); console.log(`Transaction fee: ${txReceipt.transactionFee}`); ``` > [Source code: docs/examples/ts/aztecjs\_advanced/index.ts#L212-L224](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_advanced/index.ts#L212-L224) The receipt includes: * `status` - Transaction status (`pending`, `proposed`, `checkpointed`, `proven`, `finalized`, or `dropped`) * `blockNumber` - Block where the transaction was included * `transactionFee` - Fee paid for the transaction * `error` - Error message if the transaction reverted ## Next steps[​](#next-steps "Direct link to Next steps") * Learn to [read contract data](/developers/docs/aztec-js/how_to_read_data.md) including simulating functions before sending * Understand [authentication witnesses](/developers/docs/aztec-js/how_to_use_authwit.md) for delegated transactions * Configure [gas and fees](/developers/docs/aztec-js/how_to_pay_fees.md) for transaction costs * Set up [transaction testing](/developers/docs/aztec-js/how_to_test.md) in your development workflow --- # Testing Smart Contracts This guide covers how to test Aztec smart contracts by connecting to a local network, deploying contracts, and verifying their behavior. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * A running [local Aztec network](/developers/getting_started_on_local_network.md) * A compiled contract artifact (see [How to compile a contract](/developers/docs/aztec-nr/compiling_contracts.md)) * Node.js test framework (Jest, Vitest, or similar) ## Setting up the test environment[​](#setting-up-the-test-environment "Direct link to Setting up the test environment") Connect to your local Aztec network and create an embedded wallet: connect\_to\_network ``` import { createAztecNodeClient, waitForNode } from "@aztec/aztec.js/node"; import { EmbeddedWallet } from "@aztec/wallets/embedded"; import { getInitialTestAccountsData } from "@aztec/accounts/testing"; const nodeUrl = process.env.AZTEC_NODE_URL ?? "http://localhost:8080"; const node = createAztecNodeClient(nodeUrl); // Wait for the network to be ready await waitForNode(node); // Create an EmbeddedWallet connected to the node const wallet = await EmbeddedWallet.create(node, { ephemeral: true }); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L1-L14](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_connection/index.ts#L1-L14) The `EmbeddedWallet` manages accounts, tracks deployed contracts, and handles transaction proving. It connects to the Aztec node which provides access to both the Private eXecution Environment (PXE) and the network. ## Loading test accounts[​](#loading-test-accounts "Direct link to Loading test accounts") The local network comes with pre-funded accounts. Load them into your wallet: load\_test\_accounts ``` import { registerInitialLocalNetworkAccountsInWallet } from "@aztec/wallets/testing"; // wallet is the EmbeddedWallet from the setup section above const [alice, bob] = await registerInitialLocalNetworkAccountsInWallet(wallet); ``` > [Source code: docs/examples/ts/aztecjs\_testing/index.ts#L107-L112](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_testing/index.ts#L107-L112) ## Deploying contracts in tests[​](#deploying-contracts-in-tests "Direct link to Deploying contracts in tests") Deploy contracts using the generated contract class: deploy\_test\_contract ``` // wallet is from the setup section; alice is from registerInitialLocalNetworkAccountsInWallet const { contract: testToken } = await TokenContract.deploy( wallet, alice, // admin "TestToken", "TST", 18, ).send({ from: alice }); ``` > [Source code: docs/examples/ts/aztecjs\_testing/index.ts#L114-L123](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_testing/index.ts#L114-L123) ## Verifying contract state[​](#verifying-contract-state "Direct link to Verifying contract state") Use `.simulate()` to read contract state without creating a transaction: simulate\_function ``` const { result: balance } = await token.methods .balance_of_public(aliceAddress) .simulate({ from: aliceAddress }); console.log(`Alice's token balance: ${balance}`); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L148-L154](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_connection/index.ts#L148-L154) Simulations are free (no gas cost) and return the function's result directly. Use them for: * Checking balances and state before/after transactions * Validating expected outcomes in assertions * Debugging contract behavior ## Sending test transactions[​](#sending-test-transactions "Direct link to Sending test transactions") Send transactions and wait for confirmation: send\_transaction ``` const { receipt } = await token.methods .mint_to_public(aliceAddress, 1000n) .send({ from: aliceAddress }); console.log(`Transaction mined in block ${receipt.blockNumber}`); console.log(`Transaction fee: ${receipt.transactionFee}`); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L139-L146](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_connection/index.ts#L139-L146) The `send()` method returns when the transaction is included in a block. ## Example test structure[​](#example-test-structure "Direct link to Example test structure") Here's a complete test example showing the typical structure with setup, test cases, and assertions: complete\_test\_example ``` import { createAztecNodeClient, waitForNode } from "@aztec/aztec.js/node"; import { EmbeddedWallet } from "@aztec/wallets/embedded"; import { getInitialTestAccountsData } from "@aztec/accounts/testing"; import { TokenContract } from "@aztec/noir-contracts.js/Token"; import { AztecAddress } from "@aztec/aztec.js/addresses"; // This file demonstrates a complete Jest test structure. // In a real test file, wrap this in describe() and it() blocks. // Test setup variables let wallet: EmbeddedWallet; let aliceAddress: AztecAddress; let bobAddress: AztecAddress; let token: TokenContract; // beforeAll equivalent - setup async function setup() { const node = createAztecNodeClient(process.env.AZTEC_NODE_URL ?? "http://localhost:8080"); await waitForNode(node); wallet = await EmbeddedWallet.create(node, { ephemeral: true }); const testAccounts = await getInitialTestAccountsData(); [aliceAddress, bobAddress] = await Promise.all( testAccounts.slice(0, 2).map(async (account) => { return (await wallet.createSchnorrAccount(account.secret, account.salt, account.signingKey)).address; }), ); ({ contract: token } = await TokenContract.deploy( wallet, aliceAddress, "Test", "TST", 18, ).send({ from: aliceAddress, })); } // Test: mints tokens to an account async function testMintTokens() { await token.methods .mint_to_public(aliceAddress, 1000n) .send({ from: aliceAddress }); const { result: balance } = await token.methods .balance_of_public(aliceAddress) .simulate({ from: aliceAddress }); if (balance !== 1000n) { throw new Error(`Expected balance 1000n, got ${balance}`); } console.log("✓ Mint tokens test passed"); } // Test: transfers tokens between accounts async function testTransferTokens() { // First mint some tokens await token.methods .mint_to_public(aliceAddress, 1000n) .send({ from: aliceAddress }); // Transfer to bob using public transfer await token.methods.transfer_in_public(aliceAddress, bobAddress, 100n, 0n).send({ from: aliceAddress }); const { result: aliceBalance } = await token.methods .balance_of_public(aliceAddress) .simulate({ from: aliceAddress }); const { result: bobBalance } = await token.methods .balance_of_public(bobAddress) .simulate({ from: bobAddress }); // Note: balances accumulate from previous test console.log(`Alice balance: ${aliceBalance}, Bob balance: ${bobBalance}`); console.log("✓ Transfer tokens test passed"); } // Test: reverts when transferring more than balance async function testRevertOnOverTransfer() { const { result: balance } = await token.methods .balance_of_public(aliceAddress) .simulate({ from: aliceAddress }); try { await token.methods .transfer_in_public(aliceAddress, bobAddress, balance + 1n, 0n) .simulate({ from: aliceAddress }); throw new Error("Expected simulation to throw"); } catch (error) { // Expected to throw console.log("✓ Revert on over-transfer test passed"); } } // Run all tests async function runTests() { await setup(); await testMintTokens(); await testTransferTokens(); await testRevertOnOverTransfer(); console.log("\n✓ All tests passed"); } await runTests(); ``` > [Source code: docs/examples/ts/aztecjs\_testing/index.ts#L1-L105](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_testing/index.ts#L1-L105) ## Testing failure cases[​](#testing-failure-cases "Direct link to Testing failure cases") Test that invalid operations revert as expected: test\_revert\_case ``` async function testRevertExample() { // testToken and alice are from the deploy/load sections above const { result: balance } = await testToken.methods .balance_of_public(alice) .simulate({ from: alice }); let reverted = false; try { await testToken.methods .transfer_in_public(alice, bob, balance + 1n, 0n) .simulate({ from: alice }); } catch (error) { reverted = true; } if (!reverted) { throw new Error("Expected simulation to revert for over-transfer"); } console.log("✓ Revert on over-transfer test passed"); } await testRevertExample(); ``` > [Source code: docs/examples/ts/aztecjs\_testing/index.ts#L125-L148](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_testing/index.ts#L125-L148) Use `.simulate()` to test reverts without spending gas. The simulation will throw if the transaction would fail onchain. ## Further reading[​](#further-reading "Direct link to Further reading") * [How to read contract data](/developers/docs/aztec-js/how_to_read_data.md) * [How to send transactions](/developers/docs/aztec-js/how_to_send_transaction.md) * [How to deploy a contract](/developers/docs/aztec-js/how_to_deploy_contract.md) * [How to create an account](/developers/docs/aztec-js/how_to_create_account.md) * [How to compile a contract](/developers/docs/aztec-nr/compiling_contracts.md) --- # Using Authentication Witnesses This guide shows you how to create and use authentication witnesses (authwits) to authorize other accounts to perform actions on your behalf. Automatic private authwits with EmbeddedWallet When using `EmbeddedWallet`, **private authwits are created automatically**. The wallet simulates your transaction before sending and detects which private authwits are needed, then generates them on the fly. You don't need to create them manually. Public authwits still need to be set explicitly, as they require a separate onchain transaction before use. The manual approach described below is also relevant if you're building a custom wallet implementation. aztec-nr Using AuthWitnesses is always a two-part process. This guide shows how to generate and use them, but you still need to set up your contract to accept and authenticate them. Therefore it is recommended to read the `aztec-nr` [guide on authwitnesses](/developers/docs/aztec-nr/framework-description/authentication_witnesses.md) before this one. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * [Connected to a network](how_to_connect_to_local_network) with an `EmbeddedWallet` instance and funded accounts * Contract with authwit validation (see [smart contract authwits](/developers/docs/aztec-nr/framework-description/authentication_witnesses.md)) * Understanding of [authwit concepts](/developers/docs/foundational-topics/advanced/authwit.md) ## Intent types[​](#intent-types "Direct link to Intent types") The authwit system supports different intent types depending on your use case: * **`CallIntent`**: Use when authorizing a specific contract function call. Contains `{ caller, call }` where `call` is a `FunctionCall`, typically obtained with `await interaction.getFunctionCall()`. * **`ContractFunctionInteractionCallIntent`**: Convenience form that takes the interaction directly. Contains `{ caller, action }` where `action` is a `ContractFunctionInteraction`; internally resolved to a `FunctionCall` before signing. * **`IntentInnerHash`**: Use when authorizing arbitrary data. Contains `{ consumer, innerHash }` where `consumer` is the contract that will verify the authwit. ## Create private authwits[​](#create-private-authwits "Direct link to Create private authwits") note If you're using `EmbeddedWallet`, this section is handled for you automatically. See the tip above. Private authwits authorize actions in the private domain. The authorization is included directly in the transaction that uses it. Let's say Alice wants to allow Bob to transfer tokens from her account. Alice is the **authorizer** (she owns the tokens) and Bob is the **caller** (he will execute the transfer): private\_authwit ``` // Alice wants to allow Bob to transfer tokens from her account (private) const privateNonce = Fr.random(); // Define the action Bob will execute const privateAction = tokenContract.methods.transfer_in_private( aliceAddress, // from bobAddress, // to 100n, // amount privateNonce, // authwit nonce for replay protection ); // Alice creates an authwit authorizing Bob to call this function const privateWitness = await wallet.createAuthWit(aliceAddress, { caller: bobAddress, call: await privateAction.getFunctionCall(), }); // Bob executes the transfer, providing the authwit // additionalScopes lets the PXE access Alice's private state // during authwit verification await privateAction.send({ from: bobAddress, authWitnesses: [privateWitness], additionalScopes: [aliceAddress], }); ``` > [Source code: docs/examples/ts/aztecjs\_authwit/index.ts#L45-L71](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_authwit/index.ts#L45-L71) tip The nonce prevents replay attacks. When `from` and `msg_sender` are the same (self-transfer), set the nonce to `0`. ## Create public authwits[​](#create-public-authwits "Direct link to Create public authwits") Public authwits require a transaction to store the authorization in the `AuthRegistry` contract before the authorized action can be executed: public\_authwit ``` // Alice wants to allow Bob to transfer tokens from her account (public) const publicNonce = Fr.random(); // Define the action Bob will execute const publicAction = tokenContract.methods.transfer_in_public( aliceAddress, // from bobAddress, // to 100n, // amount publicNonce, // authwit nonce ); // Alice sets the public authwit (this requires a transaction) const authwit = await SetPublicAuthwitContractInteraction.create( wallet, aliceAddress, { caller: bobAddress, action: publicAction }, true, // authorized ); await authwit.send(); // Now Bob can execute the transfer await publicAction.send({ from: bobAddress }); ``` > [Source code: docs/examples/ts/aztecjs\_authwit/index.ts#L73-L96](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_authwit/index.ts#L73-L96) ## Create arbitrary message authwits[​](#create-arbitrary-message-authwits "Direct link to Create arbitrary message authwits") Use this when authorizing arbitrary data rather than a specific contract function call: arbitrary\_authwit ``` import { computeInnerAuthWitHash } from "@aztec/aztec.js/authorization"; // Create hash of arbitrary data const innerHash = await computeInnerAuthWitHash([ Fr.fromHexString("0xcafe"), Fr.fromHexString("0xbeef"), ]); // Create an intent with the consumer contract address const intent = { consumer: tokenContract.address, innerHash, }; // Create the authwit for arbitrary data const arbitraryWitness = await wallet.createAuthWit(aliceAddress, intent); console.log("Arbitrary authwit created:", arbitraryWitness); ``` > [Source code: docs/examples/ts/aztecjs\_authwit/index.ts#L98-L116](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_authwit/index.ts#L98-L116) The `consumer` is the contract address that will verify this authwit. ## Revoke public authwits[​](#revoke-public-authwits "Direct link to Revoke public authwits") Public authwits can be revoked by setting `authorized` to `false`: revoke\_authwit ``` // Revoke a public authwit by setting authorized to false const revokeNonce = Fr.random(); const revokeAction = tokenContract.methods.transfer_in_public( aliceAddress, bobAddress, 50n, revokeNonce, ); // First, set the authwit const setAuthwit = await SetPublicAuthwitContractInteraction.create( wallet, aliceAddress, { caller: bobAddress, action: revokeAction }, true, ); await setAuthwit.send(); // Later, revoke it const revokeInteraction = await SetPublicAuthwitContractInteraction.create( wallet, aliceAddress, { caller: bobAddress, action: revokeAction }, false, // revoke authorization ); await revokeInteraction.send(); ``` > [Source code: docs/examples/ts/aztecjs\_authwit/index.ts#L118-L145](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_authwit/index.ts#L118-L145) ## Next steps[​](#next-steps "Direct link to Next steps") * Learn about [authwits in smart contracts](/developers/docs/aztec-nr/framework-description/authentication_witnesses.md) * Understand [authwit concepts](/developers/docs/foundational-topics/advanced/authwit.md) * Explore [account abstraction](/developers/docs/foundational-topics/accounts.md) --- # Pay Fees Privately This guide explains how private fee payment works on Aztec and walks through a concrete example. A fully private FPC can pay transaction fees without revealing the payer: it has no public functions, no owner, and no offchain agent. Because the contract is fully private, **no onchain deployment transaction is required**. Every app just derives the address deterministically from the class hash and a shared salt, and users interact with it privately. To illustrate the pattern, this guide uses [`PrivateFPC`](https://github.com/defi-wonderland/aztec-fee-payment), a community-built implementation by [Wonderland](https://github.com/defi-wonderland). You could write your own private FPC following the same design principles. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * [Connected to a network](how_to_connect_to_local_network) with an `EmbeddedWallet` instance and funded accounts * Familiarity with [fee concepts](/developers/docs/foundational-topics/fees.md) and [Paying Fees](/developers/docs/aztec-js/how_to_pay_fees.md) info The fee asset is only transferrable within a block to the current sequencer, as it powers the fee abstraction mechanism on Aztec. The asset is not transferable beyond this to ensure credible neutrality between all third party developer made asset portals and to ensure local compliance rules can be followed. ## Why a fully private FPC?[​](#why-a-fully-private-fpc "Direct link to Why a fully private FPC?") On Aztec, the transaction's setup phase is non-revertible, and a protocol-level allowlist controls which public function calls are permitted during it. Public token functions (like `transfer_in_public` and `_increase_public_balance`) have been removed from the default allowlist; custom FPCs may only call protocol-contract setup functions like those on `AuthRegistry` and `FeeJuice`. The reference [`FPC` contract](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/fees/fpc_contract/src/main.nr) collects user payment during setup by calling those token functions, so its flow is now rejected on public networks. The `PrivateFeePaymentMethod` shipped in `@aztec/aztec.js/fee` (which targets that contract) is therefore deprecated. See the [migration note](/developers/docs/resources/migration_notes.md#custom-token-fpcs-removed-from-default-public-setup-allowlist) for details. A fully private FPC side-steps the allowlist entirely. Instead of collecting payment from the user during setup, it holds Fee Juice as its own internal private balance (funded earlier by the user through the Fee Juice portal). When a transaction runs, the FPC just verifies that a **Fee Juice claim nullifier** exists in the nullifier tree (proof that the L1 deposit was consumed on L2) and deducts from its private note-based balance. No public cross-contract token calls happen during setup, so the allowlist never blocks anything. ## How a private FPC works[​](#how-a-private-fpc-works "Direct link to How a private FPC works") This section describes the design pattern using Wonderland's `PrivateFPC` as an example. The contract stores an internal, note-based `BalanceSet` of Fee Juice per user. There is no constructor, no admin, and no public surface. ### Two salts, not one[​](#two-salts-not-one "Direct link to Two salts, not one") Two different salt values show up in this flow; it's worth naming them up front so they don't get confused: * **Deployment salt.** Used to derive the FPC's contract address. Once a community agrees on the bytecode and this salt, everyone can derive the same address locally without an onchain deployment tx. The convention for Wonderland's `PrivateFPC` is `Fr.ZERO` (see [Recommended salt](#recommended-salt-0)). * **Bridge salt.** A random value the user chooses per L1 deposit. Combined with the user's Aztec address, it derives the *bridge secret* (`secret = poseidon2([salt, claimer], DOM_SEP__FPC_BRIDGE_SECRET)`), whose hash is passed as the `secretHash` on the L1 deposit. Only the user knows the preimage, so only the user can later produce the `secret` that `FeeJuice.claim` requires to consume the L1-to-L2 message. `PrivateFPC.mint(amount, salt, leaf_index)` and `PrivateFPC.mint_and_pay_fee(amount, salt, leaf_index)` take the **bridge** salt (along with the leaf index and the user's claimer address, which is `msg_sender`) to reconstruct the Fee Juice claim nullifier and verify the bridge was consumed. ### Two flows[​](#two-flows "Direct link to Two flows") 1. **Bridge + mint + pay** (run once to seed the user's private Fee Juice balance inside the FPC, and run again each time that balance runs low and the user wants to add more by bridging another deposit from L1): 1. **L1 deposit.** Call `FeeJuicePortal.depositToAztecPublic(_to = fpcAddress, _amount = amount, _secretHash = computeSecretHash(bridgeSecret))` where `bridgeSecret = poseidon2([bridgeSalt, claimer], DOM_SEP__FPC_BRIDGE_SECRET)`. The FPC is the *recipient* of the deposit, the user is the *claimer*. 2. **L2 claim.** In a normal L2 transaction, call `FeeJuice.claim(fpcAddress, amount, bridgeSecret, leafIndex)` directly. This consumes the L1-to-L2 message, credits Fee Juice to the FPC's **public** Fee Juice balance, and emits the claim nullifier. The fee for this transaction is paid by whatever mechanism the user normally uses (their own Fee Juice, `FeeJuicePaymentMethodWithClaim` on a *separate* bridge they control, the Sponsored FPC on devnet/testnet, and so on). The `PrivateFPC` does *not* sponsor this call, because at this point the user has no balance with it yet. 3. **Mint.** In a follow-up L2 transaction, call `PrivateFPC.mint(amount, bridgeSalt, leafIndex)` (again paid by whatever mechanism the user normally uses). `mint` does **not** call `FeeJuice.claim` again, because the claim already happened in step 1.2. The contract recomputes the same nullifier value that the earlier `claim` emitted (possible because the user supplies the `bridgeSalt` that originally produced it), asserts that nullifier exists in the nullifier tree as proof the L1 deposit was consumed, emits its own FPC-scoped nullifier to prevent double-minting the same bridge credit, and credits `amount` to the claimer's private balance inside the FPC. 4. **Pay.** From that point on, the user can pass `new FPCFeePaymentMethod(fpcAddress)` as the payment method on any transaction. Under the hood, the method calls `PrivateFPC.pay_fee()` in setup, which deducts `max_gas_cost` from the user's private balance and makes the FPC the fee payer. 2. **Cold-start** (single-transaction equivalent of steps 2–4 above, for first-time users who have only done the L1 deposit): 1. **L1 deposit.** Same as step 1.1 above. 2. **Single L2 transaction.** Pass `new PrivateMintAndPayFeePaymentMethod(fpcAddress, amount, bridgeSecret, bridgeSalt, leafIndex)` as the payment method on the user's first real transaction. The SDK bundles two calls into the setup phase of that single transaction: * `FeeJuice.claim(fpcAddress, amount, bridgeSecret, leafIndex)`: consumes the L1-to-L2 message, crediting Fee Juice to the FPC and emitting the claim nullifier (pending within the same tx). * `PrivateFPC.mint_and_pay_fee(amount, bridgeSalt, leafIndex)`: asserts the (pending) claim nullifier, credits `amount - max_gas_cost` to the user's private balance in the FPC, and marks the FPC as fee payer. The bridged amount itself funds this transaction's fee, so the user doesn't need prior Fee Juice or a sponsor to bootstrap. Any remaining credit (`amount - max_gas_cost`) is available for subsequent transactions via `FPCFeePaymentMethod`. Cold-start exists for users who have no other way to pay fees: the bridged amount itself funds that very first transaction, but `max_gas_cost` of it is consumed in the process. For top-ups (when the user already has another fee mechanism), the three-step `claim → mint → pay` path is preferable because it credits the full `amount` rather than `amount - max_gas_cost`, and it decouples the L1 bridge from the first app transaction (useful for privacy). For protocol details and the full API surface, see the [SDK README](https://github.com/defi-wonderland/aztec-fee-payment/blob/dev/src/ts/README.md) and [PRD](https://github.com/defi-wonderland/aztec-fee-payment/blob/dev/docs/private-product-requirements.md). Because neither `pay_fee` nor `mint_and_pay_fee` makes public cross-contract token calls in setup (they only deduct from the FPC's internal private balance and invoke `set_as_fee_payer`), the [setup-phase allowlist](/developers/docs/foundational-topics/transactions.md#setup-phase-non-revertible) never blocks these flows. No refund `PrivateFPC.pay_fee()` deducts the full `max_gas_cost` and does not refund unused gas. Use `estimateGas` (see [Estimate mana costs](/developers/docs/aztec-js/how_to_pay_fees.md#estimate-mana-costs)) to right-size your limits. ## Share one FPC address across the ecosystem[​](#share-one-fpc-address-across-the-ecosystem "Direct link to Share one FPC address across the ecosystem") Privacy on Aztec comes from indistinguishability. Private calldata and user identities are hidden. What an observer sees of a private call is its onchain *footprint*: the number of nullifiers and note commitments it emits, any logs, and its public gas usage. They do not learn which contract or which function produced those. Any two transactions whose footprints match are indistinguishable, even if they originated from entirely different contracts or functions, so an anonymity set at the private layer can span many unrelated contract–function pairs. Fee payments add one extra observable: the transaction's fee payer address, set via `set_as_fee_payer()`, is recorded onchain by the protocol. Every fee paid through a given FPC address is therefore publicly tagged with that address. If your app uses its own copy of the private FPC at a unique address, that tag distinguishes your users' fee payments from everyone else's. If every app derives the *same* FPC address and routes fees through it, every private fee payment in the ecosystem shares the same public fee-payer tag and joins a single, much larger shared set. This is the whole point of a fully private FPC. Because you don't have to deploy it on L2, there is no race to "be the deployer": the only thing that matters is that everyone agrees on the address. ## Recommended salt: `0`[​](#recommended-salt-0 "Direct link to recommended-salt-0") Two parties derive the same contract address if and only if they use the same compiled artifact and the same deployment salt. For any fully private FPC, using a common salt maximizes the shared privacy set. The community convention for Wonderland's `PrivateFPC` is `Fr.ZERO`. This is a convention, not a protocol-enforced default. It is up to each developer to pass the salt when registering the contract with their PXE, just as they choose any other deployment parameter. Following the convention means your users' private fee payments join the same privacy set as every other app that follows it. Version-specific addresses The `PrivateFPC` address depends on the compiled contract bytecode. A different Aztec version produces different bytecode and therefore a **different address**. Sending Fee Juice to the wrong address means **unrecoverable loss**. Before using a derived address on a given network, verify the network runs the same Aztec version as the Wonderland SDK version you have installed. ## Example: pay fees with Wonderland's `PrivateFPC`[​](#example-pay-fees-with-wonderlands-privatefpc "Direct link to example-pay-fees-with-wonderlands-privatefpc") The SDK exports two payment methods plus a `registerPrivateContract` helper that registers the FPC with your PXE using the shared deployment salt, with no deployment transaction needed: * `new FPCFeePaymentMethod(fpcAddress)`: for users who already have a private balance in the FPC. Wraps `PrivateFPC.pay_fee()`. * `new PrivateMintAndPayFeePaymentMethod(fpcAddress, amount, bridgeSecret, bridgeSalt, leafIndex)`: for cold-start. Bundles `FeeJuice.claim` and `PrivateFPC.mint_and_pay_fee` into the setup phase of a single transaction. For installation, the complete bridge-claim-mint-pay flow, required `send()` options (including `additionalScopes` and `gasSettings`), and a runnable end-to-end example, see the [SDK README](https://github.com/defi-wonderland/aztec-fee-payment/blob/dev/src/ts/README.md) and the [integration test](https://github.com/defi-wonderland/aztec-fee-payment/blob/dev/src/ts/test/private.test.ts). Transaction behavior | Scenario | Status | Execution result | Fee paid? | | -------------- | --------------------------------- | ---------------- | -------------- | | Private revert | `DROPPED` (not included in block) | N/A | No | | Public revert | `PROPOSED` | `REVERTED` | Yes (FPC pays) | | Success | `PROPOSED` | `SUCCESS` | Yes (FPC pays) | ## Reference implementation[​](#reference-implementation "Direct link to Reference implementation") Wonderland's repository ships detailed documentation for this design and its security properties: * [Private FPC Product Requirements](https://github.com/defi-wonderland/aztec-fee-payment/blob/dev/docs/private-product-requirements.md): problem statement, requirements matrix, cryptographic design (secret derivation, nullifier reconstruction, double-spend prevention), and security properties * [`PrivateFPC` Noir source](https://github.com/defi-wonderland/aztec-fee-payment/blob/dev/src/nr/private_contract/src/main.nr): the contract itself, annotated with the full bridge-to-mint-to-pay flow * [`src/ts/README.md`](https://github.com/defi-wonderland/aztec-fee-payment/blob/dev/src/ts/README.md): SDK reference with every exported class and utility * [Integration test `private.test.ts`](https://github.com/defi-wonderland/aztec-fee-payment/blob/dev/src/ts/test/private.test.ts): canonical end-to-end example of the bridge, claim, mint, sponsor flow ## Next steps[​](#next-steps "Direct link to Next steps") * Learn about [fee concepts](/developers/docs/foundational-topics/fees.md) in detail * Review the other [fee payment methods](/developers/docs/aztec-js/how_to_pay_fees.md) available in `aztec.js` * Browse Wonderland's [`aztec-fee-payment`](https://github.com/defi-wonderland/aztec-fee-payment) repository for the Noir source, TypeScript SDK, and integration examples --- # TypeScript API Reference This section provides API reference documentation for the Aztec TypeScript packages. These packages enable developers to build applications on Aztec, from simple contract interactions to complex privacy-preserving protocols. ## Package Categories[​](#package-categories "Direct link to Package Categories") ### Client SDKs[​](#client-sdks "Direct link to Client SDKs") Packages for building Aztec applications: | Package | Description | | ---------------------- | --------------------------------------------------------------------------------------------------------------------- | | **@aztec/aztec.js** | Main SDK for building Aztec applications. Provides contract deployment, transaction creation, and account management. | | **@aztec/accounts** | Sample account contract implementations including ECDSA and Schnorr accounts. | | **@aztec/pxe** | Private eXecution Environment client library for orchestrating private transaction execution and proving. | | **@aztec/wallet-sdk** | Wallet SDK for browser and extension integrations. | | **@aztec/wallets** | Embedded wallet for browser and Node.js environments. | | **@aztec/entrypoints** | Transaction entrypoint implementations for account abstraction. | ### Core Libraries[​](#core-libraries "Direct link to Core Libraries") Foundational types and utilities used across the Aztec stack: | Package | Description | | --------------------- | -------------------------------------------------------------------------------------- | | **@aztec/stdlib** | Protocol-level types including transactions, blocks, proofs, and kernel circuit types. | | **@aztec/foundation** | Low-level utilities including crypto primitives, serialization, and async helpers. | | **@aztec/constants** | Protocol constants shared between TypeScript and Noir circuits. | note Common types like `Fr`, `AztecAddress`, and `EthAddress` are re-exported through `@aztec/aztec.js` subpaths (e.g., `@aztec/aztec.js/fields`, `@aztec/aztec.js/addresses`). Most developers won't need to import from `@aztec/stdlib` directly. ## LLM-Optimized Documentation[​](#llm-optimized-documentation "Direct link to LLM-Optimized Documentation") For LLM consumption, we provide machine-readable documentation in multiple formats: * **[llms.txt](/llms.txt)** - Full documentation optimized for LLM context * [**LLM Summary**](/typescript-api/mainnet/llm-summary.txt) - Human-readable API summary ### Markdown API Files[​](#markdown-api-files "Direct link to Markdown API Files") The following markdown files are available for LLM context inclusion at `/typescript-api/mainnet/`: | File | Description | | ------------------------------------------------------------- | ----------------------------------------------- | | [`llm-summary.txt`](/typescript-api/mainnet/llm-summary.txt) | Human-readable summary with package overview | | [`aztec.js.md`](/typescript-api/mainnet/aztec.js.md) | Main SDK - contracts, transactions, accounts | | [`accounts.md`](/typescript-api/mainnet/accounts.md) | Account implementations (ECDSA, Schnorr) | | [`pxe.md`](/typescript-api/mainnet/pxe.md) | Private execution environment client | | [`wallet-sdk.md`](/typescript-api/mainnet/wallet-sdk.md) | Browser/extension wallet integration | | [`wallets.md`](/typescript-api/mainnet/wallets.md) | Embedded wallet for browser and Node.js | | [`entrypoints.md`](/typescript-api/mainnet/entrypoints.md) | Transaction entrypoints for account abstraction | | [`stdlib.md`](/typescript-api/mainnet/stdlib.md) | Protocol types (transactions, blocks, proofs) | | [`foundation.md`](/typescript-api/mainnet/foundation.md) | Low-level utilities (crypto, serialization) | | [`constants.md`](/typescript-api/mainnet/constants.md) | Protocol constants for circuits | ## Related Resources[​](#related-resources "Direct link to Related Resources") * [Aztec.js Getting Started](/developers/docs/tutorials/js_tutorials/aztecjs-getting-started.md) * [GitHub: aztec-packages](https://github.com/AztecProtocol/aztec-packages/tree/v4.3.1/yarn-project) --- # Wallet SDK The `@aztec/wallet-sdk` package defines the protocol that dApps and wallet extensions use to talk to each other on Aztec. It is analogous to EIP-1193 on Ethereum, with built-in encryption and visual MITM verification. This section covers both sides of the integration: * [Connecting a dApp to a wallet](/developers/docs/aztec-js/wallet-sdk/dapp_integration.md): discovery, secure channel, capabilities, and using the `Wallet` proxy. * [Building a wallet extension](/developers/docs/aztec-js/wallet-sdk/wallet_integration.md): implementing discovery, sessions, message routing, and `BaseWallet`. For a complete API listing, see the [`@aztec/wallet-sdk` reference](/typescript-api/mainnet/wallet-sdk.md). ## What the SDK provides[​](#what-the-sdk-provides "Direct link to What the SDK provides") | Feature | Description | | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | | Wallet discovery | dApps broadcast via `window.postMessage`; extensions respond after user approval | | ECDH key exchange | P-256 ephemeral key pairs derive a shared secret per session | | AES-256-GCM encryption | All wallet method calls and responses are encrypted after key exchange | | Emoji verification | A 9-emoji grid (72-bit security) lets the user confirm there is no man-in-the-middle | | Capability permissions | dApps declare what they need; wallets grant or deny each capability | | Trusted origin reconnect | Wallets may remember origins they have approved before and streamline later reconnects (wallet-managed policy, not framework state) | | `BaseWallet` | Abstract class that wallet extensions extend to get `sendTx`, `simulateTx`, `batch`, and more | ## Architecture[​](#architecture "Direct link to Architecture") Discovery uses `window.postMessage` (unencrypted, public). When the user approves the request, the content script creates a `MessageChannel` and transfers one port to the page. The dApp and the content script share the encrypted channel directly; the content script relays payloads to the background service worker over `chrome.runtime`. Encryption keys live on the dApp and the background, so the content script and `chrome.runtime` only see ciphertext. ## Security model[​](#security-model "Direct link to Security model") The protocol has three phases with increasing trust. 1. **Discovery (public).** The dApp broadcasts a request. Extensions only respond after the user explicitly approves the connection in their extension popup. No cryptographic material is exchanged. 2. **Key exchange (unauthenticated ECDH).** Both sides generate ephemeral ECDH P-256 key pairs and derive a shared secret. The secret is expanded via HKDF into an AES-256-GCM encryption key and a separate HMAC key. All wallet messages exchanged from this point on are encrypted with AES-256-GCM. The exchange itself is not authenticated, though an attacker who relays it can sit in the middle until phase 3 catches them. 3. **Channel authentication via emoji verification.** The HMAC key produces a verification hash that both sides convert to the same 9-emoji grid. The user visually confirms the emojis match on the dApp and the wallet, which authenticates the encrypted channel out of band and defends against man-in-the-middle attacks. The SDK itself does not gate messages on this confirmation; if you want to defer sensitive operations until the user has matched the emojis, your wallet code must do so. ## Where to start[​](#where-to-start "Direct link to Where to start") * Building a dApp that needs to connect to a wallet → [Connecting a dApp to a wallet](/developers/docs/aztec-js/wallet-sdk/dapp_integration.md). * Building a browser extension wallet → [Building a wallet extension](/developers/docs/aztec-js/wallet-sdk/wallet_integration.md). * Need a quick conceptual primer on what wallets do → [Wallets foundational topic](/developers/docs/foundational-topics/wallets.md). --- # Connecting a dApp to a wallet This guide shows how a dApp connects to an Aztec wallet extension using `@aztec/wallet-sdk`. The flow is: discover wallets, establish a secure channel, verify with emojis, request capabilities, then use the wallet. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * A wallet extension installed in the browser that implements the Aztec wallet SDK protocol, for example [Azguard](https://azguardwallet.io/). * An Aztec node URL the wallet can connect to. Run one locally with the [getting started on local network](/developers/getting_started_on_local_network.md) guide, or use a public endpoint from [getting started on testnet](/developers/getting_started_on_testnet.md). ## Install[​](#install "Direct link to Install") ``` yarn add @aztec/wallet-sdk@4.3.1 @aztec/aztec.js@4.3.1 ``` Common imports: ``` import { WalletManager, type WalletProvider, type PendingConnection, } from '@aztec/wallet-sdk/manager'; import { hashToEmoji } from '@aztec/wallet-sdk/crypto'; import type { Wallet, AppCapabilities, GrantedAccountsCapability, } from '@aztec/aztec.js/wallet'; ``` ## Step 1: Discover wallets[​](#step-1-discover-wallets "Direct link to Step 1: Discover wallets") `WalletManager` is the dApp-side coordinator from `@aztec/wallet-sdk/manager` for finding wallet extensions and brokering the secure-channel handshake with the one the user picks. Configure it once per page load and reuse it for the rest of the flow. `WalletManager.configure()` returns a manager configured for one or more provider types. `getAvailableWallets()` broadcasts a discovery request and returns a `DiscoverySession` that streams `WalletProvider` instances as users approve the request inside each extension. `chainInfo` tells wallets which chain the dApp wants to connect to. Read it from the connected Aztec node so the values track whatever network the user is on: ``` import { Fr } from '@aztec/aztec.js/fields'; import { createAztecNodeClient } from '@aztec/aztec.js/node'; const node = await createAztecNodeClient('http://localhost:8080'); const { l1ChainId, rollupVersion } = await node.getNodeInfo(); const manager = WalletManager.configure({ extensions: { enabled: true }, }); const providers: WalletProvider[] = []; const discovery = manager.getAvailableWallets({ appId: 'my-app', chainInfo: { chainId: new Fr(l1ChainId), version: new Fr(rollupVersion), }, onWalletDiscovered: (provider) => { providers.push(provider); renderWalletPicker(providers); // your UI hook }, }); ``` Wallets are streamed via `onWalletDiscovered` as soon as the user approves the request in each extension; do not block on `discovery.done` before showing options. The default discovery timeout is 60 seconds, but you should let the user pick as soon as the first acceptable wallet arrives: ``` async function onUserPicked(provider: WalletProvider) { discovery.cancel(); // proceed to Step 2 with this provider } ``` `discovery.done` is still useful if you want to wait for the timeout to elapse before declaring "no wallets found." Call `discovery.cancel()` once a wallet is selected or the user gives up. The `extensions` config also accepts an optional `allowList` and `blockList` of wallet IDs to constrain which wallets you will accept. Chain matching is wallet policy, not enforced by `WalletManager`. The SDK passes `chainInfo` through the discovery message, but it is up to each wallet to inspect it and decline (for example, by refusing approval in its popup) when the user's selected network does not match. Treat any wallet that responds as a candidate and re-check via `wallet.getChainInfo()` after the connection is established if you need a strong guarantee. To discover web/iframe wallets alongside extensions, pass a `webWallets` block too. Both kinds of provider are returned in the same `DiscoverySession`: ``` WalletManager.configure({ extensions: { enabled: true }, webWallets: { urls: ['https://wallet.example.com'] }, }); ``` Each `WalletProvider` has `id`, `name`, `icon`, optional `metadata`, and the methods used in the next steps. For extension wallets, a `WalletProvider` is only emitted after the user explicitly approves the request inside the extension popup, so websites cannot silently enumerate which extension wallets the user has installed. Web/iframe wallets respond to the discovery probe automatically without a popup, so a `WalletProvider` from `webWallets` does not imply user approval; only the per-connection emoji step in Step 3 authenticates the wallet to the user. The session itself resolves when discovery times out or you call `discovery.cancel()`; a wallet that rejects the request is silent on the dApp side. ## Step 2: Establish a secure channel[​](#step-2-establish-a-secure-channel "Direct link to Step 2: Establish a secure channel") Once the user picks a provider (the `provider` argument passed to `onUserPicked` above), perform the ECDH key exchange: ``` const pending: PendingConnection = await provider.establishSecureChannel('my-app'); const verificationEmojis = hashToEmoji(pending.verificationHash); // Display verificationEmojis to the user. They must match the grid the wallet shows. ``` `establishSecureChannel` returns a `PendingConnection` with: * `verificationHash`: hex string both sides compute independently. * `confirm()`: finalizes the connection and returns a `Wallet` proxy. * `cancel()`: aborts the pending connection. `establishSecureChannel` rejects if key exchange times out (default 2 seconds) or the wallet never responds. Treat any rejection as a hard reset: call `pending.cancel()` if you held onto the value, drop any `verificationHash` you displayed, and let the user retry from Step 1. ## Step 3: Emoji verification[​](#step-3-emoji-verification "Direct link to Step 3: Emoji verification") The emoji grid is how the user confirms the dApp and the wallet completed the key exchange directly with each other, and not with an attacker in the middle. Both sides derive the grid deterministically from the shared secret, so matching emojis on the dApp screen and the wallet popup mean no third party intercepted the handshake. A mismatch means the channel is compromised and must be torn down. Both the dApp and the wallet derive the same 9-emoji grid from the verification hash. Show the emojis prominently and let the user confirm they match before calling `confirm()`: ``` const wallet: Wallet = await pending.confirm(); ``` If the user reports they do not match, call `pending.cancel()` and start over. Some wallets choose to remember origins they have approved before, and may streamline the second connection by skipping the emoji step. That is wallet policy, not part of the protocol; the dApp uses the same flow either way. ## Step 4: Request capabilities[​](#step-4-request-capabilities "Direct link to Step 4: Request capabilities") After `confirm()` you have a `Wallet` proxy. The SDK does not enforce any permission check on it; capability negotiation is a wallet-policy convention. To play well with wallets that enforce capabilities, build an `AppCapabilities` manifest describing what your app needs and pass it to `requestCapabilities()` before calling privileged methods: ``` const manifest: AppCapabilities = { version: '1.0', metadata: { name: 'My App', version: '0.1.0', description: 'Example dApp', url: 'https://example.com', }, capabilities: [ { type: 'accounts', canGet: true, canCreateAuthWit: true }, { type: 'simulation', transactions: { scope: '*' }, utilities: { scope: '*' } }, { type: 'transaction', scope: '*' }, ], }; const result = await wallet.requestCapabilities(manifest); ``` The wallet returns a `WalletCapabilities` object whose `granted` array lists the capabilities the user approved. Capabilities not in `granted` are implicitly denied. | Capability `type` | Grants | Key fields | | ----------------- | ------------------------------------------------- | ------------------------------------------------------------------- | | `accounts` | Reading accounts, creating auth witnesses | `canGet`, `canCreateAuthWit` | | `contracts` | Registering contracts, querying contract metadata | `contracts: '*' \| AztecAddress[]`, `canRegister`, `canGetMetadata` | | `contractClasses` | Querying contract class metadata | `classes: '*' \| Fr[]`, `canGetMetadata` | | `simulation` | Simulating transactions and utility calls | `transactions.scope`, `utilities.scope` | | `transaction` | Sending state-changing transactions | `scope: '*' \| ContractFunctionPattern[]` | | `data` | Reading address book and private events | `addressBook`, `privateEvents.contracts` | The wallet decides which accounts to share. Pull the granted account list from the capability response. This is the recommended way to get the connected accounts at connection time. `wallet.getAccounts()` is for later reads against an already-connected wallet: ``` const accountsCap = result.granted.find( (c): c is GrantedAccountsCapability => c.type === 'accounts', ); if (!accountsCap?.accounts?.length) { throw new Error('No accounts granted by wallet'); } const account = accountsCap.accounts[0]; ``` ## Step 5: Use the wallet[​](#step-5-use-the-wallet "Direct link to Step 5: Use the wallet") The `Wallet` instance behaves like any other wallet. Every method call is encrypted in transit, but whether a given call is allowed is up to the wallet, based on what it granted (or did not grant) in step 4: ``` const accounts = await wallet.getAccounts(); await wallet.registerContract(contractInstance, contractArtifact); const receipt = await wallet.sendTx(executionPayload, { from: account.item }); const simulation = await wallet.simulateTx(executionPayload, { from: account.item }); const authWit = await wallet.createAuthWit(account.item, intent); const results = await wallet.batch([ { name: 'simulateTx', args: [payload1, opts1] }, { name: 'simulateTx', args: [payload2, opts2] }, ]); ``` ## Disconnect handling[​](#disconnect-handling "Direct link to Disconnect handling") A wallet can disconnect unexpectedly: the extension might be unloaded, or the user may disconnect from the popup. The `WalletProvider` returned by discovery exposes the disconnect API. Keep the `provider` reference around after `confirm()` so you can register callbacks on it: ``` const unsubscribe = provider.onDisconnect(() => { // Show a reconnect prompt or fall back to a different wallet. }); if (provider.isDisconnected()) { // Handle the disconnected state. } await provider.disconnect(); ``` ## Reference[​](#reference "Direct link to Reference") * API reference: [`@aztec/wallet-sdk` reference](/typescript-api/mainnet/wallet-sdk.md), [`@aztec/aztec.js/wallet` reference](/typescript-api/mainnet/aztec.js.md). * Capability type definitions: [`yarn-project/aztec.js/src/wallet/capabilities.ts`](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/yarn-project/aztec.js/src/wallet/capabilities.ts) at v4.3.1. * `WalletManager` source: [`yarn-project/wallet-sdk/src/manager/wallet_manager.ts`](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/yarn-project/wallet-sdk/src/manager/wallet_manager.ts) at v4.3.1. * Reference wallet implementation: [`AztecProtocol/demo-wallet`](https://github.com/AztecProtocol/demo-wallet). End-to-end open-source wallet showing the other side of the discovery, key exchange, and capability flows described above. --- # Building a wallet extension This page is a reference for wallet extension developers. It walks through each piece of the integration without prescribing a full project layout. For the canonical source you can browse alongside this guide, see [`yarn-project/wallet-sdk/`](https://github.com/AztecProtocol/aztec-packages/tree/v4.3.1/yarn-project/wallet-sdk) at v4.3.1. ## Components[​](#components "Direct link to Components") A typical Manifest V3 extension wallet has three components that use the SDK: | Component | SDK class | Responsibility | | ---------------------------------------- | -------------------------------- | ------------------------------------------------------- | | Content script | `ContentScriptConnectionHandler` | Relay messages between the page and the background | | Background service worker | `BackgroundConnectionHandler` | Manage sessions, route messages, trigger user approvals | | Wallet host (e.g. an offscreen document) | `BaseWallet` subclass | Execute wallet methods (`sendTx`, `simulateTx`, etc.) | A Manifest V3 service worker has a 5-minute inactivity timeout and limited WASM support. PXE state and proof generation typically live in an offscreen document so they survive longer than the service worker. ## Install[​](#install "Direct link to Install") ``` yarn add @aztec/wallet-sdk@4.3.1 @aztec/aztec.js@4.3.1 @aztec/pxe@4.3.1 ``` ## Content script[​](#content-script "Direct link to Content script") The content script never sees encryption keys. Construct a `ContentScriptConnectionHandler` with a transport that knows how to talk to your background service worker, then call `start()`: ``` import { ContentScriptConnectionHandler } from '@aztec/wallet-sdk/extension/handlers'; const handler = new ContentScriptConnectionHandler({ sendToBackground: (message) => chrome.runtime.sendMessage(message), addBackgroundListener: (listener) => chrome.runtime.onMessage.addListener(listener), }); handler.start(); ``` ## Background service worker[​](#background-service-worker "Direct link to Background service worker") The background script is where most of the SDK integration lives. Construct a `BackgroundConnectionHandler` with your wallet's identity, a transport, and a set of callbacks: ``` import { BackgroundConnectionHandler } from '@aztec/wallet-sdk/extension/handlers'; const WALLET_CONFIG = { walletId: 'my-wallet', walletName: 'My Aztec Wallet', walletVersion: '1.0.0', // walletIcon is optional. Omit it if you don't have one; dApps render a fallback. walletIcon: 'data:image/png;base64,...', }; const transport = { sendToTab: (tabId, message) => chrome.tabs.sendMessage(tabId, message), addContentListener: (handler) => chrome.runtime.onMessage.addListener(handler), }; const handler = new BackgroundConnectionHandler(WALLET_CONFIG, transport, callbacks); handler.initialize(); ``` Chain selection is per-session, not per-wallet. The dApp passes its target `chainInfo` in the discovery request, and `BackgroundConnectionHandler` exposes it to your callbacks via `PendingDiscovery.chainInfo`. ### Callbacks[​](#callbacks "Direct link to Callbacks") The handler exposes four optional callbacks at different protocol stages. #### `onPendingDiscovery`[​](#onpendingdiscovery "Direct link to onpendingdiscovery") Fired when a dApp broadcasts a discovery request. Decide whether to auto-approve (trusted origin) or open the popup so the user can approve: ``` const callbacks = { async onPendingDiscovery({ requestId, appId, origin, tabId, chainInfo }) { // Optional: terminate stale sessions from the same tab on page refresh. for (const s of handler.getActiveSessions()) { if (s.tabId === tabId) handler.terminateSession(s.sessionId); } if (await isTrustedOrigin({ appId, origin, chainInfo })) { handler.approveDiscovery(requestId); } else { await openApprovalPopup({ requestId, appId, origin, chainInfo }); // The popup later calls handler.approveDiscovery(requestId) // or handler.rejectDiscovery(requestId). } }, // onSessionEstablished, onWalletMessage, onSessionTerminated below. }; ``` #### `onSessionEstablished`[​](#onsessionestablished "Direct link to onsessionestablished") Fires after ECDH key exchange completes. The session has a `verificationHash` for the emoji grid. The SDK does not expose a "confirm" call on the wallet side, so what your wallet does at this point is pure policy: * For trusted origins: mark the session as trusted in your own state and restore previously granted capabilities. Persistence and policy are entirely up to your wallet code. * For new origins: stash the session and show the emoji grid in the popup so the user can match it against the dApp before you treat any incoming message as authorized. #### `onWalletMessage`[​](#onwalletmessage "Direct link to onwalletmessage") Fires when a dApp sends an encrypted wallet method call. The handler decrypts the payload and immediately invokes your callback. The SDK does not buffer messages by verification state; if you want to defer messages that arrive before the user confirms the emojis, queue them yourself in your callback. #### `onSessionTerminated`[​](#onsessionterminated "Direct link to onsessionterminated") Fires when a session ends (tab closed, user disconnects, etc.). Use it to clean up extension state. ### Routing wallet calls[​](#routing-wallet-calls "Direct link to Routing wallet calls") For every incoming `onWalletMessage`, decide whether the call needs user approval, then either prompt the user or forward to your wallet implementation. `aztec.js` models privileged methods behind named capabilities (see `AppCapabilities` and `Capability` in `@aztec/aztec.js/wallet`). The expected pattern is: the dApp calls `requestCapabilities` once, your wallet shows the user a single combined dialog, and methods covered by an approved capability can run without further prompting. The matrix below assumes you follow that pattern. | Method | Approval policy | Capability gating the method | | ----------------------------------------- | --------------------------------------------------------------------- | ---------------------------------------- | | `requestCapabilities` (first time) | Per-call: prompt the user with the full manifest | n/a (grants the capabilities themselves) | | `sendTx` | Per-call confirmation, on top of an approved `transaction` capability | `transaction` | | `batch` | Treat as the union of its inner methods | derived from inner methods | | `simulateTx`, `profileTx` | Run after an approved `simulation` (with a `transactions` scope) | `simulation.transactions` | | `executeUtility` | Run after an approved `simulation` (with a `utilities` scope) | `simulation.utilities` | | `getAccounts`, `createAuthWit` | Run after an approved `accounts` capability | `accounts` | | `registerContract`, `getContractMetadata` | Run after an approved `contracts` capability | `contracts` | | `getContractClassMetadata` | Run after an approved `contractClasses` capability | `contractClasses` | | `getAddressBook`, `getPrivateEvents` | Run after an approved `data` capability | `data` | The SDK does not enforce any of this — `requestCapabilities` in `BaseWallet` throws "Not implemented" by default, and capability persistence and per-call policy are entirely your wallet's responsibility. A wallet is free to require an extra confirmation for sensitive methods even when a capability is granted (for example, every `sendTx`), or to skip prompts for trusted origins it has remembered. Once you decide a call may proceed, forward it to your wallet host and reply with `handler.sendResponse()`: ``` await handler.sendResponse(session.sessionId, { messageId: message.messageId, result: someResult, walletId: WALLET_CONFIG.walletId, }); ``` For errors, send an error response in the same shape, but with `error` instead of `result`: ``` await handler.sendResponse(session.sessionId, { messageId: message.messageId, error: 'Something went wrong', walletId: WALLET_CONFIG.walletId, }); ``` ## Extending `BaseWallet`[​](#extending-basewallet "Direct link to extending-basewallet") `BaseWallet` provides default implementations of `sendTx`, `simulateTx`, `batch`, `createAuthWit`, `registerContract`, `getChainInfo`, and others. Subclass it and supply the two account hooks: ``` import { BaseWallet, type CompleteFeeOptionsConfig } from '@aztec/wallet-sdk/base-wallet'; import type { Account } from '@aztec/aztec.js/account'; import type { Aliased } from '@aztec/aztec.js/wallet'; import type { AztecAddress } from '@aztec/aztec.js/addresses'; class MyWallet extends BaseWallet { protected async getAccountFromAddress(address: AztecAddress): Promise { // Look up the Account object for this address from your account store. } async getAccounts(): Promise[]> { // Return all accounts the wallet knows about, with aliases. } } ``` `completeFeeOptions(config: CompleteFeeOptionsConfig)` has a default that uses the sender's fee juice balance. Override it if you want to inject a custom fee payment strategy (for example, paying via a sponsored fee paying contract on every transaction): ``` protected async completeFeeOptions(config: CompleteFeeOptionsConfig) { const base = await super.completeFeeOptions(config); return { ...base, walletFeePaymentMethod: mySponsoredMethod, }; } ``` `requestCapabilities()` is part of the `Wallet` interface but throws "Not implemented" by default in `BaseWallet`. The SDK does not enforce capabilities for you, so a wallet that wants to support capability negotiation must override `requestCapabilities()` (or handle it inside the message-routing layer above) and decide what to grant. ## Session lifecycle[​](#session-lifecycle "Direct link to Session lifecycle") If you want returning users to skip the approval popup, your wallet code can persist the set of trusted origins. Use `chrome.storage.local` if the trust list should survive browser restarts, or `chrome.storage.session` if it should clear when the browser closes. The SDK does not store trust state for you. Be careful what you scope trust to. Origin alone is rarely enough: a wallet that auto-approves an `origin` for one chain shouldn't auto-approve the same origin on a different chain or under a different `appId`. Storing the tuple `(appId, origin, chainInfo.chainId, chainInfo.version)` and checking the full tuple in `onPendingDiscovery` keeps the auto-approve scoped tightly enough (`chainInfo.version` is the L2 rollup version the dApp passed in; the SDK forwards both fields to your callback unchanged). The handler exposes: * `handler.terminateSession(sessionId)`: end a specific session. * `handler.terminateForTab(tabId)`: end every session for a tab. * `handler.getPendingDiscoveries()`: list pending discovery requests. * `handler.getActiveSessions()`: list active sessions. The handler's in-memory state (active sessions, pending discoveries) does not survive service worker restarts. dApps will simply re-discover and reconnect; if your wallet recognizes the origin and skips the prompt, the reconnect feels seamless. On extension install or update, clear any persisted "pending" state since sessions never survive a reload. ## Reference[​](#reference "Direct link to Reference") * API reference: [`@aztec/wallet-sdk` reference](/typescript-api/mainnet/wallet-sdk.md). * `BaseWallet` source: [`yarn-project/wallet-sdk/src/base-wallet/base_wallet.ts`](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/yarn-project/wallet-sdk/src/base-wallet/base_wallet.ts) at v4.3.1. * `BackgroundConnectionHandler` source: [`yarn-project/wallet-sdk/src/extension/handlers/background_connection_handler.ts`](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/yarn-project/wallet-sdk/src/extension/handlers/background_connection_handler.ts) at v4.3.1. * Wallet SDK README at v4.3.1: [`yarn-project/wallet-sdk/README.md`](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/yarn-project/wallet-sdk/README.md). --- # Overview Aztec.nr is a Noir framework used to develop and test Aztec smart contracts. It contains both high-level abstractions (state variables, messages) and low-level protocol primitives, providing granular control to developers if they want custom contracts. tip If you are already familiar with writing Aztec smart contracts and Aztec.nr, visit the [API reference](/aztec-nr-api/mainnet/). ## Motivation[​](#motivation "Direct link to Motivation") Noir *can* be used to write circuits, but Aztec contracts are more complex than this. They include multiple external functions, each of a different type: circuits for private functions, AVM bytecode for public functions, and brillig bytecode for utility functions. The circuits for private functions also need to interact with the protocol's kernel circuits in specific ways, so manually writing them, and then combining everything into a contract artifact is involved work. Aztec.nr takes care of all of this heavy lifting and makes writing contracts as simple as marking functions with the corresponding attributes e.g. `#[external("private")]`. It allows safe and easy implementation of well understood design patterns, such as the multiple kinds of private state variables, meaning developers don't need to understand the low-levels of how the protocol works. These features are optional, however, advanced developers are not prevented from building their own custom solutions. ## Design principles[​](#design-principles "Direct link to Design principles") * Make it hard to shoot yourself in the foot by making it clear when something is unsafe. * Dangerous actions should be easy to spot. e.g. ignoring return values or calling functions with the `_unsafe` prefix. * This is achieved by having rails that intentionally trigger a developer's "WTF?" response, to ensure they understand what they're doing. A good example of this is writing to private state variables. These functions return a `NoteMessage` struct, which results in a compiler error unless used. This is because writing to private state also requires sending an encrypted message with the new state to the people that need to access it - otherwise, because it is private, they will not even know the state changed. ``` storage.votes.insert(new_vote); // compiler error - unused NoteMessage return value storage.votes.insert(new_vote).deliver(MessageDelivery.ONCHAIN_CONSTRAINED); // deliver the note message onchain ``` ## Contract Development[​](#contract-development "Direct link to Contract Development") ### Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Install [Aztec Local Network and Tooling](/developers/getting_started_on_local_network.md) * Install the [Noir VSCode Extension](/developers/docs/aztec-nr/installation.md) for syntax highlighting and error detection. ### Flow[​](#flow "Direct link to Flow") 1. Write your contract and specify your contract dependencies. Create a new project with `aztec new my_project`, which scaffolds a workspace with two crates: a `my_project_contract` crate for your contract and a `my_project_test` crate for tests, with the `aztec` dependency already configured. If you need additional dependencies, add them to `my_project_contract/Nargo.toml`: ``` # my_project_contract/Nargo.toml [dependencies] aztec = { git="https://github.com/AztecProtocol/aztec-nr/", tag="v4.3.1", directory="aztec" } ``` Update your `my_project_contract/src/main.nr` contract file to use the Aztec.nr macros for writing contracts. setup ``` use aztec::macros::aztec; #[aztec] pub contract Counter { ``` > [Source code: docs/examples/contracts/counter\_contract/src/main.nr#L1-L6](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/counter_contract/src/main.nr#L1-L6) and import dependencies from the Aztec.nr library. imports ``` use aztec::{ macros::{functions::{external, initializer}, storage::storage}, messages::message_delivery::MessageDelivery, oracle::logging::debug_log_format, protocol::{address::AztecAddress, traits::ToField}, state_vars::Owned, }; use balance_set::BalanceSet; ``` > [Source code: docs/examples/contracts/counter\_contract/src/main.nr#L7-L16](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/counter_contract/src/main.nr#L7-L16) info You can see a complete example of a simple counter contract written with Aztec.nr [here](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/counter_contract/src/main.nr). 2. [Profile](/developers/docs/aztec-nr/framework-description/advanced/how_to_profile_transactions.md) the private functions in your contract to get a sense of how long generating client side proofs will take 3. Write unit tests [directly in Noir](/developers/docs/aztec-nr/testing_contracts.md) and end-to-end tests [with TypeScript](/developers/docs/aztec-js/how_to_test.md) 4. [Compile](/developers/docs/aztec-nr/compiling_contracts.md) your contract 5. [Deploy](/developers/docs/aztec-js/how_to_deploy_contract.md) your contract with Aztec.js ## Section Contents[​](#section-contents "Direct link to Section Contents") ## [📄️Noir VSCode Extension](/developers/docs/aztec-nr/installation.md) [Learn how to install and configure the Noir Language Server for a better development experience.](/developers/docs/aztec-nr/installation.md) ## [📄️Compiling Contracts](/developers/docs/aztec-nr/compiling_contracts.md) [Compile your Aztec smart contracts into deployable artifacts using aztec command.](/developers/docs/aztec-nr/compiling_contracts.md) ## [📄️Contract Deployment Reference](/developers/docs/aztec-nr/contract_readiness_states.md) [A practical guide to determine which deployment steps your Aztec contract needs and when functions become callable.](/developers/docs/aztec-nr/contract_readiness_states.md) ## [📄️Debugging Aztec Code](/developers/docs/aztec-nr/debugging.md) [This guide shows you how to debug issues in your Aztec contracts.](/developers/docs/aztec-nr/debugging.md) ## [🗃Framework Description](/developers/docs/aztec-nr/framework-description/functions.md) [16 items](/developers/docs/aztec-nr/framework-description/functions.md) ## [📄️Testing Contracts](/developers/docs/aztec-nr/testing_contracts.md) [Write and run tests for your Aztec smart contracts using Noir's TestEnvironment.](/developers/docs/aztec-nr/testing_contracts.md) ## [🗃Standards](/developers/docs/aztec-nr/standards.md) [6 items](/developers/docs/aztec-nr/standards.md) ## [📄️Aztec.nr API Reference](/developers/docs/aztec-nr/api.md) [Auto-generated API reference documentation for the Aztec.nr smart contract framework.](/developers/docs/aztec-nr/api.md) --- # Aztec.nr API Reference The Aztec.nr API reference documentation is auto-generated from the source code using `nargo doc`. ## View the API Documentation[​](#view-the-api-documentation "Direct link to View the API Documentation") [**Aztec.nr**](/aztec-nr-api/mainnet/noir_aztec/index.html) The API reference includes documentation for all public modules, functions, structs, and types in the aztec-nr workspace: ### Core Crates[​](#core-crates "Direct link to Core Crates") * [**noir\_aztec**](/aztec-nr-api/mainnet/noir_aztec/index.html) - Core Aztec contract framework including: * [`context`](/aztec-nr-api/mainnet/noir_aztec/context/index.html) - Private and public execution contexts * [`state_vars`](/aztec-nr-api/mainnet/noir_aztec/state_vars/index.html) - State variable types (PrivateMutable, PublicMutable, Map, etc.) * [`note`](/aztec-nr-api/mainnet/noir_aztec/note/index.html) - Note interfaces and utilities * [`authwit`](/aztec-nr-api/mainnet/noir_aztec/authwit/index.html) - Authentication witness support * [`history`](/aztec-nr-api/mainnet/noir_aztec/history/index.html) - Historical state proofs * [`messages`](/aztec-nr-api/mainnet/noir_aztec/messages/index.html) - Cross-chain messaging * [`oracle`](/aztec-nr-api/mainnet/noir_aztec/oracle/index.html) - Oracle interfaces * [`macros`](/aztec-nr-api/mainnet/noir_aztec/macros/index.html) - Contract macros and attributes * [`hash`](/aztec-nr-api/mainnet/noir_aztec/hash/index.html) - Hash functions and utilities * [`keys`](/aztec-nr-api/mainnet/noir_aztec/keys/index.html) - Key management utilities * [`event`](/aztec-nr-api/mainnet/noir_aztec/event/index.html) - Event emission and interfaces * [`test`](/aztec-nr-api/mainnet/noir_aztec/test/index.html) - Testing utilities * [`utils`](/aztec-nr-api/mainnet/noir_aztec/utils/index.html) - General utilities ### Note Types[​](#note-types "Direct link to Note Types") * [**address\_note**](/aztec-nr-api/mainnet/address_note/index.html) - Note type for storing Aztec addresses * [**field\_note**](/aztec-nr-api/mainnet/field_note/index.html) - Note type for storing a single Field value * [**uint\_note**](/aztec-nr-api/mainnet/uint_note/index.html) - Note type for storing unsigned integers ### State Variables[​](#state-variables "Direct link to State Variables") * [**balance\_set**](/aztec-nr-api/mainnet/balance_set/index.html) - State variable for managing private balances ### Utilities[​](#utilities "Direct link to Utilities") * [**compressed\_string**](/aztec-nr-api/mainnet/compressed_string/index.html) - Compressed string utilities for efficient storage --- # Compiling Contracts This guide shows you how to compile your Aztec contracts into artifacts ready for deployment and interaction. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * An Aztec contract written in Aztec.nr * `aztec` installed * Contract project with proper `Nargo.toml` configuration ## Compile your contract[​](#compile-your-contract "Direct link to Compile your contract") Compile your Noir contracts to generate JSON artifacts: ``` aztec compile ``` This outputs contract artifacts to the `target` folder. ## Use generated interfaces[​](#use-generated-interfaces "Direct link to Use generated interfaces") The compiler automatically generates type-safe interfaces for contract interaction. ### Import and use contract interfaces[​](#import-and-use-contract-interfaces "Direct link to Import and use contract interfaces") Use generated interfaces instead of manual function calls: ``` contract MyContract { use token::Token; #[external("private")] fn transfer_tokens(token_address: AztecAddress, recipient: AztecAddress, amount: u128) { // Use the generated Token interface to call another contract self.call(Token::at(token_address).transfer(recipient, amount)); } #[external("private")] fn transfer_then_mint(token_address: AztecAddress, recipient: AztecAddress, amount: u128) { // Private call executed immediately self.call(Token::at(token_address).transfer(recipient, amount)); // Public call enqueued for later execution self.enqueue(Token::at(token_address).mint_to_public(recipient, amount)); } } ``` warning Do not import generated interfaces from the same project as the source contract to avoid circular references. ## Next steps[​](#next-steps "Direct link to Next steps") After compilation, use the generated artifacts to: * Deploy contracts with the `Contract` class from `aztec.js` * Interact with deployed contracts using type-safe interfaces * Import contracts in other Aztec.nr projects --- # Contract Deployment Reference This guide helps you quickly determine which deployment steps your contract needs. For conceptual background on how contract deployment works, see [Contract Deployment](/developers/docs/foundational-topics/contract_creation.md). ## What Do I Need to Do?[​](#what-do-i-need-to-do "Direct link to What Do I Need to Do?") Use this decision tree to determine which steps your contract needs. No initializer? If your contract has no `#[initializer]` function and was deployed with `without_initializer()`, it's considered initialized immediately. Skip the initialization checks above. ## Checking Contract State Programmatically[​](#checking-contract-state-programmatically "Direct link to Checking Contract State Programmatically") Use `wallet.getContractMetadata(contractAddress)` to check whether a contract is registered, published, and initialized. See [Verify deployment](/developers/docs/aztec-js/how_to_deploy_contract.md#verify-deployment) for usage examples and details on what the PXE checks automatically versus what you need to verify manually. ## When Can You Skip States?[​](#when-can-you-skip-states "Direct link to When Can You Skip States?") | Contract Type | Class Registration | Instance Creation | Initialization | Public Deployment | | ------------------------- | ------------------ | ----------------- | -------------- | ----------------- | | Private-only | Optional | Required | Depends | Skip | | Public-only | Required | Required | Depends | Required | | Hybrid (private + public) | Required | Required | Depends | Required | | Stateless helper | Optional | Required | Skip | Depends | "Depends" means it depends on whether your contract has a constructor marked with `#[initializer]`. ## When Functions Become Callable[​](#when-functions-become-callable "Direct link to When Functions Become Callable") | State | Private Functions | Public Functions | | ----------------------------------- | --------------------- | ---------------- | | Address computed only | With `#[noinitcheck]` | No | | Class registered | With `#[noinitcheck]` | No | | Instance deployed (not initialized) | With `#[noinitcheck]` | No | | Initialized | Yes | No | | Publicly deployed | Yes | Yes | Private functions marked with `#[noinitcheck]` can be called as soon as you know the address, even before initialization. This enables patterns like pre-funded accounts. Contracts without initializers If your contract has no initializer and is deployed with `without_initializer()`, it's considered initialized immediately. Private functions are callable right after instance creation without needing `#[noinitcheck]`. Public functions still require public deployment. ## Further Reading[​](#further-reading "Direct link to Further Reading") * [Contract Deployment](/developers/docs/foundational-topics/contract_creation.md) - Conceptual foundation of classes, instances, and lifecycle states * [Deploying Contracts](/developers/docs/aztec-js/how_to_deploy_contract.md) - TypeScript deployment guide * [Defining Initializer Functions](/developers/docs/aztec-nr/framework-description/functions/how_to_define_functions.md#define-initializer-functions) - How to use `#[initializer]` and `#[noinitcheck]` * [Communicating Cross-Chain](/developers/docs/aztec-nr/framework-description/ethereum_aztec_messaging.md) - Portal contracts and L1/L2 messaging --- # Debugging Aztec Code This guide shows you how to debug issues in your Aztec development environment. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Running Aztec local network * Aztec.nr contract or aztec.js application * Basic understanding of Aztec architecture ## Enable logging[​](#enable-logging "Direct link to Enable logging") Enable different levels of logging on the local network or node by setting `LOG_LEVEL`: ``` # Set log level (options: fatal, error, warn, info, verbose, debug, trace) LOG_LEVEL="debug; info: json-rpc, simulator" aztec start --local-network # Different levels for different services LOG_LEVEL="verbose;info:sequencer" aztec start --local-network ``` ## Logging in Aztec.nr contracts[​](#logging-in-aztecnr-contracts "Direct link to Logging in Aztec.nr contracts") Log values from your contract using `debug_log`: ``` // Import debug logging use dep::aztec::oracle::logging::{ debug_log, debug_log_format }; // Log simple messages debug_log("checkpoint reached"); // Log field values with context debug_log_format("slot:{0}, hash:{1}", [storage_slot, note_hash]); // Log a single value debug_log_format("my_field: {0}", [my_field]); // Log multiple values debug_log_format("values: {0}, {1}, {2}", [val1, val2, val3]); ``` note Debug logs appear only during local execution. Private functions always execute locally, but public functions must be simulated to show logs. Use `.simulate()` or `.prove()` in TypeScript, or `env.simulate_public_function()` in TXE tests. To see debug logs from your tests, set `LOG_LEVEL` when running: ``` LOG_LEVEL="debug" yarn run test ``` To filter specific modules, use a semicolon-delimited list: ``` LOG_LEVEL="info;debug:simulator:client_execution_context;debug:simulator:client_view_context" yarn run test ``` Log filter format `LOG_LEVEL` accepts a semicolon-delimited list of filters. Each filter can be: * `level` - Sets default level for all modules * `level:module` - Sets level for a specific module * `level:module:submodule` - Sets level for a specific submodule **The default-level filter must be the first segment.** A bare `level:module` with no preceding default (e.g. `LOG_LEVEL="warn:simulator"`) is invalid and throws `Invalid log level`, because the parser reads everything before the first `;` as the default level. To filter only specific modules, lead with a default level — use `silent` to suppress everything else. ``` # Default level only LOG_LEVEL="debug" # Default level + specific module overrides LOG_LEVEL="info;debug:simulator;debug:execution" # Default level + specific submodule overrides LOG_LEVEL="info;debug:simulator:client_execution_context;debug:simulator:client_view_context" # Silence everything except one module LOG_LEVEL="silent;debug:simulator" ``` ## Debugging common errors[​](#debugging-common-errors "Direct link to Debugging common errors") ### Contract Errors[​](#contract-errors "Direct link to Contract Errors") | Error | Solution | | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Aztec dependency not found` | Add to Nargo.toml: `aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v4.3.1", directory="noir-projects/aztec-nr/aztec" }` | | `Public state writes only supported in public functions` | Move state writes to public functions | | `Unknown contract 0x0` | Call `wallet.registerContract(...)` to register contract | | `No public key registered for address` | Call `wallet.registerSender(...)` | | `Direct invocation of ... functions is not supported` | Use `self.call()`, `self.view()`, or `self.enqueue()` to [call contract functions](/developers/docs/aztec-nr/framework-description/calling_contracts.md) | | `Failed to solve brillig function` | Check function parameters and note validity | | `Cross-contract utility call denied` | Configure an `authorizeUtilityCall` [execution hook](#cross-contract-utility-call-denied) on your PXE | #### Cross-contract utility call denied[​](#cross-contract-utility-call-denied "Direct link to Cross-contract utility call denied") When a contract executes a utility function that calls into a different contract, PXE asks an **execution hook** whether the call should be allowed. If no hook is configured, or the hook denies the request, you will see: ``` Cross-contract utility call denied: . attempted to call : (). ``` To fix this, pass an `authorizeUtilityCall` hook when creating your PXE: ``` import { PXE } from "@aztec/pxe/server"; const pxe = await PXE.create({ // ...other options hooks: { authorizeUtilityCall: async (request) => { // Inspect request.caller, request.target, request.functionSelector, etc. return { authorized: true }; }, }, }); ``` The hook receives a `UtilityCallAuthorizationRequest` with the caller address, target address, function selector, function name, arguments, and caller context (`'private'` or `'utility'`). Return `{ authorized: true }` to allow or `{ authorized: false, reason: '...' }` to deny with a message. ### Circuit Errors[​](#circuit-errors "Direct link to Circuit Errors") | Error Code | Meaning | Fix | | ----------- | ---------------------------- | -------------------------------------------------- | | `2002` | Invalid contract address | Ensure contract is deployed and address is correct | | `2005/2006` | Static call violations | Remove state modifications from static calls | | `2017` | User intent mismatch | Verify transaction parameters match function call | | `3001` | Unsupported operation | Check if operation is supported in current context | | `3005` | Non-empty private call stack | Ensure private functions complete before public | | `4007/4008` | Chain ID/version mismatch | Verify L1 chain ID and Aztec version | | `7008` | Membership check failed | Ensure using valid historical state | | `7009` | Array overflow | Reduce number of operations in transaction | ### Quick Fixes for Common Issues[​](#quick-fixes-for-common-issues "Direct link to Quick Fixes for Common Issues") ``` # Archiver sync issues - force progress with dummy transactions. # Assumes you have imported the local network test accounts # (aztec-wallet import-test-accounts) and have a deployed token # aliased as `testtoken`. aztec-wallet send transfer --from test0 --contract-address testtoken --args accounts:test0 0 aztec-wallet send transfer --from test0 --contract-address testtoken --args accounts:test0 0 # L1 to L2 message pending - wait for inclusion # Messages need 2 blocks to be processed ``` ## Debugging WASM errors[​](#debugging-wasm-errors "Direct link to Debugging WASM errors") ### Enable debug WASM[​](#enable-debug-wasm "Direct link to Enable debug WASM") ``` // In vite.config.ts or similar export default { define: { "process.env.BB_WASM_PATH": JSON.stringify("https://debug.wasm.url"), }, }; ``` ### Profile transactions[​](#profile-transactions "Direct link to Profile transactions") ``` import { serializePrivateExecutionSteps } from "@aztec/stdlib"; // Profile the transaction const profileTx = await contract.methods .myMethod(param1, param2) .profile({ profileMode: "execution-steps" }); // Serialize for debugging const ivcMessagePack = serializePrivateExecutionSteps(profileTx.executionSteps); // Download debug file const blob = new Blob([ivcMessagePack]); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = "debug-steps.msgpack"; link.click(); ``` ⚠️ **Warning:** Debug files may contain private data. Use only in development. ## Interpret error messages[​](#interpret-error-messages "Direct link to Interpret error messages") ### Circuit and protocol errors[​](#circuit-and-protocol-errors "Direct link to Circuit and protocol errors") * **Private kernel errors (2xxx)**: Issues with private function execution * **Public kernel errors (3xxx)**: Issues with public function execution * **Rollup errors (4xxx)**: Block production issues * **Generic errors (7xxx)**: Resource limits or state validation ### Transaction limits[​](#transaction-limits "Direct link to Transaction limits") Current limits that trigger `7009 - ARRAY_OVERFLOW`: * Max new notes per tx: Check `MAX_NOTE_HASHES_PER_TX` * Max nullifiers per tx: Check `MAX_NULLIFIERS_PER_TX` * Max function calls: Check call stack size limits * Max L2→L1 messages: Check message limits ## Debugging sequencer issues[​](#debugging-sequencer-issues "Direct link to Debugging sequencer issues") ### Common sequencer errors[​](#common-sequencer-errors "Direct link to Common sequencer errors") | Error | Cause | Solution | | ------------------------------------ | --------------------- | ------------------------------------------------ | | `tree root mismatch` | State inconsistency | Restart local network or check state transitions | | `next available leaf index mismatch` | Tree corruption | Verify tree updates are sequential | | `Public call stack size exceeded` | Too many public calls | Reduce public function calls | | `Failed to publish block` | L1 submission failed | Check L1 connection and gas | ## Reporting issues[​](#reporting-issues "Direct link to Reporting issues") When debugging fails: 1. Collect error messages and codes 2. Generate transaction profile (if applicable) 3. Note your environment setup 4. Create issue at [aztec-packages](https://github.com/AztecProtocol/aztec-packages/issues/new) ## Quick reference[​](#quick-reference "Direct link to Quick reference") ### Enable verbose logging[​](#enable-verbose-logging "Direct link to Enable verbose logging") ``` LOG_LEVEL=verbose aztec start --local-network ``` ### Common debug imports[​](#common-debug-imports "Direct link to Common debug imports") ``` use dep::aztec::oracle::logging::{ debug_log, debug_log_format }; ``` ### Check contract registration[​](#check-contract-registration "Direct link to Check contract registration") ``` await wallet.getContractMetadata(myContractInstance.address); ``` ### Decode L1 errors[​](#decode-l1-errors "Direct link to Decode L1 errors") Check hex errors against [Errors.sol](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/l1-contracts/src/core/libraries/Errors.sol) ## Tips[​](#tips "Direct link to Tips") * Always check logs before diving into circuit errors * State-related errors often indicate timing issues * Array overflow errors mean you hit transaction limits * Use debug WASM for detailed stack traces * Profile transactions when errors are unclear ## Next steps[​](#next-steps "Direct link to Next steps") * [Circuit Architecture](/developers/docs/foundational-topics/advanced/circuits.md) * [Call Types](/developers/docs/foundational-topics/call_types.md) * [Aztec.nr Dependencies](/developers/docs/aztec-nr/framework-description/dependencies.md) --- # Profiling Transactions This guide shows you how to profile Aztec transactions to understand gate counts and identify optimization opportunities. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * `aztec` command installed ([see installation](/developers/getting_started_on_local_network.md)) * Aztec contract compiled (`aztec compile`) * Basic understanding of proving and gate counts ## Choosing a profiling tool[​](#choosing-a-profiling-tool "Direct link to Choosing a profiling tool") Aztec provides three ways to profile. Each serves a different purpose: | Tool | What it measures | Needs deployment? | When to use | | ------------------------------------------------- | --------------------------------------------------------- | ----------------- | -------------------------------------------------------- | | `aztec profile gates` | Per-function gate counts | No | Quick check of individual function costs after compiling | | `aztec profile flamegraph` | Per-function flamegraph SVG | No | Deep-dive into where gates come from inside a function | | `aztec-wallet profile` / `.profile()` in aztec.js | Full transaction gate count including all kernel circuits | Yes\* | Understanding the true cost of a transaction end-to-end | \* `aztec-wallet profile` and `ContractFunctionInteraction.profile()` require a deployed contract. However, `DeployMethod.profile()` in aztec.js can profile deployment transactions before the contract exists. In most cases, start with `aztec profile gates` for a quick overview, then use the full transaction profiling tools when you need to understand kernel overhead. ## Quick profiling with `aztec profile`[​](#quick-profiling-with-aztec-profile "Direct link to quick-profiling-with-aztec-profile") These commands work on compiled artifacts directly — no deployment or running network required. ### Gate counts[​](#gate-counts "Direct link to Gate counts") ``` # Compile your contract aztec compile # Get gate counts for all functions aztec profile gates ./target ``` Example output: ``` Gate counts: ──────────────────────────────────────────────────────────────────── my_contract-MyContract::constructor 5,200 my_contract-MyContract::my_function 14,832 my_contract-MyContract::transfer 31,559 ──────────────────────────────────────────────────────────────────── Total: 3 circuit(s) ``` These are the gate counts for your contract functions alone, **without** kernel circuit overhead. See [Understanding kernel overhead](#understanding-kernel-overhead) for how this translates to total transaction cost. BB binary `aztec profile` needs the Barretenberg (`bb`) backend binary. It is auto-detected from the `@aztec/bb.js` package. If auto-detection fails, set the `BB` environment variable: ``` BB=/path/to/bb aztec profile gates ./target ``` Machine-readable output For build automation, use `--json` to emit gate counts as a JSON array. Each entry has `name`, `type` (`contract-function` or `program`), and `gates`: ``` aztec profile gates --json ./target ``` ### Flamegraphs[​](#flamegraphs "Direct link to Flamegraphs") To generate an interactive flamegraph SVG for a specific function: ``` aztec profile flamegraph ./target/my_contract-MyContract.json my_function ``` This outputs a file like `my_contract-MyContract-my_function-flamegraph.svg` in the same directory. Open it in a browser for an interactive view where: * **Width** represents gate count * **Height** represents call stack depth * **Wide sections** indicate optimization targets tip If `noir-profiler` is not on your PATH, set the `PROFILER_PATH` environment variable: ``` PROFILER_PATH=/path/to/noir-profiler aztec profile flamegraph ./target/my_contract-MyContract.json my_function ``` ## Full transaction profiling[​](#full-transaction-profiling "Direct link to Full transaction profiling") The tools above measure individual function gate counts. To understand the **total** proving cost of a transaction — including account entrypoints and kernel circuits — use `aztec-wallet profile` or `.profile()` in aztec.js. These require a running network and, in most cases, a deployed contract. The exception is `DeployMethod.profile()` in aztec.js, which can profile deployment transactions before the contract exists. ### Profile with aztec-wallet[​](#profile-with-aztec-wallet "Direct link to Profile with aztec-wallet") Use the `profile` command instead of `send` to get detailed gate counts: ``` # Import test accounts aztec-wallet import-test-accounts # Deploy your contract aztec-wallet deploy MyContractArtifact \ --from accounts:test0 \ --args [CONSTRUCTOR_ARGS] \ -a mycontract # Profile a function call aztec-wallet profile my_function \ -ca mycontract \ --args [FUNCTION_ARGS] \ -f accounts:test0 ``` #### Reading the output[​](#reading-the-output "Direct link to Reading the output") The profile command outputs a per-circuit breakdown: ``` Per circuit breakdown: Function name Time Gates Subtotal -------------------------------------------------------------------------------- - SchnorrAccount:entrypoint 12.34ms 21,724 21,724 - private_kernel_init 23.45ms 45,351 67,075 - MyContract:my_function 15.67ms 31,559 98,634 - private_kernel_inner 34.56ms 78,452 177,086 Total gates: 177,086 (Biggest circuit: private_kernel_inner -> 78,452) ``` Key metrics: * **Gates**: Circuit complexity for each step * **Subtotal**: Accumulated gate count * **Time**: Execution time per circuit Notice that the kernel circuits (`private_kernel_init`, `private_kernel_inner`) appear alongside your contract functions. These are protocol overhead — see [Understanding kernel overhead](#understanding-kernel-overhead). ### Profile with aztec.js[​](#profile-with-aztecjs "Direct link to Profile with aztec.js") ``` const result = await contract.methods.my_function(args).profile({ from: walletAddress, profileMode: "full", skipProofGeneration: true, }); // Access gate counts from execution steps for (const step of result.executionSteps) { console.log(`${step.functionName}: ${step.gateCount} gates`); } // Access timing information console.log("Total time:", result.stats.timings.total, "ms"); ``` #### Profile modes[​](#profile-modes "Direct link to Profile modes") * `gates`: Gate counts per circuit * `execution-steps`: Detailed execution trace with bytecode and witnesses * `full`: Complete profiling information (gates + execution steps) Set `skipProofGeneration: true` for faster iteration when you only need gate counts. ## Generate flamegraphs with noir-profiler[​](#generate-flamegraphs-with-noir-profiler "Direct link to Generate flamegraphs with noir-profiler") For deeper analysis of individual contract functions beyond what `aztec profile flamegraph` provides, you can use the Noir profiler directly. The profiler is installed automatically with Nargo (starting noirup v0.1.4). ``` # Compile your contract first aztec compile # Generate a gates flamegraph (requires bb backend) noir-profiler gates \ --artifact-path ./target/my_contract-MyContract.json \ --backend-path bb \ --output ./target # Generate an ACIR opcodes flamegraph noir-profiler opcodes \ --artifact-path ./target/my_contract-MyContract.json \ --output ./target ``` For detailed usage, see the [Noir profiler documentation](https://noir-lang.org/docs/tooling/profiler). ## Understanding kernel overhead[​](#understanding-kernel-overhead "Direct link to Understanding kernel overhead") When you profile a full transaction, you'll see kernel circuits alongside your contract functions. These are protocol overhead — the private kernel runs once per private function call in the transaction. Even a typical transaction calling a single contract function involves two private calls (the account entrypoint + your function), totaling \~427k gates of which only \~14k are your function. For a detailed breakdown of kernel phases and their gate costs, see [Private Kernel Circuit - Performance Impact](/developers/docs/foundational-topics/advanced/circuits/private_kernel.md#performance-impact). ## Gate count guidelines[​](#gate-count-guidelines "Direct link to Gate count guidelines") These are rough guidelines for a **single contract function's** gate count (i.e. what `aztec profile gates` reports). A typical transaction (e.g. a token transfer) totals \~500,000 gates across all circuits including kernel overhead, so use that as a reference point. | Gate Count | Assessment | | ----------------- | ---------------------------- | | < 50,000 | Excellent | | 50,000 - 200,000 | Good | | 200,000 - 500,000 | Consider optimizing | | > 500,000 | Worth optimizing if possible | Note that a high gate count does **not** prevent transaction inclusion — it only affects client-side proving time. See [Private Kernel Circuit - Performance Impact](/developers/docs/foundational-topics/advanced/circuits/private_kernel.md#performance-impact) for details. ## Next steps[​](#next-steps "Direct link to Next steps") * [Writing efficient contracts](/developers/docs/aztec-nr/framework-description/advanced/writing_efficient_contracts.md) - optimization strategies and examples * [Transaction lifecycle](/developers/docs/foundational-topics/transactions.md) * [Testing contracts](/developers/docs/aztec-nr/testing_contracts.md) --- # Proving Historic State This guide shows you how to prove historical state transitions and note inclusion using Aztec's Archive tree. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * An Aztec contract project set up * Understanding of Aztec's note and nullifier system ## What you can prove[​](#what-you-can-prove "Direct link to What you can prove") You can create proofs for these elements at any past block height: * **Note inclusion** - prove a note existed in the note hash tree * **Note validity** - prove a note existed and wasn't nullified at a specific block * **Nullifier inclusion/non-inclusion** - prove a nullifier was or wasn't in the nullifier tree * **Contract deployment** - prove a contract's bytecode was published or initialized Common use cases: * Verify ownership of an asset from another contract without revealing which specific note * Prove eligibility based on historical state (e.g., "owned tokens at block X") * Claim rewards based on past contributions (see the [claim contract](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/claim_contract/src/main.nr) for a complete example) ## Prove note inclusion[​](#prove-note-inclusion "Direct link to Prove note inclusion") Import the function: history\_import ``` use aztec::history::note::assert_note_existed_by; ``` > [Source code: noir-projects/noir-contracts/contracts/app/claim\_contract/src/main.nr#L5-L7](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/claim_contract/src/main.nr#L5-L7) Prove a note exists in the note hash tree: prove\_note\_inclusion ``` let header = self.context.get_anchor_block_header(); let confirmed_note = assert_note_existed_by(header, hinted_note); ``` > [Source code: noir-projects/noir-contracts/contracts/app/claim\_contract/src/main.nr#L41-L44](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/claim_contract/src/main.nr#L41-L44) ## Prove note validity[​](#prove-note-validity "Direct link to Prove note validity") To prove a note was valid (existed AND wasn't nullified) at a historical block: ``` use aztec::history::note::assert_note_was_valid_by; let header = self.context.get_anchor_block_header(); assert_note_was_valid_by(header, hinted_note, &mut self.context); ``` This verifies both: 1. The note was included in the note hash tree 2. The note's nullifier was not in the nullifier tree ## Prove at a specific historical block[​](#prove-at-a-specific-historical-block "Direct link to Prove at a specific historical block") To prove against state at a specific past block (not just the anchor block): ``` use aztec::history::note::assert_note_existed_by; let historical_header = self.context.get_block_header_at(block_number); assert_note_existed_by(historical_header, hinted_note); ``` warning Using `get_block_header_at` adds \~3k constraints to prove Archive tree membership. The anchor block header is effectively free since it's verified once per transaction. ## Prove a note was nullified[​](#prove-a-note-was-nullified "Direct link to Prove a note was nullified") To prove a note has been spent/nullified: ``` use aztec::history::note::assert_note_was_nullified_by; let header = self.context.get_anchor_block_header(); assert_note_was_nullified_by(header, confirmed_note, &mut self.context); ``` ## Prove contract bytecode was published[​](#prove-contract-bytecode-was-published "Direct link to Prove contract bytecode was published") To prove a contract's bytecode was published at a historical block: ``` use aztec::history::deployment::assert_contract_bytecode_was_published_by; let header = self.context.get_anchor_block_header(); assert_contract_bytecode_was_published_by(header, contract_address); ``` You can also prove a contract was initialized (constructor was called): ``` use aztec::history::deployment::assert_contract_was_initialized_by; use aztec::oracle::get_contract_instance::get_contract_instance; let header = self.context.get_anchor_block_header(); let instance = get_contract_instance(contract_address); assert_contract_was_initialized_by(header, contract_address, instance.initialization_hash); ``` ## Available proof functions[​](#available-proof-functions "Direct link to Available proof functions") The `aztec::history` module provides these functions: | Function | Module | Purpose | | ----------------------------------------------- | --------------------- | ----------------------------------------------- | | `assert_note_existed_by` | `history::note` | Prove note exists in note hash tree | | `assert_note_was_valid_by` | `history::note` | Prove note exists and is not nullified | | `assert_note_was_nullified_by` | `history::note` | Prove note's nullifier is in nullifier tree | | `assert_note_was_not_nullified_by` | `history::note` | Prove note's nullifier is not in nullifier tree | | `assert_nullifier_existed_by` | `history::nullifier` | Prove a raw nullifier exists | | `assert_nullifier_did_not_exist_by` | `history::nullifier` | Prove a raw nullifier does not exist | | `assert_contract_bytecode_was_published_by` | `history::deployment` | Prove a contract's bytecode was published | | `assert_contract_bytecode_was_not_published_by` | `history::deployment` | Prove a contract's bytecode was not published | | `assert_contract_was_initialized_by` | `history::deployment` | Prove a contract was initialized | | `assert_contract_was_not_initialized_by` | `history::deployment` | Prove a contract was not initialized | --- # Retrieving and Filtering Notes This guide shows you how to retrieve and filter notes from private storage using `NoteGetterOptions`. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Aztec contract with note storage * Understanding of note structure and properties ## Required imports[​](#required-imports "Direct link to Required imports") ``` use aztec::note::note_getter_options::{NoteGetterOptions, NoteStatus, SortOrder}; use aztec::utils::comparison::Comparator; ``` ## Set up basic note retrieval[​](#set-up-basic-note-retrieval "Direct link to Set up basic note retrieval") ### Step 1: Create default options[​](#step-1-create-default-options "Direct link to Step 1: Create default options") ``` let mut options = NoteGetterOptions::new(); ``` This returns up to `MAX_NOTE_HASH_READ_REQUESTS_PER_CALL` notes without filtering. ### Step 2: Retrieve notes from storage[​](#step-2-retrieve-notes-from-storage "Direct link to Step 2: Retrieve notes from storage") ``` // Returns BoundedVec, ...> let confirmed_notes = storage.my_notes.at(owner).get_notes(options); ``` get\_notes vs pop\_notes * `get_notes`: Retrieves notes without nullifying. Note data is not guaranteed to be current or non-nullified—use when you only need to read note data without consuming it. * `pop_notes`: Retrieves AND nullifies notes in one operation. Use when consuming notes (e.g., spending tokens). More efficient than calling `get_notes` followed by manual nullification. Here's an example of `pop_notes` with filtering from the NFT contract: pop\_notes ``` let notes = nfts.at(from).pop_notes(NoteGetterOptions::new() .select(NFTNote::properties().token_id, Comparator.EQ, token_id) .set_limit(1)); assert(notes.len() == 1, "NFT not found when transferring"); ``` > [Source code: noir-projects/noir-contracts/contracts/app/nft\_contract/src/main.nr#L215-L220](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr#L215-L220) ## Filter notes by properties[​](#filter-notes-by-properties "Direct link to Filter notes by properties") ### Step 1: Select notes with specific field values[​](#step-1-select-notes-with-specific-field-values "Direct link to Step 1: Select notes with specific field values") ``` // Assuming MyNote has an 'owner' field let mut options = NoteGetterOptions::new(); options = options.select( MyNote::properties().owner, Comparator.EQ, owner ); ``` ### Step 2: Apply multiple selection criteria[​](#step-2-apply-multiple-selection-criteria "Direct link to Step 2: Apply multiple selection criteria") ``` let mut options = NoteGetterOptions::new(); options = options .select(MyNote::properties().value, Comparator.EQ, value) .select(MyNote::properties().owner, Comparator.EQ, owner); ``` tip Chain multiple `select` calls to filter by multiple fields. Remember to call `get_notes(options)` after applying all your selection criteria to retrieve the filtered notes. ## Sort retrieved notes[​](#sort-retrieved-notes "Direct link to Sort retrieved notes") ### Sort and paginate results[​](#sort-and-paginate-results "Direct link to Sort and paginate results") ``` let mut options = NoteGetterOptions::new(); options = options .select(MyNote::properties().owner, Comparator.EQ, owner) .sort(MyNote::properties().value, SortOrder.DESC) .set_limit(10) // Max 10 notes .set_offset(20); // Skip first 20 ``` ## Apply custom filters[​](#apply-custom-filters "Direct link to Apply custom filters") Filter Performance Database `select` is more efficient than custom filters. Use custom filters only for complex logic. ### Create and use a custom filter[​](#create-and-use-a-custom-filter "Direct link to Create and use a custom filter") custom\_filter ``` pub fn filter_notes_min_sum( notes: [Option>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL], min_sum: Field, ) -> [Option>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] { let mut selected = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; let mut sum = 0; for i in 0..notes.len() { if notes[i].is_some() & sum.lt(min_sum) { let hinted_note = notes[i].unwrap_unchecked(); selected[i] = Option::some(hinted_note); sum += hinted_note.note.value; } } selected } ``` > [Source code: noir-projects/noir-contracts/contracts/test/pending\_note\_hashes\_contract/src/filter.nr#L4-L22](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/test/pending_note_hashes_contract/src/filter.nr#L4-L22) Then use it with `NoteGetterOptions`: ``` let options = NoteGetterOptions::with_filter(filter_notes_min_sum, min_value); ``` Note Limits Maximum notes per call: `MAX_NOTE_HASH_READ_REQUESTS_PER_CALL` (currently 16) Available Comparators * `Comparator.EQ`: Equal to * `Comparator.NEQ`: Not equal to * `Comparator.LT`: Less than * `Comparator.LTE`: Less than or equal * `Comparator.GT`: Greater than * `Comparator.GTE`: Greater than or equal ## Call from TypeScript[​](#call-from-typescript "Direct link to Call from TypeScript") You can pass comparator values from TypeScript to your contract functions: ``` import { Comparator } from '@aztec/aztec.js/note'; // Pass comparator to a contract function that accepts it as a parameter await contract.methods.read_notes(Comparator.GTE, 5).simulate({ from: senderAddress }); ``` ## View notes without constraints[​](#view-notes-without-constraints "Direct link to View notes without constraints") Use `NoteViewerOptions` in unconstrained utility functions to query notes without generating proofs: view\_notes ``` #[external("utility")] unconstrained fn get_private_nfts(owner: AztecAddress, page_index: u32) -> ([Field; MAX_NOTES_PER_PAGE], bool) { let offset = page_index * MAX_NOTES_PER_PAGE; let options = NoteViewerOptions::new().set_offset(offset); let notes = self.storage.private_nfts.at(owner).view_notes(options); let mut owned_nft_ids = [0; MAX_NOTES_PER_PAGE]; for i in 0..options.limit { if i < notes.len() { owned_nft_ids[i] = notes.get_unchecked(i).token_id; } } let page_limit_reached = notes.len() == options.limit; (owned_nft_ids, page_limit_reached) } ``` > [Source code: noir-projects/noir-contracts/contracts/app/nft\_contract/src/main.nr#L255-L272](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr#L255-L272) Viewer vs Getter * `NoteGetterOptions`: For constrained private functions with proof generation (max 16 notes) * `NoteViewerOptions`: For unconstrained utility functions, no proofs (max 10 notes per page via `MAX_NOTES_PER_PAGE`) ## Query notes with different status[​](#query-notes-with-different-status "Direct link to Query notes with different status") ### Set status to include nullified notes[​](#set-status-to-include-nullified-notes "Direct link to Set status to include nullified notes") ``` let mut options = NoteGetterOptions::new(); options = options.set_status(NoteStatus.ACTIVE_OR_NULLIFIED); ``` Note Status Options * `NoteStatus.ACTIVE`: Only active (non-nullified) notes (default) * `NoteStatus.ACTIVE_OR_NULLIFIED`: Both active and nullified notes ## Next steps[​](#next-steps "Direct link to Next steps") * Learn about [custom note implementations](/developers/docs/aztec-nr/framework-description/custom_notes.md) * Explore [note discovery mechanisms](/developers/docs/foundational-topics/advanced/storage/note_discovery.md) * Understand [partial notes](/developers/docs/aztec-nr/framework-description/advanced/partial_notes.md) --- # Using Capsules Capsules provide per-contract non-volatile storage in the PXE. Data is stored locally (not onchain), scoped per contract address, and persists until explicitly deleted. ## Basic usage[​](#basic-usage "Direct link to Basic usage") ``` use aztec::oracle::capsules; // Capsule operations are unconstrained, so these values are typically // passed in as parameters from the calling context. let contract_address: AztecAddress = /* self.address */; let slot: Field = 1; // scope is an AztecAddress used for capsule isolation, allowing multiple // independent namespaces within the same contract. let scope: AztecAddress = /* e.g. the account address */; // Store data at a slot (overwrites existing data) capsules::store(contract_address, slot, value, scope); // Load data (returns Option) let result: Option = capsules::load(contract_address, slot, scope); // Delete data at a slot capsules::delete(contract_address, slot, scope); // Copy contiguous slots (supports overlapping regions) // copy(contract_address, src_slot, dst_slot, num_entries: u32, scope) capsules::copy(contract_address, src_slot, dst_slot, 3, scope); ``` Types must implement `Serialize` and `Deserialize` traits. warning All capsule operations are `unconstrained`. Data loaded from capsules should be validated in constrained contexts. Contracts can only access their own capsules. ## CapsuleArray[​](#capsulearray "Direct link to CapsuleArray") `CapsuleArray` provides dynamic array storage backed by capsules: ``` use aztec::capsules::CapsuleArray; use aztec::protocol::hash::sha256_to_field; // Use a hash for base_slot to avoid collisions with other storage global BASE_SLOT: Field = sha256_to_field("MY_CONTRACT::MY_ARRAY".as_bytes()); let array: CapsuleArray = CapsuleArray::at(contract_address, BASE_SLOT, scope); array.push(value); // Append to end let value = array.get(index); // Read at index (throws if out of bounds) let length = array.len(); // Get current size (returns u32) array.remove(index); // Delete & shift elements (index is u32) // Iterate and optionally remove elements array.for_each(|index, value| { if some_condition(value) { array.remove(index); // Safe to remove current element only } }); ``` `for_each` Safety It is safe to remove the current element during `for_each`, but **do not push new elements** during iteration. Storage Layout CapsuleArray stores length at the base slot, with elements in consecutive slots (base+1 for index 0, base+2 for index 1, etc.). Ensure sufficient space between different array base slots. --- # Partial Notes ## What are Partial Notes?[​](#what-are-partial-notes "Direct link to What are Partial Notes?") Partial notes are notes created with incomplete data, usually during private execution, which can be completed with additional information that becomes available later, usually during public execution. Let's say, for example, we have a `UintNote`: uint\_note\_def ``` #[derive(Deserialize, Eq, Serialize, Packable)] #[custom_note] pub struct UintNote { /// The number stored in the note. pub value: u128, } ``` > [Source code: noir-projects/aztec-nr/uint-note/src/uint\_note.nr#L26-L33](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/aztec-nr/uint-note/src/uint_note.nr#L26-L33) The `UintNote` struct itself only contains the `value` field. Additional fields including `owner`, `randomness`, and `storage_slot` are passed as parameters during note hash computation. When creating the note locally during private execution, the `owner` and `storage_slot` are known, but the `value` potentially is not (e.g., it depends on some onchain dynamic variable). First, a **partial note** can be created during private execution that commits to the `owner` and `randomness`, and then the note is *"completed"* to create a full note by later adding the `storage_slot` and `value` fields, usually during public execution. ![](/assets/ideal-img/partial-notes.167e271.640.png) ## Use Cases[​](#use-cases "Direct link to Use Cases") Partial notes are useful when a e.g., part of the note struct is a value that depends on dynamic, public onchain data that isn't available during private execution, such as: * AMM swap prices * Current gas prices * Time-dependent interest accrual ## Implementation[​](#implementation "Direct link to Implementation") All notes in Aztec use the partial note format internally. This ensures that notes produce identical note hashes regardless of whether they were created as complete notes (with all fields known in private) or as partial notes (completed later in public). By having all notes follow the same two-phase hash commitment process, the protocol maintains consistency and allows notes created through different flows to behave identically. ### Note Structure Example[​](#note-structure-example "Direct link to Note Structure Example") The `UintNote` struct contains only the `value` field: uint\_note\_def ``` #[derive(Deserialize, Eq, Serialize, Packable)] #[custom_note] pub struct UintNote { /// The number stored in the note. pub value: u128, } ``` > [Source code: noir-projects/aztec-nr/uint-note/src/uint\_note.nr#L26-L33](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/aztec-nr/uint-note/src/uint_note.nr#L26-L33) ### Two-Phase Commitment Process[​](#two-phase-commitment-process "Direct link to Two-Phase Commitment Process") **Phase 1: Partial Commitment (Private Execution)** The private fields (`owner` and `randomness`) are committed during local, private execution: compute\_partial\_commitment ``` fn compute_partial_commitment(owner: AztecAddress, randomness: Field) -> Field { poseidon2_hash_with_separator([owner.to_field(), randomness], DOM_SEP__NOTE_HASH) } ``` > [Source code: noir-projects/aztec-nr/uint-note/src/uint\_note.nr#L143-L147](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/aztec-nr/uint-note/src/uint_note.nr#L143-L147) This creates a partial note commitment: ``` partial_commitment = H(owner, randomness) ``` **Phase 2: Note Completion (Public Execution)** The note is completed by hashing the partial commitment with the public value: compute\_complete\_note\_hash ``` fn compute_complete_note_hash(self, storage_slot: Field, value: u128) -> Field { // Here we finalize the note hash by including the (public) storage slot and value into the partial note // commitment. Note that we use the same separator as we used for the first round of poseidon - this is not // an issue. poseidon2_hash_with_separator( [self.commitment, storage_slot, value.to_field()], DOM_SEP__NOTE_HASH, ) } ``` > [Source code: noir-projects/aztec-nr/uint-note/src/uint\_note.nr#L241-L251](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/aztec-nr/uint-note/src/uint_note.nr#L241-L251) The resulting structure is a nested commitment: ``` note_hash = H(H(owner, randomness), storage_slot, value) = H(partial_commitment, storage_slot, value) ``` ## Universal Note Format[​](#universal-note-format "Direct link to Universal Note Format") All notes in Aztec use the partial note format internally, even when all data is known during private execution. This ensures consistent note hash computation regardless of how the note was created. When a note is created with all fields known (including `owner`, `storage_slot`, `randomness`, and `value`): 1. A partial commitment is computed from the private fields (`owner`, `randomness`) 2. The partial commitment is immediately completed with the `storage_slot` and `value` fields compute\_note\_hash ``` fn compute_note_hash(self, owner: AztecAddress, storage_slot: Field, randomness: Field) -> Field { // Partial notes can be implemented by having the note hash be either the result of multiscalar multiplication // (MSM), or two rounds of poseidon. MSM results in more constraints and is only required when multiple // variants of partial notes are supported. Because UintNote has just one variant (where the value is public), // we use poseidon instead. // We must compute the same note hash as would be produced by a partial note created and completed with the // same values, so that notes all behave the same way regardless of how they were created. To achieve this, we // perform both steps of the partial note computation. // First we create the partial note from a commitment to the private content. let partial_note = PartialUintNote { commitment: compute_partial_commitment(owner, randomness) }; // Then compute the completion note hash. In a real partial note this step would be performed in public. partial_note.compute_complete_note_hash(storage_slot, self.value) } ``` > [Source code: noir-projects/aztec-nr/uint-note/src/uint\_note.nr#L36-L53](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/aztec-nr/uint-note/src/uint_note.nr#L36-L53) This two-step process ensures that notes with identical field values produce identical note hashes, regardless of whether they were created as partial notes or complete notes. ## Partial Notes in Practice[​](#partial-notes-in-practice "Direct link to Partial Notes in Practice") To understand how to use partial notes in practice, [this AMM contract](https://github.com/AztecProtocol/aztec-packages/tree/v4.3.1/noir-projects/noir-contracts/contracts/app/amm_contract) uses partial notes to initiate and complete the swap of `token1` to `token2`. Since the exchange rate is onchain, it cannot be known ahead of time while executing in private so a full note cannot be created. Instead, a partial note is created for the `owner` swapping the tokens. This partial note is then completed during public execution once the exchange rate can be read. --- # Oracle Functions This page goes over what oracles are in Aztec and how they work. Looking for a hands-on guide? You can learn how to use oracles in a smart contract [here](/developers/docs/aztec-nr/framework-description/advanced/how_to_use_capsules.md). An oracle is something that allows us to get data from the outside world into our contracts. The most widely-known types of oracles in blockchain systems are probably Chainlink price feeds, which allow us to get the price of an asset in USD taking non-blockchain data into account. While this is one type of oracle, the more general oracle, allows us to get any data into the contract. In the context of oracle functions or oracle calls in Aztec, it can essentially be seen as user-provided arguments, that can be fetched at any point in the circuit, and don't need to be an input parameter. **Why is this useful? Why don't just pass them as input parameters?** In the world of EVM, you would just read the values directly from storage and call it a day. However, when we are working with circuits for private execution, this becomes more tricky as you cannot just read the storage directly from your state tree, because there are only commitments (e.g. hashes) there. The pre-images (content) of your commitments need to be provided to the function to prove that you actually allowed to modify them. If we fetch the notes using an oracle call, we can keep the function signature independent of the underlying data and make it easier to use. A similar idea, applied to the authentication mechanism is used for the Authentication Witnesses that allow us to have a single function signature for any wallet implementation, see [AuthWit](/developers/docs/aztec-nr/framework-description/authentication_witnesses.md) for more information on this. Oracles introduce **non-determinism** into a circuit, and thus are `unconstrained`. It is important that any information that is injected into a circuit through an oracle is later constrained for correctness. Otherwise, the circuit will be **under-constrained** and potentially insecure! `Aztec.nr` has a [module dedicated to its oracles](/aztec-nr-api/mainnet/noir_aztec/oracle/index.html) where you can browse the full list. ## Inbuilt oracles[​](#inbuilt-oracles "Direct link to Inbuilt oracles") * [`debug_log`](/aztec-nr-api/mainnet/noir_aztec/protocol/logging/fn.debug_log) - Provides debug functions that can be used to log information to the console. Read more about debugging [here](/developers/docs/aztec-nr/debugging.md). * [`auth_witness`](/aztec-nr-api/mainnet/noir_aztec/oracle/auth_witness/index.html) - Provides a way to fetch the authentication witness for a given address. This is useful when building account contracts to support approve-like functionality. * [`get_l1_to_l2_membership_witness`](/aztec-nr-api/mainnet/noir_aztec/oracle/get_l1_to_l2_membership_witness/index.html) - Returns the leaf index and sibling path for an L1 to L2 message, used to prove message existence in cross-chain applications like token bridges. * [`notes`](/aztec-nr-api/mainnet/noir_aztec/oracle/notes/index.html) - Provides functions related to notes, such as fetching notes from storage, used behind the scenes for value notes and other pre-built note implementations. * [`logs`](/aztec-nr-api/mainnet/noir_aztec/oracle/logs/index.html) - Provides functions to log encrypted and unencrypted data. Find a full list [on GitHub](https://github.com/AztecProtocol/aztec-packages/tree/v4.3.1/noir-projects/aztec-nr/aztec/src/oracle). Please note that it is **not** possible to write a custom oracle for your dapp. Oracles are implemented in the PXE, so all users of your dapp would have to use a PXE with your custom oracle included. If you want to inject some arbitrary data that does not have a dedicated oracle, you can use [capsules](/developers/docs/aztec-nr/framework-description/advanced/how_to_use_capsules.md). --- # Writing Efficient Contracts ## Writing functions[​](#writing-functions "Direct link to Writing functions") On Ethereum L1, all data is public and all execution is completely reproducible. The Aztec L2 takes on the challenge of execution of private functions on private data. This is done client side, along with the generation of corresponding proofs, so that the network can verify the proofs and append any encrypted data/nullifiers (privacy preserving state update). This highlights a key difference with how public vs private functions are written. Writing efficiently * **Public functions** can be written intuitively - optimising for execution/gas as one would for EVM L2s * **Private functions** are optimized differently, as they are compiled to a circuit to be proven locally (see [Thinking in Circuits](https://noir-lang.org/docs/explainers/explainer-writing-noir)) ## Assessing efficiency[​](#assessing-efficiency "Direct link to Assessing efficiency") On Aztec (like other L2s) there are several costs/limit to consider... * L1 costs - execution, blobs, events * L2 costs - public execution, data, logs * Local limits - proof generation time, execution ### Local Proof generation[​](#local-proof-generation "Direct link to Local Proof generation") Since proof generation is a significant local burden, being mindful of the gate-count of private functions is important. The gate-count is a proportionate indicator of the memory and time required to prove locally, so should not be ignored. #### Noir for circuits[​](#noir-for-circuits "Direct link to Noir for circuits") An explanation of efficient use of Noir for circuits should be considered for each subsection under [writing efficient Noir](https://noir-lang.org/docs/explainers/explainer-writing-noir#writing-efficient-noir-for-performant-products) to avoid hitting local limits. The general theme is to use language features that favour the underlying primitives and representation of a circuit from code. A couple of examples: * Since the underlying cryptography uses an equation made of additions and multiplications, these are more efficient (wrt gate count) in Noir than say bit-shifting. * Unconstrained functions by definition do not constrain their operations/output, so do not contribute to gate count. Using them carefully can bring in some savings, but the results must then be constrained so that proofs are meaningful for your application. Tradeoffs and caveats Each optimisation technique has its own tradeoffs and caveats so should be carefully considered with the full details in the linked [section](https://noir-lang.org/docs/explainers/explainer-writing-noir#writing-efficient-noir-for-performant-products). #### Overhead of nested private calls[​](#overhead-of-nested-private-calls "Direct link to Overhead of nested private calls") Every transaction pays a fixed kernel overhead (\~290k gates for init, reset, and tail circuits). Each additional private function call beyond the account entrypoint adds a `private_kernel_inner` iteration (\~101k gates). This overhead compounds with the number of distinct private function calls, so be mindful of calling/nesting too many private functions — this may influence your design towards larger private functions rather than conventionally atomic ones. For example, if you have a function that calls an external verification step as a separate private function, inlining that verification saves an entire kernel iteration (\~101k gates), even if it slightly increases the calling function's own gate count. See [Private Kernel Circuit - Performance Impact](/developers/docs/foundational-topics/advanced/circuits/private_kernel.md#performance-impact) for detailed numbers. #### Profiling[​](#profiling "Direct link to Profiling") Measuring gate counts is explained in the [profiling guide](/developers/docs/aztec-nr/framework-description/advanced/how_to_profile_transactions.md). Use `aztec profile gates` for quick per-function gate counts, or `aztec-wallet profile` for full transaction profiling including kernel overhead. ### L2 Data costs[​](#l2-data-costs "Direct link to L2 Data costs") Of the L2 costs, the public/private data being updated is most significant. As L2 functions create notes, nullifiers, encrypted logs, all of this get posted into blobs on ethereum and will be quite expensive ### L1 Limits[​](#l1-limits "Direct link to L1 Limits") While most zk rollups don't leverage the zero-knowledge property like Aztec, they do leverage the succinctness property. That is, what is stored in an L1 contract is simply a hash. For data availability, blobs are utilized since data storage is often cheaper here than in contracts. Like other L2s such costs are factored into the L2 fee mechanisms. These limits can be seen and iterated on when a transaction is simulated/estimated. ## Examples for private functions (reducing gate count)[​](#examples-for-private-functions-reducing-gate-count "Direct link to Examples for private functions (reducing gate count)") After the first section about generating a flamegraph for an Aztec function, each section shows an example of different optimisation techniques. ### Inspecting with flamegraphs[​](#inspecting-with-flamegraphs "Direct link to Inspecting with flamegraphs") Use the Noir profiler to generate flamegraphs for your contract functions. The profiler is installed automatically with Nargo (starting noirup v0.1.4). ``` # Generate a gates flamegraph (requires bb backend) noir-profiler gates \ --artifact-path ./target/counter-Counter.json \ --backend-path bb \ --output ./target ``` Open the generated `.svg` file in a browser for an interactive view. For more details, see the [profiling guide](/developers/docs/aztec-nr/framework-description/advanced/how_to_profile_transactions.md). ![](/assets/ideal-img/flamegraph-counter.aeb3d35.640.png) To get a sense of things, here is a table of gate counts for common operations: | Gates | Operation | | ------- | ------------------------------------------------------------------------------ | | \~75 | Hashing 3 fields with Poseidon2 | | 3500 | Reading a value from a tree (public data tree, note hash tree, nullifier tree) | | 4000 | Reading a delayed public mutable read | | \~5,000 | Calculating sha256 (varies by input size) | | Varies | Constrained encryption of a private log (depends on field count) | | Varies | Constrained encryption and tagging of a private log (depends on field count) | ### Optimization: use arithmetic instead of non-arithmetic operations[​](#optimization-use-arithmetic-instead-of-non-arithmetic-operations "Direct link to Optimization: use arithmetic instead of non-arithmetic operations") Because the underlying equation in the proving backend makes use of multiplication and addition, these operations incur less gates than bit-shifting or bit-masking. For example: ``` comptime global TWO_POW_16: Field = 2.pow_32(16); // ... { #[external("private")] fn mul_inefficient(number: Field) -> u128 { number as u128 << 16 as u8 } // 5244 gates #[external("private")] fn mul_efficient(number: Field) -> u128 { (number * TWO_POW_16) as u128 } // 5184 gates (60 gates less) } ``` When comparing the flamegraph of the two functions, the inefficient shift example has a section of gates not present in the multiplication example. This difference equates to a saving of 60 gates. In the same vein bitwise `AND`/`OR`, and inequality relational operators (`>`, `<`) are expensive. Try avoid these in your circuits. For example, use boolean equality effectively instead of `>=`: ``` { #[external("private")] fn sum_from_inefficient(from: u32, array: [u32; 1000]) -> u32 { let mut sum: u32 = 0; for i in 0..1000 { if i >= from { // condition based on `>=` each time (higher gate count) sum += array[i]; } } sum } // 44317 gates #[external("private")] fn sum_from_efficient(from: u32, array: [u32; 1000]) -> u32 { let mut sum: u32 = 0; let mut do_sum = false; for i in 0..1000 { if i == from { // latches boolean at transition (equality comparison) do_sum = true; } if do_sum { // condition based on boolean true (lower gate count) sum += array[i]; } } sum } // 45068 gates (751 gates more due to the boolean operations, but the pattern demonstrates how to avoid range checks) } ``` So for a loop of 1000 iterations, 751 gates were saved by: * Adding an equivalence check and a boolean assignment * Replacing `>=` with a boolean equivalence check Difference with Rust Such designs with boolean flags lend themselves well into logical comparisons too since `&&` and `||` do not exist. With booleans, using `&` and `|` can give you the required logic efficiently. For more points specific to the Noir language, see [this](https://noir-lang.org/docs/explainers/explainer-writing-noir#translating-from-rust) section. ### Optimization: Loop design[​](#optimization-loop-design "Direct link to Optimization: Loop design") Since private functions are circuits, their size must be known at compile time, which is equivalent to its execution trace. See [this example](https://github.com/noir-lang/noir-examples/blob/master/noir_by_example/loops/noir/src/main.nr#L11) for how to use loops when dynamic execution lengths (ie variable number of loops) is not possible. ### Optimization: considered use of `unconstrained` functions[​](#optimization-considered-use-of-unconstrained-functions "Direct link to optimization-considered-use-of-unconstrained-functions") #### Example - calculating square root[​](#example---calculating-square-root "Direct link to Example - calculating square root") Consider the following example of an implementation of the `sqrt` function: ``` use aztec::macros::aztec; #[aztec] pub contract OptimisationExample { use aztec::macros::{functions::{external, initializer}, storage::storage}; #[storage] struct Storage {} #[external("public")] #[initializer] fn constructor() {} #[external("private")] fn sqrt_inefficient(number: Field) -> Field { super::sqrt_constrained(number) } #[external("private")] fn sqrt_efficient(number: Field) -> Field { // Safety: calculate in unconstrained function, then constrain the result let x = unsafe { super::sqrt_unconstrained(number) }; assert(x * x == number, "x*x should be number"); x } } fn sqrt_constrained(number: Field) -> Field { let MAX_LEN = 100; let mut guess = number; let mut guess_squared = guess * guess; for _ in 1..MAX_LEN as u32 + 1 { // only use square root part of circuit when required, otherwise use alternative part of circuit that does nothing // Note: both parts of the circuit exist MAX_LEN times in the circuit, regardless of whether the square root part is used or not if (guess_squared != number) { guess = (guess + number / guess) / 2; guess_squared = guess * guess; } } guess } unconstrained fn sqrt_unconstrained(number: Field) -> Field { let mut guess = number; let mut guess_squared = guess * guess; while guess_squared != number { guess = (guess + number / guess) / 2; guess_squared = guess * guess; } guess } ``` The two implementations after the contract differ in one being constrained vs unconstrained, as well as the loop implementation (which has other design considerations). Measuring the two, we find the `sqrt_inefficient` to require around 1500 extra gates compared to `sqrt_efficient`. To generate flamegraphs for each function: ``` noir-profiler gates \ --artifact-path ./target/optimisation_example-OptimisationExample.json \ --backend-path bb \ --output ./target ``` If you make changes to the code, recompile and regenerate the flamegraph, then refresh the `.svg` file in your browser. Note: this is largely a factor of the loop size choice based on the maximum size of `number` you are required to be calculating the square root of. For larger numbers, the loop would have to be much larger, so perform in an unconstrained way (then constraining the result) is much more efficient. #### Example - sorting an array[​](#example---sorting-an-array "Direct link to Example - sorting an array") Like with sqrt, we have the inefficient function that does the sort with constrained operations, and the efficient function that uses the unconstrained sort function then constrains the result. ``` //... { #[external("private")] fn sort_inefficient(array: [u32; super::ARRAY_SIZE]) -> [u32; super::ARRAY_SIZE] { let mut sorted_array = array; for i in 0..super::ARRAY_SIZE as u32 { for j in 0..super::ARRAY_SIZE as u32 { if sorted_array[i] < sorted_array[j] { let temp = sorted_array[i as u32]; sorted_array[i as u32] = sorted_array[j as u32]; sorted_array[j as u32] = temp; } } } sorted_array } // 6823 gates for 10 elements, 127780 gates for 100 elements #[external("private")] fn sort_efficient(array: [u32; super::ARRAY_SIZE]) -> [u32; super::ARRAY_SIZE] { // Safety: calculate in unconstrained function, then constrain the result let sorted_array = unsafe { super::sort_array(array) }; // constrain that sorted_array elements are sorted for i in 0..super::ARRAY_SIZE as u32 - 1 { assert(sorted_array[i] <= sorted_array[i + 1], "array should be sorted"); } // Note: A production implementation should also verify that sorted_array is a // permutation of the input array to prevent a malicious prover from returning // arbitrary sorted values. sorted_array } // 5870 gates (953 gates less) for 10 elements, 12582 gates for 100 elements (115198 gates less) } unconstrained fn sort_array(array: [u32; ARRAY_SIZE]) -> [u32; ARRAY_SIZE] { let mut sorted_array = array; for i in 0..ARRAY_SIZE as u32 { for j in 0..ARRAY_SIZE as u32 { if sorted_array[i] < sorted_array[j] { let temp = sorted_array[i as u32]; sorted_array[i as u32] = sorted_array[j as u32]; sorted_array[j as u32] = temp; } } } sorted_array } ``` Like before, `noir-profiler` can be used to visualize the gate counts of the private functions, highlighting that 953 gates could be saved. Note: The stdlib provides a highly optimized version of sort on arrays, `array.sort()`, which saves even more gates. ``` #[external("private")] fn sort_stdlib(array: [u32; super::ARRAY_SIZE]) -> [u32; super::ARRAY_SIZE] { array.sort() } // 5943 gates (880 gates less) for 10 elements, 13308 gates for 100 elements (114472 gates less) ``` #### Example - refactoring arrays[​](#example---refactoring-arrays "Direct link to Example - refactoring arrays") In the same vein, refactoring is inefficient when done constrained, and more efficient to do unconstrained then constrain the output. ``` { #[external("private")] fn refactor_inefficient(array: [u32; super::ARRAY_SIZE]) -> [u32; super::ARRAY_SIZE] { let mut compacted_array = [0; super::ARRAY_SIZE]; let mut index = 0; for i in 0..super::ARRAY_SIZE as u32 { if (array[i] != 0) { compacted_array[index] = array[i]; index += 1; } } compacted_array } // 6570 gates for 10 elements, 93071 gates for 100 elements #[external("private")] fn refactor_efficient(array: [u32; super::ARRAY_SIZE]) -> [u32; super::ARRAY_SIZE] { let compacted_array = unsafe { super::refactor_array(array) }; // count non-zero elements in array let mut count = 0; for i in 0..super::ARRAY_SIZE as u32 { if (array[i] != 0) { count += 1; } } // count non-zero elements in compacted_array let mut count_compacted = 0; for i in 0..super::ARRAY_SIZE as u32 { if (compacted_array[i] != 0) { count_compacted += 1; } else { assert(compacted_array[i] == 0, "trailing compacted_array elements should be 0"); } } assert(count == count_compacted, "count should be equal to count_compacted"); compacted_array } // 5825 gates (745 gates less), 12290 gates for 100 elements (80781 gates less) } unconstrained fn refactor_array(array: [u32; ARRAY_SIZE]) -> [u32; ARRAY_SIZE] { let mut compacted_array = [0; ARRAY_SIZE]; let mut index = 0; for i in 0..ARRAY_SIZE as u32 { if (array[i] != 0) { compacted_array[index] = array[i]; index += 1; } } compacted_array } ``` ### Optimizing: Reducing L2 reads[​](#optimizing-reducing-l2-reads "Direct link to Optimizing: Reducing L2 reads") If a struct has many fields to be read, we can design an extra variable maintained as the hash of all values within it (like a checksum). When it comes to reading, we can now do an unconstrained read (incurring no read requests), and then check the hash of the result against that stored for the struct. This final check is thus only one read request rather than one per variable. Leverage unconstrained functions When needing to make use of large private operations (eg private execution or many read requests), use of [unconstrained functions](https://noir-lang.org/docs/explainers/explainer-writing-noir#leverage-unconstrained-execution) wisely to reduce the gate count of private functions. --- # Authentication Witnesses Authentication witnesses (authwit) allow other contracts to execute actions on behalf of your account. This guide shows you how to implement and use authwits in your Aztec smart contracts. For a video walkthrough of the concepts and the implementation pattern, watch this explainer (find more on the [video lessons](/developers/docs/resources/video_lessons.md) page): [How Authorization Works on Aztec](https://www.youtube-nocookie.com/embed/VRZVOCdjGZ4) ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * An Aztec contract project set up with `aztec-nr` dependency * Understanding of private and public functions in Aztec For conceptual background, see [Authentication Witnesses](/developers/docs/foundational-topics/advanced/authwit.md). ## Import the authwit library[​](#import-the-authwit-library "Direct link to Import the authwit library") The `aztec` library includes authwit functionality. Import the necessary components: ``` use aztec::{ authwit::auth::{compute_authwit_message_hash_from_call, set_authorized}, macros::functions::authorize_once, }; ``` ## Using the `authorize_once` macro[​](#using-the-authorize_once-macro "Direct link to using-the-authorize_once-macro") The `#[authorize_once]` macro validates that a caller has authorization from the `from` address. It handles authwit verification and nullifier emission automatically. ### Private function example[​](#private-function-example "Direct link to Private function example") transfer\_in\_private ``` #[authorize_once("from", "authwit_nonce")] #[external("private")] fn transfer_in_private(from: AztecAddress, to: AztecAddress, amount: u128, authwit_nonce: Field) { self.storage.balances.at(from).sub(amount).deliver(MessageDelivery.ONCHAIN_CONSTRAINED); self.storage.balances.at(to).add(amount).deliver(MessageDelivery.ONCHAIN_CONSTRAINED); } ``` > [Source code: noir-projects/noir-contracts/contracts/app/token\_contract/src/main.nr#L283-L290](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L283-L290) ### Public function example[​](#public-function-example "Direct link to Public function example") transfer\_in\_public ``` #[authorize_once("from", "authwit_nonce")] #[external("public")] fn transfer_in_public(from: AztecAddress, to: AztecAddress, amount: u128, authwit_nonce: Field) { let from_balance = self.storage.public_balances.at(from).read().sub(amount); self.storage.public_balances.at(from).write(from_balance); let to_balance = self.storage.public_balances.at(to).read().add(amount); self.storage.public_balances.at(to).write(to_balance); } ``` > [Source code: noir-projects/noir-contracts/contracts/app/token\_contract/src/main.nr#L157-L166](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L157-L166) The macro parameters specify: * `"from"` - the parameter name containing the address that must have authorized the call * `"authwit_nonce"` - the parameter name containing the nonce for replay protection ## Setting authorization from contracts[​](#setting-authorization-from-contracts "Direct link to Setting authorization from contracts") When a contract needs to authorize another contract to act on its behalf, use `set_authorized` to update the auth registry. This is common in bridge contracts where contract A authorizes contract B to perform actions. authwit\_uniswap\_set ``` // This helper method approves the bridge to burn this contract's funds and exits the input asset to L1 // Assumes contract already has funds. // Assume `token` relates to `token_bridge` (ie token_bridge.token == token) // Note that private can't read public return values so created an `only_self` public that handles everything // this method is used for both private and public swaps. #[external("public")] #[only_self] fn _approve_bridge_and_exit_input_asset_to_L1(token: AztecAddress, token_bridge: AztecAddress, amount: u128) { // Since we will authorize and instantly spend the funds, all in public, we can use the same nonce // every interaction. In practice, the authwit should be squashed, so this is also cheap! let authwit_nonce = 0xdeadbeef; let selector = FunctionSelector::from_signature("burn_public((Field),u128,Field)"); let message_hash = compute_authwit_message_hash_from_call( token_bridge, token, self.context.chain_id(), self.context.version(), selector, [self.address.to_field(), amount as Field, authwit_nonce], ); // We need to make a call to update it. set_authorized(self.context, message_hash, true); let this_portal_address = self.storage.portal_address.read(); // Exit to L1 Uniswap Portal ! self.call(TokenBridge::at(token_bridge).exit_to_l1_public( this_portal_address, amount, this_portal_address, authwit_nonce, )); } ``` > [Source code: noir-projects/noir-contracts/contracts/app/uniswap\_contract/src/main.nr#L152-L187](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/uniswap_contract/src/main.nr#L152-L187) Key steps: 1. Compute the message hash using `compute_authwit_message_hash_from_call` 2. Call `set_authorized` to store the approval in the registry 3. Execute the authorized action When authorization and consumption happen in the same transaction, state changes are squashed, saving gas. ## Canceling authwits[​](#canceling-authwits "Direct link to Canceling authwits") Users can revoke an authwit before it's used by emitting its nullifier: cancel\_authwit ``` #[external("private")] fn cancel_authwit(inner_hash: Field) { let on_behalf_of = self.msg_sender(); let nullifier = compute_authwit_nullifier(on_behalf_of, inner_hash); self.context.push_nullifier(nullifier); } ``` > [Source code: noir-projects/noir-contracts/contracts/app/token\_contract/src/main.nr#L274-L281](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L274-L281) note The cancel transaction must be finalized before any transaction attempts to use the authwit. If both are pending simultaneously, the outcome depends on which the sequencer includes first. ## Next steps[​](#next-steps "Direct link to Next steps") * [Using authwits in aztec.js](/developers/docs/aztec-js/how_to_use_authwit.md) - Create and manage authwits from your client application * [Authentication Witnesses concepts](/developers/docs/foundational-topics/advanced/authwit.md) - Deeper explanation of the authwit mechanism --- # Calling Other Contracts This guide shows you how to call functions in other contracts from your Aztec smart contracts. ## Add the target contract as a dependency[​](#add-the-target-contract-as-a-dependency "Direct link to Add the target contract as a dependency") Add the contract you want to call to your `Nargo.toml` dependencies: ``` [dependencies] token = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v4.3.1", directory="noir-projects/noir-contracts/contracts/app/token_contract" } ``` Then import the contract interface at the top of your contract file: ``` use token::Token; ``` ## Call contract functions[​](#call-contract-functions "Direct link to Call contract functions") Use `self.call()` to call functions on other contracts: ``` self.call(Token::at(token_address).transfer(recipient, amount)); ``` The pattern is: 1. Form the call: `Contract::at(address).function_name(args)` 2. Execute it: `self.call(...)` or `self.view(...)` for read-only calls ### Private-to-private calls[​](#private-to-private-calls "Direct link to Private-to-private calls") private\_call ``` let _ = self.call(Token::at(stable_coin).burn_private(from, amount, authwit_nonce)); ``` > [Source code: noir-projects/noir-contracts/contracts/app/lending\_contract/src/main.nr#L218-L220](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/lending_contract/src/main.nr#L218-L220) ### Public-to-public calls[​](#public-to-public-calls "Direct link to Public-to-public calls") From a public function, call other public functions directly: ``` self.call(Token::at(token_address).transfer_in_public(recipient, amount)); ``` Capture return values by assigning the result: ``` let balance = self.view(Token::at(token_address).balance_of_public(account)); ``` Use `self.view()` for read-only calls that cannot modify state. ### Private-to-public calls[​](#private-to-public-calls "Direct link to Private-to-public calls") From a private function, enqueue public function calls for later execution: ``` self.enqueue(Token::at(token_address).mint_to_public(recipient, amount)); ``` info Public functions execute after all private execution completes. Return values are not available in the private context. Learn more about [call types](/developers/docs/foundational-topics/call_types.md). --- # Contract Artifacts Compiling an Aztec contract produces a contract artifact file (`.json`) containing everything needed to interact with that contract: its name, functions, their interfaces, and compiled bytecode. Since private function bytecode is never published to the network, you need this artifact file to call private functions. Most developers don't need this When you [compile a contract](/developers/docs/aztec-nr/compiling_contracts.md) and use [`aztec codegen`](/developers/docs/aztec-js/how_to_deploy_contract.md#generate-typescript-bindings), you get type-safe TypeScript classes that handle artifacts automatically. This page is useful if you're: * Building custom tooling around Aztec contracts * Debugging compilation or deployment issues * Understanding what data is available in artifacts ## Where to Find Artifacts[​](#where-to-find-artifacts "Direct link to Where to Find Artifacts") After running `aztec compile`, artifacts are output to the `target/` directory: ``` target/ └── my_contract-MyContract.json # Contract artifact ``` Use `aztec codegen` to generate TypeScript bindings from these artifacts for type-safe contract interaction. ## Contract Artifact Structure[​](#contract-artifact-structure "Direct link to Contract Artifact Structure") A contract artifact contains: * **`name`**: The contract name as defined in Noir * **`functions`**: Array of function artifacts (private, public dispatch, and utility functions) * **`nonDispatchPublicFunctions`**: Public function ABIs (excluding the dispatch function) * **`outputs`**: Exported structs and globals from the contract * **`storageLayout`**: Storage slot mappings for contract state * **`fileMap`**: Source file mappings for debugging ## Function Properties[​](#function-properties "Direct link to Function Properties") Each function in the artifact includes: | Property | Description | | ----------------- | -------------------------------------------------------------------- | | `name` | Function name as defined in Noir | | `functionType` | One of `private`, `public`, or `utility` | | `isOnlySelf` | If `true`, function can only be called from within the same contract | | `isStatic` | If `true`, function cannot alter state | | `isInitializer` | If `true`, function can be used as a constructor | | `parameters` | Array of input parameters with name, type, and visibility | | `returnTypes` | Array of return value types | | `errorTypes` | Custom error types the function can throw | | `bytecode` | Compiled ACIR bytecode (base64 encoded) | | `verificationKey` | Verification key for private functions (optional) | | `debugSymbols` | Compressed debug information linking to source code | ### Function Types[​](#function-types "Direct link to Function Types") * **`private`**: Executed and proved locally by the client. Bytecode is not published to the network. * **`public`**: Executed and proved by the sequencer. Bytecode is published to the network. * **`utility`**: Executed locally to compute information (e.g., view functions). Cannot be called in transactions. ## Parameter and Return Types[​](#parameter-and-return-types "Direct link to Parameter and Return Types") Parameters and return values use these type definitions: | Type | Description | | --------- | ----------------------------------------------------------------- | | `field` | A field element in the BN254 curve's scalar field | | `boolean` | True/false value | | `integer` | Whole number with `sign` (`signed`/`unsigned`) and `width` (bits) | | `array` | Collection of elements with `length` and element `type` | | `string` | Character sequence with fixed `length` | | `struct` | Composite type with named `fields` and a `path` identifier | | `tuple` | Unnamed composite type with ordered `fields` | Parameter visibility can be `public`, `private`, or `databus`. ## Next Steps[​](#next-steps "Direct link to Next Steps") * [Compile contracts](/developers/docs/aztec-nr/compiling_contracts.md) to generate artifacts * [Deploy contracts](/developers/docs/aztec-js/how_to_deploy_contract.md) using generated TypeScript bindings * [Send transactions](/developers/docs/aztec-js/how_to_send_transaction.md) to interact with deployed contracts --- # Contract Structure High-level structure of how Aztec smart contracts including the different components. ## Directory structure[​](#directory-structure "Direct link to Directory structure") When you create a new project with `aztec new my_project`, it generates a two-crate Noir workspace: a contract crate for your smart contract code and a sibling test crate for Noir tests. layout of an aztec contract project ``` ─── my_project ├── Nargo.toml <-- workspace file ([workspace] members) ├── my_project_contract │ ├── Nargo.toml <-- contract package (type = "contract") │ └── src │ └── main.nr <-- your contract └── my_project_test ├── Nargo.toml <-- test package (type = "lib") └── src └── lib.nr <-- Noir tests ``` The top-level `Nargo.toml` is a workspace file. Contract dependencies live in `my_project_contract/Nargo.toml` (with `type = "contract"`). Tests live in the separate `my_project_test` crate and import the contract by package name (for example, `use my_project_contract::MyContract;`) — see [Testing Contracts](/developers/docs/aztec-nr/testing_contracts.md). To add another contract to the same workspace, run `aztec new ` from inside the workspace directory; this adds a new `_contract` and `_test` crate pair. To initialize a project inside an existing empty directory, `cd` into it and run `aztec init`, which scaffolds the same two-crate layout pre-populated with a runnable [Counter example](/developers/docs/tutorials/contract_tutorials/counter_contract.md) (use `aztec new` if you want a blank starting point instead). See the vanilla Noir docs for [more info on packages](https://noir-lang.org/docs/noir/modules_packages_crates/crates_and_packages). ## Contract block[​](#contract-block "Direct link to Contract block") All contracts start with importing the required files and declaring a contract using the `contract` keyword: ``` // import the `aztec` macro from Aztec.nr use aztec::macros::aztec; // use the 'contract' keyword to declare a contract, applying the `aztec` macro #[aztec] pub contract MyContract { // contract code here } ``` By convention, contracts are named in `PascalCase`. The `#[aztec]` macro performs a lot of the low-level operations required to take a circuit language like Noir and build smart contracts out of it - including automatically creating external interfaces, inserting standard contract functions, etc. **All Aztec smart contracts must have this macro applied to them.** **Note:** each Noir crate (package) can only have *a single* contract. If you are writing a multi-contract system, then each of them needs to be in their own separate crate. To learn more about crates and packages, visit the [Noir documentation](https://noir-lang.org/docs/noir/modules_packages_crates/crates_and_packages). ## Imports[​](#imports "Direct link to Imports") Aside from the [`#[aztec]`](/aztec-nr-api/mainnet/noir_aztec/macros/fn.aztec) macro import, all other imports need to go *inside* the `contract` block - this is because `contract` acts like `mod`, creating a new [module](https://noir-lang.org/docs/noir/modules_packages_crates/modules). ``` use aztec::macros::aztec; #[aztec] pub contract MyContract { // other imports go here use aztec::state_vars::{PrivateMutable, PrivateSet}; } ``` **Note:** [Noir's VSCode extension](/developers/docs/aztec-nr/installation.md) is able to take care of most imports and put them in the correct place automatically. ## State Variables[​](#state-variables "Direct link to State Variables") With the boilerplate out of the way, it is now the time to begin defining the contract logic. It is recommended to start development by understanding the shape the *state* of the contract will have: * Which values will be private? * Which will be public? * What properties are required (is mutability or immutability needed? Is there a single global value, like a token total supply, or does each user get one, like a balance?). In Solidity, this is done by simply declaring variables inside of the contract, like so: ``` contract MyContract { uint128 public my_public_state_variable; } ``` In Aztec, defining state requires a few more steps, as there are both private and public variables (where these keywords refer to the privacy of the variable rather than their accessibility), and multiple *kinds* of state variables. We define state using a [`struct`](https://noir-lang.org/docs/noir/concepts/data_types/structs) that will hold the entire contract state. We call this struct *the storage struct*, and each variable inside this struct is called [*a state variable*.](/developers/docs/aztec-nr/framework-description/state_variables.md) ``` use aztec::macros::aztec; #[aztec] pub contract MyContract { use aztec::{ macros::storage, state_vars::{Owned, PrivateMutable, PublicMutable} }; use uint_note::UintNote; // The storage struct must be named `Storage` and must have the `#[storage]` macro applied to it. // This struct must also have a generic type called C or Context. #[storage] struct Storage { // A private numeric value which can change over time. This value will be hidden, and only those with the secret can know its current value. my_private_state_variable: Owned, Context>, // A public numeric value which can change over time. This value will be known to everyone and is equivalent to the Solidity example above. my_public_state_variable: PublicMutable, } } ``` ## Events[​](#events "Direct link to Events") Like Solidity contracts, Aztec contracts can define events to notify that some state has changed. However, in Aztec, events can also be emitted privately, in which case only some users will learn of the event. [Events](/developers/docs/aztec-nr/framework-description/events_and_logs.md) are a struct marked with the `#[event]` macro: ``` #[event] struct Transfer { from: AztecAddress, to: AztecAddress, amount: u128, } ``` ## Functions[​](#functions "Direct link to Functions") Contracts are interacted with by invoking their `external` [functions](/developers/docs/aztec-nr/framework-description/functions.md). There are three kinds of `external` functions: * External **private** functions, which reveal nothing about their execution and are executed off chain on the user's device, producing a zero-knowledge proof of execution that is sent to the network as part of a transaction. * External **public** functions, which nodes in the network invoke publicly (like any `external` Solidity contract function). * External **utility** functions, which are executed off chain on the user's device by applications in order to display useful information, e.g. retrieve contract state. These are never part of a transaction. ``` use aztec::macros::aztec; #[aztec] contract MyContract { use aztec::macros::functions::external; use aztec::protocol::address::AztecAddress; #[external("private")] fn my_private_function(parameter_a: u128, parameter_b: AztecAddress) { // ... } #[external("public")] fn my_public_function(parameter_a: u128, parameter_b: AztecAddress) { // ... } #[external("utility")] unconstrained fn my_utility_function(parameter_a: u128, parameter_b: AztecAddress) { // ... } } ``` Contracts can also define `internal` functions, which cannot be called by other contracts (like any `internal` Solidity function). These exist to help organize your code, reuse functionality, etc. ### Current Limitations[​](#current-limitations "Direct link to Current Limitations") All `#[external]` contract functions must be defined *directly inside the `contract` block*, that is, in the same file. It is possible to define `#[internal]` and helper functions in `mod`s in other files, but not `#[external]` functions. **Noir does not feature inheritance** nor is there currently any other mechanism to extend and reuse contract logic. For example, you cannot take a token contract and extend it to add minting functionality, or reuse it in a liquidity pool. Like Vyper, the entire logic must live in a single file. We expect to lift some of these restrictions sometime after the release of Noir 1.0. ## Next steps[​](#next-steps "Direct link to Next steps") * [Define functions](/developers/docs/aztec-nr/framework-description/functions.md) - Learn about private, public, and utility functions * [Define storage](/developers/docs/aztec-nr/framework-description/state_variables.md) - Work with persistent state variables * [Compile your contract](/developers/docs/aztec-nr/compiling_contracts.md) - Build your contract artifact --- # Contract Upgrades Each contract instance refers to a contract class ID for its code. Upgrading a contract's implementation involves updating its current class ID to a new class ID, while retaining the original class ID for address verification. ## Original class ID[​](#original-class-id "Direct link to Original class ID") A contract stores the original contract class it was instantiated with. This original class ID is used when calculating and verifying the contract's [address](/developers/docs/foundational-topics/contract_creation.md#instance-address) and remains unchanged even if a contract is upgraded. ## Current class ID[​](#current-class-id "Direct link to Current class ID") When a contract is first deployed, its current class ID equals its original class ID. The current class ID determines which code implementation the contract executes. During an upgrade: * The original class ID remains unchanged * The current class ID is updated to the new implementation * All contract state and data are preserved ## How to upgrade[​](#how-to-upgrade "Direct link to How to upgrade") Contract upgrades must be initiated by the contract itself calling the `ContractInstanceRegistry`: ``` use aztec::protocol::{ constants::CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS, contract_class_id::ContractClassId, }; use contract_instance_registry::ContractInstanceRegistry; #[external("private")] fn update_to(new_class_id: ContractClassId) { self.enqueue( ContractInstanceRegistry::at(CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS) .update(new_class_id) ); } ``` info To use the `ContractInstanceRegistry`, add this dependency to your `Nargo.toml`: ``` contract_instance_registry = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v4.3.1", directory="noir-projects/noir-contracts/contracts/protocol_interface/contract_instance_registry_interface" } ``` The `update` function in the registry is a public function, so you can enqueue it from a private function (as shown above) or call it directly from a public function. Access Control The example `update_to` function above has no access control, meaning anyone could call it to upgrade your contract. Production contracts should implement proper authorization checks to secure against malicious upgrades. Contract upgrades use a `DelayedPublicMutable` storage variable in the `ContractInstanceRegistry`, applying to both public and private functions. Upgrades have a delay before taking effect. The default delay is `86400` seconds (one day) but can be configured: ``` #[external("private")] fn set_update_delay(new_delay: u64) { self.enqueue( ContractInstanceRegistry::at(CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS) .set_update_delay(new_delay) ); } ``` The `new_delay` parameter is in seconds. Changing the update delay is also subject to the previous delay, so the first delay change takes `86400` seconds to take effect. info The minimum update delay is `600` seconds. ### Transaction expiration[​](#transaction-expiration "Direct link to Transaction expiration") When sending a transaction, the expiration timestamp is calculated as the current block timestamp plus the minimum update delay of all contracts you interact with. For example: * If you interact with contracts having delays of 1000 and 10000 seconds, expiration is current timestamp + 1000 seconds * If a contract has a pending upgrade in 100 seconds, expiration would be current timestamp + 99 seconds Other `DelayedPublicMutable` storage variables in your transaction may reduce the expiration timestamp further. note Only deployed contract instances can upgrade or change their upgrade delay. This restriction may be lifted in the future. ### Upgrade process[​](#upgrade-process "Direct link to Upgrade process") 1. **Register the new implementation**: Register the new contract class if it contains public functions. The new implementation must maintain state variable compatibility with the original contract. 2. **Perform the upgrade**: Call the update function with the new contract class ID. The contract's original class ID remains unchanged while the current class ID updates to the new implementation. 3. **Wait for the delay**: The upgrade takes effect after the configured delay period. 4. **Verify the upgrade**: After the delay, the contract executes functions from the new implementation. The contract address remains the same since it's based on the original class ID. ### Interacting with an upgraded contract[​](#interacting-with-an-upgraded-contract "Direct link to Interacting with an upgraded contract") The PXE stores contract instances and classes locally. After a contract upgrades, you must register the new artifact with the wallet before interacting with it: ``` import { getContractClassFromArtifact } from '@aztec/aztec.js/contracts'; import { publishContractClass } from '@aztec/aztec.js/deployment'; // Deploy the original contract (use .wait() to get both contract and instance) const { contract, instance } = await UpdatableContract.deploy(wallet, ...args) .send({ from: accountAddress }) .wait(); // Publish the new contract class (required before upgrading) await (await publishContractClass(wallet, UpdatedContractArtifact)) .send({ from: accountAddress }) .wait(); // Get the new contract class ID const updatedContractClassId = ( await getContractClassFromArtifact(UpdatedContractArtifact) ).id; // Trigger the upgrade await contract.methods .update_to(updatedContractClassId) .send({ from: accountAddress }) .wait(); // Wait for the upgrade delay to pass... // Register the new artifact with the wallet await wallet.registerContract(instance, UpdatedContract.artifact); // Create a contract instance with the new artifact const updatedContract = UpdatedContract.at(contract.address, wallet); ``` If you try to register a contract artifact that doesn't match the current contract class, the registration will fail. ### Security considerations[​](#security-considerations "Direct link to Security considerations") 1. **Access control**: Implement proper access controls for upgrade functions. Consider using `set_update_delay` to customize the delay for your security requirements. 2. **State compatibility**: Ensure the new implementation is compatible with existing state. Maintain the same storage layout to prevent data corruption. 3. **Testing**: Test upgrades thoroughly in a development environment. Verify all existing functionality works with the new implementation. --- # Custom notes This guide shows you how to create custom note types for storing specialized private data in your Aztec contracts. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Basic understanding of [Aztec private state and notes](/developers/docs/foundational-topics/state_management.md) * Aztec development environment set up ## When to create custom notes[​](#when-to-create-custom-notes "Direct link to When to create custom notes") You may want to create your own note type if you need to: * Store specific data types not provided by built-in note libraries * Combine multiple fields into a single note (e.g., game cards with multiple attributes) * Implement custom nullifier schemes for advanced use cases Built-in Note Types Aztec.nr provides pre-built note types for common use cases: **UintNote** - For numeric values like token balances (supports partial notes): ``` # In Nargo.toml uint_note = { git="https://github.com/AztecProtocol/aztec-nr", tag="v4.3.1", directory="uint-note" } ``` **FieldNote** - For storing single Field values: ``` # In Nargo.toml field_note = { git="https://github.com/AztecProtocol/aztec-nr", tag="v4.3.1", directory="field-note" } ``` **AddressNote** - For storing Aztec addresses: ``` # In Nargo.toml address_note = { git="https://github.com/AztecProtocol/aztec-nr", tag="v4.3.1", directory="address-note" } ``` ## Creating a custom note[​](#creating-a-custom-note "Direct link to Creating a custom note") Define your custom note with the `#[note]` macro: nft\_note\_struct ``` use aztec::{macros::notes::note, protocol::traits::Packable}; #[derive(Eq, Packable)] #[note] pub struct NFTNote { pub token_id: Field, } ``` > [Source code: docs/examples/contracts/nft/src/nft.nr#L1-L9](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/nft/src/nft.nr#L1-L9) The `#[note]` macro generates the following for your struct: * `NoteType` trait - Provides a unique type ID for the note * `NoteHash` trait - Handles note hash and nullifier computation * `NoteProperties` - Enables field selection when querying notes ### Required traits[​](#required-traits "Direct link to Required traits") Your note struct must derive: * `Packable` - Required by the `#[note]` macro for serialization * `Eq` - Required by storage types like `PrivateSet` for note comparisons The `#[note]` macro handles the `NoteType`, `NoteHash`, and `NoteProperties` traits automatically. ### How note hashing works[​](#how-note-hashing-works "Direct link to How note hashing works") When a note is inserted, the `#[note]` macro generates code that computes the note hash by combining: 1. **Your packed note data** - The fields you define in your struct 2. **Owner address** - Provided by the storage variable 3. **Storage slot** - Determined by the storage layout 4. **Randomness** - Generated automatically to prevent brute-force attacks This happens automatically - you don't need to include owner or randomness fields in your struct. ## Using notes in storage[​](#using-notes-in-storage "Direct link to Using notes in storage") Notes are stored using `Owned>` which manages note ownership: ``` use aztec::{ macros::storage::storage, state_vars::{Owned, PrivateSet}, }; #[storage] struct Storage { // Collection of notes, indexed by owner nfts: Owned, Context>, } ``` ### Inserting notes[​](#inserting-notes "Direct link to Inserting notes") mint ``` #[external("private")] fn mint(to: AztecAddress, token_id: Field) { assert( self.storage.minter.read().eq(self.msg_sender()), "caller is not the authorized minter", ); // we create an NFT note and insert it to the PrivateSet - a collection of notes meant to be read in private let new_nft = NFTNote { token_id }; self.storage.owners.at(to).insert(new_nft).deliver(MessageDelivery.ONCHAIN_CONSTRAINED); // calling the internal public function above to indicate that the NFT is taken self.enqueue_self._mark_nft_exists(token_id, true); } ``` > [Source code: docs/examples/contracts/nft/src/main.nr#L50-L65](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/nft/src/main.nr#L50-L65) ### Reading and removing notes[​](#reading-and-removing-notes "Direct link to Reading and removing notes") Use `pop_notes` to read and nullify notes atomically. This is the recommended pattern for most use cases: burn ``` #[external("private")] fn burn(from: AztecAddress, token_id: Field) { assert( self.storage.minter.read().eq(self.msg_sender()), "caller is not the authorized minter", ); // from the NFTNote properties, selects token_id and compares it against the token_id to be burned let options = NoteGetterOptions::new() .select(NFTNote::properties().token_id, Comparator.EQ, token_id) .set_limit(1); let notes = self.storage.owners.at(from).pop_notes(options); assert(notes.len() == 1, "NFT not found"); self.enqueue_self._mark_nft_exists(token_id, false); } ``` > [Source code: docs/examples/contracts/nft/src/main.nr#L75-L92](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/nft/src/main.nr#L75-L92) warning There's also a `get_notes` function that reads without nullifying, but use it with caution - the returned notes may have already been spent in another transaction. ## Custom note hashing[​](#custom-note-hashing "Direct link to Custom note hashing") Most notes should use the standard `#[note]` macro. Use `#[custom_note]` only when you need: * Custom nullifier schemes (e.g., notes spendable by anyone with a secret, not tied to an owner) * Partial notes that can be completed in public execution * Non-standard hash computation for specific security requirements With `#[custom_note]`, you must implement the `NoteHash` trait yourself: ``` use aztec::{ context::PrivateContext, keys::getters::{get_nhk_app, get_public_keys, try_get_public_keys}, macros::notes::custom_note, note::note_interface::NoteHash, protocol::{ address::AztecAddress, constants::{DOM_SEP__NOTE_HASH, DOM_SEP__NOTE_NULLIFIER}, hash::poseidon2_hash_with_separator, traits::Packable, }, }; #[derive(Eq, Packable)] #[custom_note] pub struct CustomHashNote { pub data: Field, } impl NoteHash for CustomHashNote { fn compute_note_hash( self, owner: AztecAddress, storage_slot: Field, randomness: Field, ) -> Field { // Custom hash computation poseidon2_hash_with_separator( [self.data, owner.to_field(), storage_slot, randomness], DOM_SEP__NOTE_HASH, ) } fn compute_nullifier( self, context: &mut PrivateContext, owner: AztecAddress, note_hash_for_nullification: Field, ) -> Field { // Standard nullifier using owner's nullifier hiding key let owner_npk_m = get_public_keys(owner).npk_m; let secret = context.request_nhk_app(owner_npk_m.hash()); poseidon2_hash_with_separator( [note_hash_for_nullification, secret], DOM_SEP__NOTE_NULLIFIER, ) } unconstrained fn compute_nullifier_unconstrained( self, owner: AztecAddress, note_hash_for_nullification: Field, ) -> Option { try_get_public_keys(owner).map(|public_keys| { let secret = get_nhk_app(public_keys.npk_m.hash()); poseidon2_hash_with_separator( [note_hash_for_nullification, secret], DOM_SEP__NOTE_NULLIFIER, ) }) } } ``` Naming note The secret returned by `request_nhk_app` is the **nullifier hiding key** (abbreviated `nhk`). Older docs and code comments may call it the "nullifier secret key" (`nsk`) — these refer to the same key. Always use `request_nhk_app()` rather than computing this key yourself. ## Viewing notes (unconstrained)[​](#viewing-notes-unconstrained "Direct link to Viewing notes (unconstrained)") For read-only queries without constraints: view\_notes ``` #[external("utility")] unconstrained fn get_private_nfts(owner: AztecAddress, page_index: u32) -> ([Field; MAX_NOTES_PER_PAGE], bool) { let offset = page_index * MAX_NOTES_PER_PAGE; let options = NoteViewerOptions::new().set_offset(offset); let notes = self.storage.private_nfts.at(owner).view_notes(options); let mut owned_nft_ids = [0; MAX_NOTES_PER_PAGE]; for i in 0..options.limit { if i < notes.len() { owned_nft_ids[i] = notes.get_unchecked(i).token_id; } } let page_limit_reached = notes.len() == options.limit; (owned_nft_ids, page_limit_reached) } ``` > [Source code: noir-projects/noir-contracts/contracts/app/nft\_contract/src/main.nr#L255-L272](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr#L255-L272) ## Further reading[​](#further-reading "Direct link to Further reading") * [What the `#[note]` macro does](/developers/docs/aztec-nr/framework-description/functions/attributes.md#implementing-notes) * [Note getter options](/developers/docs/aztec-nr/framework-description/advanced/how_to_retrieve_filter_notes.md) * [Storage types](/developers/docs/foundational-topics/state_management.md) * [Macros reference](/developers/docs/aztec-nr/framework-description/macros.md) --- # Aztec.nr Dependencies This page lists the available Aztec.nr libraries. Add dependencies to the `[dependencies]` section of your `Nargo.toml`: ``` [dependencies] aztec = { git="https://github.com/AztecProtocol/aztec-nr/", tag="v4.3.1", directory="aztec" } # Add other libraries as needed ``` ## Core[​](#core "Direct link to Core") ### Aztec (required)[​](#aztec-required "Direct link to Aztec (required)") ``` aztec = { git="https://github.com/AztecProtocol/aztec-nr/", tag="v4.3.1", directory="aztec" } ``` The core Aztec library required for every Aztec.nr smart contract. ## Note Types[​](#note-types "Direct link to Note Types") ### Address Note[​](#address-note "Direct link to Address Note") ``` address_note = { git="https://github.com/AztecProtocol/aztec-nr/", tag="v4.3.1", directory="address-note" } ``` Provides `AddressNote`, a note type for storing `AztecAddress` values. ### Field Note[​](#field-note "Direct link to Field Note") ``` field_note = { git="https://github.com/AztecProtocol/aztec-nr/", tag="v4.3.1", directory="field-note" } ``` Provides `FieldNote`, a note type for storing a single `Field` value. ### Uint Note[​](#uint-note "Direct link to Uint Note") ``` uint_note = { git="https://github.com/AztecProtocol/aztec-nr/", tag="v4.3.1", directory="uint-note" } ``` Provides `UintNote`, a note type for storing `u128` values. Also includes `PartialUintNote` for partial note workflows where the value is completed in public execution. ## State Variables[​](#state-variables "Direct link to State Variables") ### Balance Set[​](#balance-set "Direct link to Balance Set") ``` balance_set = { git="https://github.com/AztecProtocol/aztec-nr/", tag="v4.3.1", directory="balance-set" } ``` Provides `BalanceSet`, a state variable for managing private balances. Includes helper functions for adding, subtracting, and querying balances. ## Utilities[​](#utilities "Direct link to Utilities") ### Compressed String[​](#compressed-string "Direct link to Compressed String") ``` compressed_string = { git="https://github.com/AztecProtocol/aztec-nr/", tag="v4.3.1", directory="compressed-string" } ``` Provides `CompressedString` and `FieldCompressedString` utilities for working with compressed string data. ## Updating your aztec dependencies[​](#updating-your-aztec-dependencies "Direct link to Updating your aztec dependencies") When `aztec compile` warns that your aztec dependency tag does not match the CLI version, update the `tag` field in every Aztec.nr entry in your `Nargo.toml` to match the CLI version you are running. For example, if your CLI is `vv4.3.1`, change: ``` aztec = { git="https://github.com/AztecProtocol/aztec-nr/", tag="v", directory="aztec" } ``` to: ``` aztec = { git="https://github.com/AztecProtocol/aztec-nr/", tag="vv4.3.1", directory="aztec" } ``` Repeat for every other Aztec.nr dependency in your `Nargo.toml` (e.g. `address_note`, `balance_set`, etc.). You can check your current CLI version with `aztec --version`. --- # Ethereum<>Aztec Messaging This guide covers cross-chain communication between Ethereum (L1) and Aztec (L2) using portal contracts. Aztec uses an Inbox/Outbox pattern for cross-chain messaging. Messages sent from L1 are inserted into the `Inbox` contract and later consumed on L2. Messages sent from L2 are inserted into the `Outbox` contract and later consumed on L1. Portal contracts are L1 contracts that facilitate this communication for your application. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * An Aztec contract project with `aztec-nr` dependency * Access to Ethereum development environment for L1 contracts * Deployed portal contract on L1 (see [token bridge tutorial](/developers/docs/tutorials/js_tutorials/token_bridge.md)) ## L1 to L2 messaging[​](#l1-to-l2-messaging "Direct link to L1 to L2 messaging") ### Send a message from L1[​](#send-a-message-from-l1 "Direct link to Send a message from L1") Use the `Inbox` contract's `sendL2Message` function: | Parameter | Type | Description | | ------------- | --------- | -------------------------------------------------- | | `_recipient` | `L2Actor` | L2 contract address and rollup version | | `_content` | `bytes32` | Hash of message content (use `Hash.sha256ToField`) | | `_secretHash` | `bytes32` | Hash of secret for message consumption | deposit\_public ``` /** * @notice Deposit funds into the portal and adds an L2 message which can only be consumed publicly on Aztec * @param _to - The aztec address of the recipient * @param _amount - The amount to deposit * @param _secretHash - The hash of the secret consumable message. The hash should be 254 bits (so it can fit in a * Field element) * @return The key of the entry in the Inbox and its leaf index */ function depositToAztecPublic(bytes32 _to, uint256 _amount, bytes32 _secretHash) external returns (bytes32, uint256) ``` > [Source code: l1-contracts/test/portals/TokenPortal.sol#L48-L60](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/l1-contracts/test/portals/TokenPortal.sol#L48-L60) Message availability L1 to L2 messages are not available immediately. The proposer batches messages from the Inbox and includes them in the next L2 block. You must wait for this before consuming the message on L2. ### Consume the message on L2[​](#consume-the-message-on-l2 "Direct link to Consume the message on L2") Call `consume_l1_to_l2_message` on the context. The `content` must match the hash sent from L1, and the `secret` must be the pre-image of the `secretHash`. Consuming a message emits a nullifier to prevent double-spending. The content hash must be computed identically on both L1 and L2. Create a shared library for your content hash functions—see [`token_portal_content_hash_lib`](https://github.com/AztecProtocol/aztec-packages/tree/v4.3.1/noir-projects/noir-contracts/contracts/app/token_portal_content_hash_lib) for an example. claim\_public ``` // Consumes a L1->L2 message and calls the token contract to mint the appropriate amount publicly #[external("public")] fn claim_public(to: AztecAddress, amount: u128, secret: Field, message_leaf_index: Field) { let content_hash = get_mint_to_public_content_hash(to, amount); let config = self.storage.config.read(); // Consume message and emit nullifier self.context.consume_l1_to_l2_message(content_hash, secret, config.portal, message_leaf_index); // Mint tokens self.call(Token::at(config.token).mint_to_public(to, amount)); } ``` > [Source code: noir-projects/noir-contracts/contracts/app/token\_bridge\_contract/src/main.nr#L49-L63](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/token_bridge_contract/src/main.nr#L49-L63) This function works in both public and private contexts. ## L2 to L1 messaging[​](#l2-to-l1-messaging "Direct link to L2 to L1 messaging") ### Send a message from L2[​](#send-a-message-from-l2 "Direct link to Send a message from L2") Call `message_portal` on the context to send messages to your L1 portal: exit\_to\_l1\_public ``` // Burns the appropriate amount of tokens and creates a L2 to L1 withdraw message publicly // Requires `msg.sender` to give approval to the bridge to burn tokens on their behalf using witness signatures #[external("public")] fn exit_to_l1_public( recipient: EthAddress, // ethereum address to withdraw to amount: u128, caller_on_l1: EthAddress, // ethereum address that can call this function on the L1 portal (0x0 if anyone can // call) authwit_nonce: Field, // nonce used in the approval message by `msg.sender` to let bridge burn their tokens on // L2 ) { let config = self.storage.config.read(); // Send an L2 to L1 message let content = get_withdraw_content_hash(recipient, amount, caller_on_l1); self.context.message_portal(config.portal, content); // Burn tokens self.call(Token::at(config.token).burn_public(self.msg_sender(), amount, authwit_nonce)); } ``` > [Source code: noir-projects/noir-contracts/contracts/app/token\_bridge\_contract/src/main.nr#L65-L86](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/token_bridge_contract/src/main.nr#L65-L86) This function works in both public and private contexts. ### Consume the message on L1[​](#consume-the-message-on-l1 "Direct link to Consume the message on L1") Use the `Outbox` contract to consume L2 messages. Message availability L2 to L1 messages are only available after the epoch proof is submitted to L1. Since multiple L2 blocks fit within an epoch, there may be a delay—especially if the message was sent near the start of an epoch. token\_portal\_withdraw ``` /** * @notice Withdraw funds from the portal * @dev Second part of withdraw, must be initiated from L2 first as it will consume a message from outbox * @param _recipient - The address to send the funds to * @param _amount - The amount to withdraw * @param _withCaller - Flag to use `msg.sender` as caller, otherwise address(0) * @param _epoch - The epoch the message is in * @param _leafIndex - The amount to withdraw * @param _path - Flag to use `msg.sender` as caller, otherwise address(0) * Must match the caller of the message (specified from L2) to consume it. */ function withdraw( address _recipient, uint256 _amount, bool _withCaller, Epoch _epoch, uint256 _leafIndex, bytes32[] calldata _path ) external { // The purpose of including the function selector is to make the message unique to that specific call. Note that // it has nothing to do with calling the function. DataStructures.L2ToL1Msg memory message = DataStructures.L2ToL1Msg({ sender: DataStructures.L2Actor(l2Bridge, rollupVersion), recipient: DataStructures.L1Actor(address(this), block.chainid), content: Hash.sha256ToField( abi.encodeWithSignature( "withdraw(address,uint256,address)", _recipient, _amount, _withCaller ? msg.sender : address(0) ) ) }); outbox.consume(message, _epoch, _leafIndex, _path); underlying.safeTransfer(_recipient, _amount); } ``` > [Source code: l1-contracts/test/portals/TokenPortal.sol#L112-L148](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/l1-contracts/test/portals/TokenPortal.sol#L112-L148) Getting the membership witness Compute the witness for the L2 to L1 message in TypeScript: ``` import { computeL2ToL1MembershipWitness } from "@aztec/stdlib/messaging"; import { computeL2ToL1MessageHash } from "@aztec/stdlib/hash"; const l2ToL1Message = computeL2ToL1MessageHash({ l2Sender: l2BridgeAddress, l1Recipient: EthAddress.fromString(portalAddress), content: withdrawContentHash, rollupVersion: new Fr(version), chainId: new Fr(chainId), }); const witness = await computeL2ToL1MembershipWitness( aztecNode, txReceipt.blockNumber!, l2ToL1Message ); // Use witness.leafIndex and witness.siblingPath for the L1 consume call ``` ## Example implementations[​](#example-implementations "Direct link to Example implementations") * [Token Portal (L1)](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/l1-contracts/test/portals/TokenPortal.sol) * [Token Bridge (L2)](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/token_bridge_contract/src/main.nr) ## Next steps[​](#next-steps "Direct link to Next steps") Follow the [token bridge tutorial](/developers/docs/tutorials/js_tutorials/token_bridge.md) for a complete implementation example. --- # Events and Logs Events allow contracts to communicate with offchain applications. Private events are encrypted and delivered to specific recipients, while public events are visible to everyone. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * An Aztec contract project set up with `aztec-nr` dependency * Understanding of private vs public functions in Aztec ## Define an event[​](#define-an-event "Direct link to Define an event") Declare events using the `#[event]` attribute: ``` #[event] struct Transfer { from: AztecAddress, to: AztecAddress, amount: u128, } ``` ## Emit private events[​](#emit-private-events "Direct link to Emit private events") In private functions, emit events using `self.emit()` and deliver them to recipients: ``` use aztec::messages::message_delivery::MessageDelivery; #[external("private")] fn transfer(to: AztecAddress, amount: u128) { let from = self.msg_sender(); // ... transfer logic ... self.emit(Transfer { from, to, amount }).deliver_to( to, MessageDelivery.ONCHAIN_UNCONSTRAINED, ); } ``` warning You **must** call `deliver_to()` on the returned `EventMessage`. If you don't, the event information is lost forever. The compiler will warn you about unused `EventMessage` values. ### Deliver to multiple recipients[​](#deliver-to-multiple-recipients "Direct link to Deliver to multiple recipients") You can deliver the same event to multiple recipients with different delivery modes: ``` let message = self.emit(Transfer { from, to, amount }); message.deliver_to(from, MessageDelivery.OFFCHAIN); message.deliver_to(to, MessageDelivery.ONCHAIN_CONSTRAINED); ``` The `MessageDelivery` options are: * **`ONCHAIN_CONSTRAINED`** - Constrained encryption with onchain delivery. Slowest proving but provides cryptographic guarantees that recipients can decrypt messages. * **`ONCHAIN_UNCONSTRAINED`** - Unconstrained encryption with onchain delivery. Faster proving, but trusts the sender to encrypt correctly. * **`OFFCHAIN`** - Unconstrained encryption with offchain delivery. Lowest cost, but requires custom infrastructure to deliver messages to recipients. note Emitting private events is optional. Onchain delivery publishes encrypted data to Ethereum blobs, inheriting Ethereum's data availability guarantees. You can choose to share information offchain instead. ## Emit public events[​](#emit-public-events "Direct link to Emit public events") In public functions, emit events using `self.emit()`: ``` #[external("public")] fn update_value(value: Field) { // ... update logic ... self.emit(ValueUpdated { value }); } ``` Public events are emitted as plaintext logs, similar to Solidity events. ## Emit unstructured public logs[​](#emit-unstructured-public-logs "Direct link to Emit unstructured public logs") For unstructured data, use `emit_public_log_unsafe` directly on the context. It takes a tag (placed at the first field of the emitted log, which nodes use to index logs) followed by the data: ``` self.context.emit_public_log_unsafe(0, "My message"); self.context.emit_public_log_unsafe(0, [1, 2, 3]); ``` The tag should be domain-separated to prevent collisions with unrelated log types. Prefer `self.emit(event)` where possible, which handles tagging automatically. ## Query public logs[​](#query-public-logs "Direct link to Query public logs") Query public logs from offchain applications using the Aztec node: ``` const fromBlock = await node.getBlockNumber(); const logFilter = { fromBlock, toBlock: fromBlock + 1, }; const publicLogs = (await node.getPublicLogs(logFilter)).logs; ``` ## Cost considerations[​](#cost-considerations "Direct link to Cost considerations") Event data published onchain is stored in Ethereum blobs, which incurs costs. Consider: * Use `OFFCHAIN` delivery for lower costs when you have custom delivery infrastructure * Only emit events when necessary for your application's functionality ## Next steps[​](#next-steps "Direct link to Next steps") * Learn about [storage](/developers/docs/aztec-nr/framework-description/state_variables.md) to persist data in your contracts * Explore [calling other contracts](/developers/docs/aztec-nr/framework-description/calling_contracts.md) for cross-contract interactions * Understand [cross-chain communication](/developers/docs/aztec-nr/framework-description/ethereum_aztec_messaging.md) between Ethereum and Aztec --- # Defining Functions Functions serve as the building blocks of smart contracts. Functions can be either **public**, ie they are publicly available for anyone to see and can directly interact with public state, or **private**, meaning they are executed completely client-side in the [PXE](/developers/docs/foundational-topics/pxe.md). Read more about how private functions work [here](/developers/docs/aztec-nr/framework-description/functions/attributes.md#private-functions-externalprivate). Currently, any function is "mutable" in the sense that it might alter state. However, we also support static calls, similarly to EVM. A static call is essentially a call that does not alter state (it keeps state static). ## Initializer functions[​](#initializer-functions "Direct link to Initializer functions") Smart contracts may have one, or many, initializer functions which are called when the contract is deployed. Initializers are regular functions that set an "initialized" flag (a nullifier) for the contract. A contract can only be initialized once, and contract functions can only be called after the contract has been initialized, much like a constructor. However, if a contract defines no initializers, it can be called at any time. Additionally, you can define as many initializer functions in a contract as you want, both private and public. ## Oracles[​](#oracles "Direct link to Oracles") There are also special oracle functions, which can get data from outside of the smart contract. In the context of Aztec, oracles are often used to get user-provided inputs. ## Learn more about functions[​](#learn-more-about-functions "Direct link to Learn more about functions") * [How function visibility works in Aztec](/developers/docs/aztec-nr/framework-description/functions/visibility.md) * How to write an [initializer function](/developers/docs/aztec-nr/framework-description/functions/how_to_define_functions.md#define-initializer-functions) * [Oracles](/developers/docs/aztec-nr/framework-description/advanced/protocol_oracles.md) and how Aztec smart contracts might use them * [How functions work under the hood](/developers/docs/aztec-nr/framework-description/functions/attributes.md) Find a function macros reference [here](/developers/docs/aztec-nr/framework-description/macros.md) --- # Attributes and Macros This page documents the attributes (macros) available in Aztec.nr for defining contract functions, storage, and notes. ## Quick reference[​](#quick-reference "Direct link to Quick reference") | Attribute | Applies to | Purpose | | ------------------------ | ---------- | ----------------------------------------------------------------- | | `#[external("private")]` | functions | Client-side private execution with proofs | | `#[external("public")]` | functions | Sequencer-side public execution | | `#[external("utility")]` | functions | Unconstrained queries, not included in transactions | | `#[internal("private")]` | functions | Private helper functions, inlined at call sites | | `#[internal("public")]` | functions | Public helper functions, inlined at call sites | | `#[view]` | functions | Prevents state modification | | `#[initializer]` | functions | Contract constructor | | `#[noinitcheck]` | functions | Callable before contract initialization | | `#[allow_phase_change]` | functions | Allows for phase change to happen during the function's execution | | `#[only_self]` | functions | Only callable by the same contract | | `#[authorize_once]` | functions | Requires authwit authorization with replay protection | | `#[note]` | structs | Defines a private note type | | `#[custom_note]` | structs | Defines a note with custom hash/nullifier logic | | `#[storage]` | structs | Defines contract storage layout | | `#[storage_no_init]` | structs | Storage with manual slot allocation | For macro internals, see the [macros reference](/developers/docs/aztec-nr/framework-description/macros.md). # External functions #\[external("...")] Like in Solidity, external functions can be called from outside the contract. There are 3 types of external functions differing in the execution environment they are executed in: private, public, and utility. We will describe each type in the following sections. ## Private functions #\[external("private")][​](#private-functions-externalprivate "Direct link to Private functions #\[external(\"private\")]") A private function operates on private information, and is executed by the user on their device. Annotate the function with the `#[external("private")]` attribute to tell the compiler it's a private function. This will make the [private context](/developers/docs/aztec-nr/framework-description/functions/context.md#the-private-context) available within the function's execution scope. The compiler will create a circuit to define this function. `#[external("private")]` is just syntactic sugar. At compile time, the Aztec.nr framework inserts code that allows the function to interact with the [kernel](/developers/docs/foundational-topics/advanced/circuits/private_kernel.md). If you are interested in what exactly the macros are doing we encourage you to run `aztec-nargo expand` on your contract. This will display your contract's code after the transformations are performed. (If you are using VSCode you can display the expanded code by pressing `CMD + Shift + P` and typing `nargo expand` and selecting `Noir: nargo expand on current package`. Make sure the Noir extension's `Nargo Path` is set to `aztec-nargo` — see the [Noir VSCode extension guide](/developers/docs/aztec-nr/installation.md) for setup.) Under the hood, the macro: * Creates a `PrivateContext` from kernel-provided inputs (chain ID, block data, etc.) * Initializes the `self` object with context and storage * Hashes function inputs for the kernel (enabling variable argument counts) * Returns execution results via `PrivateCircuitPublicInputs` (nullifiers, messages, return values) ## Utility functions #\[external("utility")][​](#utility-functions-externalutility "Direct link to Utility functions #\[external(\"utility\")]") Utility functions perform state queries from an offchain client and are never included in transactions. They can access both private and public state, and can modify local PXE state (e.g., processing logs). Since execution is unconstrained and relies on [oracle calls](https://noir-lang.org/docs/explainers/explainer-oracle), no guarantees are made on result correctness. A reasonable mental model is a Solidity `view` function that can only be invoked via `eth_call`, never in a transaction. Unlike Solidity `view` functions, utility functions can also modify local offchain PXE state. balance\_of\_private ``` #[external("utility")] unconstrained fn balance_of_private(owner: AztecAddress) -> u128 { self.storage.balances.at(owner).balance_of() } ``` > [Source code: noir-projects/noir-contracts/contracts/app/token\_contract/src/main.nr#L502-L507](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L502-L507) info Utility functions can access both private and historical public data since they're not part of transactions—there's no risk of using stale or unverified state. ## Public functions #\[external("public")][​](#public-functions-externalpublic "Direct link to Public functions #\[external(\"public\")]") A public function is executed by the sequencer and has access to a state model that is very similar to that of the EVM and Ethereum. Even though they work in an EVM-like model for public transactions, they are able to write data into private storage that can be consumed later by a private function. note All data inserted into private storage from a public function will be publicly viewable (not private). To create a public function you can annotate it with the `#[external("public")]` attribute. This will make the public context available within the function's execution scope. set\_minter ``` #[external("public")] fn set_minter(minter: AztecAddress, approve: bool) { assert(self.storage.admin.read().eq(self.msg_sender()), "caller is not admin"); self.storage.minters.at(minter).write(approve); } ``` > [Source code: noir-projects/noir-contracts/contracts/app/token\_contract/src/main.nr#L140-L146](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L140-L146) Under the hood, the macro: * Creates a `PublicContext` object that provides access to public state and transaction information * Initializes the storage struct if one is defined * Wraps the function body in a scope that handles context setup and return values * Marks the function as `pub` and `unconstrained`, meaning it doesn't generate proofs and is executed directly by the sequencer To see the exact generated code, run `aztec-nargo expand` on your contract. ## Constrained `view` Functions #\[view][​](#constrained-view-functions-view "Direct link to constrained-view-functions-view") The `#[view]` attribute can be applied to a `#[external("private")]` or a `#[external("public")]` function and it guarantees that the function cannot modify any contract state (just like `view` functions in Solidity). ## `Initializer` Functions #\[initializer][​](#initializer-functions-initializer "Direct link to initializer-functions-initializer") This is used to designate functions as initializers (or constructors) for an Aztec contract. These functions are responsible for setting up the initial state of the contract when it is first deployed. The macro does two important things: * `assert_initialization_matches_address_preimage(context)`: This checks that the arguments and sender to the initializer match the commitments from the address preimage * `mark_as_initialized(&mut context)`: This is called at the end of the function to emit the initialization nullifier, marking the contract as fully initialized and ensuring this function cannot be called again Key things to keep in mind: * A contract can have multiple initializer functions defined, but only one initializer function should be called for the lifetime of a contract instance * Other functions in the contract will have an initialization check inserted, ie they cannot be called until the contract is initialized, unless they are marked with [`#[noinitcheck]`](#noinitcheck) ## #\[noinitcheck][​](#noinitcheck "Direct link to #\[noinitcheck]") In normal circumstances, all functions in an Aztec contract (except initializers) have an initialization check inserted at the beginning of the function body. This check ensures that the contract has been initialized before any other function can be called. However, there may be scenarios where you want a function to be callable regardless of the contract's initialization state. This is when you would use `#[noinitcheck]`. When a function is annotated with `#[noinitcheck]`: * The Aztec macro processor skips the [insertion of the initialization check](#initializer-functions-initializer) for this specific function * The function can be called at any time, even if the contract hasn't been initialized yet ## #\[only\_self][​](#only_self "Direct link to #\[only_self]") External functions marked with #\[only\_self] attribute can only be called by the contract itself - if other contracts try to make the call it will fail. This attribute is commonly used when an action starts in private but needs to be completed in public. The public function must be marked with #\[only\_self] to restrict access to only the contract itself. A typical example is a private token mint operation that needs to enqueue a call to a public function to update the publicly tracked total token supply. It is also useful in private functions when dealing with tasks of an unknown size but with a large upper bound (e.g. when needing to process an unknown amount of notes or nullifiers) as they allow splitting the work in multiple circuits, possibly resulting in performance improvements for low-load scenarios. This macro inserts a check at the beginning of the function to ensure that the caller is the contract itself. This is done by adding the following assertion: ``` assert(self.msg_sender() == self.address, "Function can only be called internally"); ``` ## #\[allow\_phase\_change][​](#allow_phase_change "Direct link to #\[allow_phase_change]") Private functions normally include a check to validate the current transaction phase. The `#[allow_phase_change]` attribute skips this validation, allowing the function to handle phase transitions internally. This is primarily used in account contract entrypoints that need to handle fee payment methods spanning multiple phases: ``` #[external("private")] #[allow_phase_change] fn entrypoint(app_payload: AppPayload, fee_payment_method: u8, cancellable: bool) { // Handle different fee payment methods that may span phases } ``` ## #\[authorize\_once][​](#authorize_once "Direct link to #\[authorize_once]") The `#[authorize_once]` attribute enables authorization checks via the [authwit mechanism](/developers/docs/foundational-topics/advanced/authwit.md) with replay protection. Use this when a function performs actions on behalf of someone who is not the caller. ``` #[authorize_once("from", "authwit_nonce")] #[external("public")] fn transfer_in_public(from: AztecAddress, to: AztecAddress, amount: u128, authwit_nonce: Field) { // Transfer tokens from 'from' to 'to' } ``` The macro: * Verifies the caller is authorized to act on behalf of the `from` address * Emits the authorization request as an offchain effect for wallet verification * Consumes a nullifier with the provided nonce, preventing replay attacks ## Internal functions #\[internal("...")][​](#internal-functions-internal "Direct link to Internal functions #\[internal(\"...\")]") Internal functions are callable only from within the same contract and are inlined at call sites (like Solidity's internal functions). Unlike `#[only_self]`, they don't create a separate call—the code is directly inserted where called. ``` #[internal("private")] fn _prepare_private_balance_increase(to: AztecAddress) -> PartialNote { // Helper logic for private balance operations } #[internal("public")] fn _finalize_transfer(from: AztecAddress, amount: u128) { // Helper logic for public finalization } ``` Call internal functions via `self.internal`: ``` let partial = self.internal._prepare_private_balance_increase(recipient); ``` Key differences from `#[only_self]`: * **Inlined**: Code is inserted at call site, not a separate circuit/call * **Private internal**: Can only be called from private external or internal functions * **Public internal**: Can only be called from public external or internal functions ## Implementing notes[​](#implementing-notes "Direct link to Implementing notes") The `#[note]` attribute is used to define notes in Aztec contracts. When a struct is annotated with `#[note]`, the Aztec macro applies a series of transformations and generates implementations to turn it into a note that can be used in contracts to store private data. 1. **NoteType trait**: Provides a unique identifier for the note type via `get_id()` 2. **NoteHash trait**: Implements note hash and nullifier computation: * `compute_note_hash(self, owner, storage_slot, randomness)` - computes the note's hash * `compute_nullifier(self, context, owner, note_hash_for_nullification)` - computes the nullifier using the owner's nullifying key * `compute_nullifier_unconstrained(self, owner, note_hash_for_nullification)` - unconstrained version for use outside circuits 3. **NoteProperties struct**: A separate struct is generated to describe the note's fields, which is used for efficient retrieval of note data ### Example[​](#example "Direct link to Example") ``` #[note] struct CustomNote { value: Field, } ``` The `owner` is passed as a runtime parameter to the `compute_note_hash` and `compute_nullifier` functions, not stored as a field on the note. To see the exact generated code, run `aztec-nargo expand` on your contract. Key things to keep in mind: * The note struct must implement or derive the `Packable` trait * Developers can use `#[custom_note]` instead of `#[note]` to provide their own `NoteHash` implementation * The note's fields are automatically serialized and deserialized in the order they are defined in the struct ## Storage struct #\[storage][​](#storage-struct-storage "Direct link to Storage struct #\[storage]") The `#[storage]` attribute is used to define the storage structure for an Aztec contract. When a struct is annotated with `#[storage]`, the macro: 1. **Context Injection**: Injects a `Context` generic parameter into the storage struct and all its fields, allowing storage to interact with the Aztec context 2. **Storage Implementation Generation**: Generates an `impl` block with an `init` function that initializes each storage variable with its assigned slot 3. **Storage Slot Assignment**: Automatically assigns storage slots to each field based on their serialized length 4. **Storage Layout Generation**: Creates a `StorageLayout` struct exported via `#[abi(storage)]` for use in the contract artifact ### Example[​](#example-1 "Direct link to Example") ``` #[storage] struct Storage { balance: PublicMutable, owner: PublicMutable, token_map: Map, } ``` To see the exact generated code, run `aztec-nargo expand` on your contract. Alternatively, use `#[storage_no_init]` if you need manual control over storage slot allocation. Key things to keep in mind: * Only one storage struct can be defined per contract, and it must be named `Storage` * `Map` types and private `Note` types always occupy a single storage slot ## #\[storage\_no\_init][​](#storage_no_init "Direct link to #\[storage_no_init]") The `#[storage_no_init]` attribute is an alternative to `#[storage]` that gives you manual control over storage slot allocation. Use this when you need custom slot assignments or want to maintain compatibility with existing storage layouts. With `#[storage_no_init]`, you must provide your own `init` function: ``` #[storage_no_init] struct Storage { balance: PublicMutable, owner: PublicMutable, } impl Storage { fn init(context: Context) -> Self { Storage { balance: PublicMutable::new(context, 1), // Explicit slot assignment owner: PublicMutable::new(context, 5), // Non-sequential slot } } } ``` Unlike `#[storage]`, this macro does not generate: * The `init` function (you must implement it) * The `StorageLayout` struct for the contract artifact ## Further reading[​](#further-reading "Direct link to Further reading") * [Macros reference](/developers/docs/aztec-nr/framework-description/macros.md) --- # Understanding Function Context ## What is the context[​](#what-is-the-context "Direct link to What is the context") The context is an object that is made available within every function in `Aztec.nr`. As mentioned in the [kernel circuit documentation](/developers/docs/foundational-topics/advanced/circuits/private_kernel.md). At the beginning of a function's execution, the context contains all of the kernel information that application needs to execute. During the lifecycle of a transaction, the function will update the context with each of its side effects (created notes, nullifiers etc.). At the end of a function's execution the mutated context is returned to the kernel to be checked for validity. Behind the scenes, Aztec.nr will pass data the kernel needs to and from a circuit, this is abstracted away from the developer. In a developer's eyes, the context is a useful structure that allows you to access and mutate the state of the Aztec blockchain. On this page, you'll learn * The details and functionalities of the private context in Aztec.nr * Difference between the private and public contexts and their unified APIs * Components of the private context, such as inputs and block header. * Elements like return values, read requests, new note hashes, and nullifiers in transaction processing * Differences between the private and public contexts, especially the unique features and variables in the public context ## Two contexts, one API[​](#two-contexts-one-api "Direct link to Two contexts, one API") The `Aztec` blockchain contains two environments - public and private. * Private, for private transactions taking place on user's devices. * Public, for public transactions taking place on the network's sequencers. As there are two distinct execution environments, they both require slightly differing execution contexts. Despite their differences, the APIs for interacting with each are unified. Leading to minimal context switch when working between the two environments. The following section will cover both contexts. ## The Private Context[​](#the-private-context "Direct link to The Private Context") The code snippet below shows what is contained within the private context. private-context ``` pub inputs: PrivateContextInputs, pub side_effect_counter: u32, pub min_revertible_side_effect_counter: u32, pub is_fee_payer: bool, pub args_hash: Field, pub return_hash: Field, pub expiration_timestamp: u64, pub(crate) note_hash_read_requests: BoundedVec>, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL>, pub(crate) nullifier_read_requests: BoundedVec>, MAX_NULLIFIER_READ_REQUESTS_PER_CALL>, key_validation_requests_and_separators: BoundedVec, pub note_hashes: BoundedVec, MAX_NOTE_HASHES_PER_CALL>, pub nullifiers: BoundedVec, MAX_NULLIFIERS_PER_CALL>, pub private_call_requests: BoundedVec, pub public_call_requests: BoundedVec, MAX_ENQUEUED_CALLS_PER_CALL>, pub public_teardown_call_request: PublicCallRequest, pub l2_to_l1_msgs: BoundedVec, MAX_L2_TO_L1_MSGS_PER_CALL>, ``` > [Source code: noir-projects/aztec-nr/aztec/src/context/private\_context.nr#L140-L163](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/aztec-nr/aztec/src/context/private_context.nr#L140-L163) ### Private Context Broken Down[​](#private-context-broken-down "Direct link to Private Context Broken Down") #### Inputs[​](#inputs "Direct link to Inputs") The context inputs includes all of the information that is passed from the kernel circuit into the application circuit. It contains the following values. private-context-inputs ``` #[derive(Eq)] pub struct PrivateContextInputs { pub call_context: CallContext, pub anchor_block_header: BlockHeader, pub tx_context: TxContext, pub start_side_effect_counter: u32, } ``` > [Source code: noir-projects/aztec-nr/aztec/src/context/inputs/private\_context\_inputs.nr#L7-L15](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/aztec-nr/aztec/src/context/inputs/private_context_inputs.nr#L7-L15) As shown in the snippet, the application context is made up of 3 main structures. The call context, the block header, and the private global variables. First of all, the call context. call-context ``` #[derive(Deserialize, Eq, Serialize)] pub struct CallContext { // The address of the contract that is making the call. pub msg_sender: AztecAddress, // The address of the contract being called. pub contract_address: AztecAddress, // The selector of the function being called. pub function_selector: FunctionSelector, // Whether the call will modify the state of the contract. pub is_static_call: bool, } ``` > [Source code: noir-projects/noir-protocol-circuits/crates/types/src/abis/call\_context.nr#L8-L20](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-protocol-circuits/crates/types/src/abis/call_context.nr#L8-L20) The call context contains information about the current call being made: 1. Msg Sender * The message sender is the account (Aztec Contract) that sent the message to the current context. In the first call of the kernel circuit (often the account contract call), this value will be empty. For all subsequent calls the value will be the previous call. > The graphic below illustrates how the message sender changes throughout the kernel circuit iterations. ![](/assets/ideal-img/sender_context_change.7a4633f.640.png) 2. Contract address * This value is the address of the current context's contract address. This value will be the value of the current contract that is being executed. 3. Flags * Furthermore there are a series of flags that are stored within the application context: * is\_static\_call: This will be set if and only if the current call is a static call. In a static call, state changing altering operations are not allowed. ### Block Header[​](#block-header "Direct link to Block Header") Another structure that is contained within the context is the `BlockHeader` object, which is the header of the block used to generate proofs against. block-header ``` #[derive(Deserialize, Eq, Serialize)] pub struct BlockHeader { pub last_archive: AppendOnlyTreeSnapshot, pub state: StateReference, // The hash of the sponge blob for this block, which commits to the tx effects added in this block. // Note: it may also include tx effects from previous blocks within the same checkpoint. // When proving tx effects from this block only, we must refer to the `sponge_blob_hash` in the previous block // header to show that the effect was added after the previous block. // The previous block header can be validated using a membership proof of the last leaf in `last_archive`. pub sponge_blob_hash: Field, pub global_variables: GlobalVariables, pub total_fees: Field, pub total_mana_used: Field, } ``` > [Source code: noir-projects/noir-protocol-circuits/crates/types/src/abis/block\_header.nr#L12-L29](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-protocol-circuits/crates/types/src/abis/block_header.nr#L12-L29) ### Transaction Context[​](#transaction-context "Direct link to Transaction Context") The private context provides access to the transaction context as well, which are user-defined values for the transaction in general that stay constant throughout its execution. tx-context ``` #[derive(Deserialize, Eq, Serialize)] pub struct TxContext { // The chain ID on which this transaction is executed. pub chain_id: Field, // The version of the L1 Rollup contract. pub version: Field, // The gas settings for the transaction. pub gas_settings: GasSettings, } ``` > [Source code: noir-projects/noir-protocol-circuits/crates/types/src/abis/transaction/tx\_context.nr#L8-L18](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-protocol-circuits/crates/types/src/abis/transaction/tx_context.nr#L8-L18) ### Args Hash[​](#args-hash "Direct link to Args Hash") To allow for flexibility in the number of arguments supported by Aztec functions, all function inputs are reduced to a singular value which can be proven from within the application. The `args_hash` is the result of poseidon2 hashing all of a function's inputs. ### Return Values[​](#return-values "Direct link to Return Values") The return values are a set of values that are returned from an applications execution to be passed to other functions through the kernel. Developers do not need to worry about passing their function return values to the `context` directly as `Aztec.nr` takes care of it for you. See the documentation surrounding `Aztec.nr` [macro expansion](/developers/docs/aztec-nr/framework-description/functions/function_transforms.md#function-transformation) for more details. ``` return_hash: Field, ``` ## Expiration Timestamp[​](#expiration-timestamp "Direct link to Expiration Timestamp") Some data structures impose time constraints, e.g. they may make it so that a value can only be changed after a certain delay. Interacting with these in private involves creating proofs that are only valid as long as they are included before a certain future point in time. To achieve this, the `set_expiration_timestamp` function can be used to set this property: expiration-timestamp ``` pub fn set_expiration_timestamp(&mut self, expiration_timestamp: u64) { ``` > [Source code: noir-projects/aztec-nr/aztec/src/context/private\_context.nr#L608-L610](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/aztec-nr/aztec/src/context/private_context.nr#L608-L610) A transaction that sets this value will never be included in a block with a timestamp larger than the requested value, since it would be considered invalid. This can also be used to make transactions automatically expire after some time if not included. ### Read Requests[​](#read-requests "Direct link to Read Requests") Read requests are used to prove that certain notes existed at a specific point in time. When a private function reads a note, it generates a read request that gets validated by the kernel circuit to ensure the note was valid at the time of the transaction. ### New Note Hashes[​](#new-note-hashes "Direct link to New Note Hashes") New note hashes contains an array of all of the note hashes created in the current execution context. ### New Nullifiers[​](#new-nullifiers "Direct link to New Nullifiers") New nullifiers contains an array of the new nullifiers emitted from the current execution context. ### Nullified Note Hashes[​](#nullified-note-hashes "Direct link to Nullified Note Hashes") Nullified note hashes is an optimization for introduced to help reduce state growth. There are often cases where note hashes are created and nullified within the same transaction. In these cases there is no reason that these note hashes should take up space on the node's commitment/nullifier trees. Keeping track of nullified note hashes allows us to "cancel out" and prove these cases. ### Private Call Stack[​](#private-call-stack "Direct link to Private Call Stack") The private call stack contains all of the external private function calls that have been created within the current context. Any function call objects are hashed and then pushed to the execution stack. The kernel circuit will orchestrate dispatching the calls and returning the values to the current context. ### Public Call Stack[​](#public-call-stack "Direct link to Public Call Stack") The public call stack contains all of the external function calls that are created within the current context. Like the private call stack above, the calls are hashed and pushed to this stack. Unlike the private call stack, these calls are not executed client side. Whenever the function is sent to the network, it will have the public call stack attached to it. At this point the sequencer will take over and execute the transactions. ### New L2 to L1 msgs[​](#new-l2-to-l1-msgs "Direct link to New L2 to L1 msgs") New L2 to L1 messages contains messages that are delivered to the l1 outbox on the execution of each rollup. ## Public Context[​](#public-context "Direct link to Public Context") The Public Context includes all of the information passed from the `Public VM` into the execution environment. Its interface is very similar to the [Private Context](#the-private-context), however it has some minor differences (detailed below). ### Public Global Variables[​](#public-global-variables "Direct link to Public Global Variables") The public global variables are provided by the rollup sequencer and consequently contain some more values than the private global variables. global-variables ``` #[derive(Deserialize, Eq, Serialize)] pub struct GlobalVariables { pub chain_id: Field, pub version: Field, pub block_number: u32, pub slot_number: Field, pub timestamp: u64, pub coinbase: EthAddress, pub fee_recipient: AztecAddress, pub gas_fees: GasFees, } ``` > [Source code: noir-projects/noir-protocol-circuits/crates/types/src/abis/global\_variables.nr#L7-L19](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-protocol-circuits/crates/types/src/abis/global_variables.nr#L7-L19) --- # Inner Workings of Functions This page explains what happens under the hood when you create a function in an Aztec contract. The [next page](/developers/docs/aztec-nr/framework-description/functions/attributes.md) covers what the function attributes do. ## Overview[​](#overview "Direct link to Overview") Private functions in Aztec compile to standalone circuits that must conform to the protocol's kernel circuit interface. Public functions compile to AVM bytecode. The transformations described below bridge the gap between developer-friendly Aztec.nr syntax and these underlying requirements. Utility functions (marked with `#[external("utility")]`) do not undergo these transformations—they remain as regular Noir functions. ## Function transformation[​](#function-transformation "Direct link to Function transformation") When you define a private or public function in an Aztec contract, it undergoes several transformations during compilation: * [Creating a context for the function](#context-creation) * [Handling function inputs](#private-and-public-input-injection) * [Processing return values](#return-value-handling) ## Context creation[​](#context-creation "Direct link to Context creation") Every function in an Aztec contract operates within a specific context that provides execution information and functionality. This is either a `PrivateContext` or `PublicContext` object, depending on whether it is a private or public function. ### Private functions[​](#private-functions "Direct link to Private functions") For private functions, context creation involves serializing and hashing all input parameters: ``` // Parameters are serialized into an array let serialized_args: [Field; N] = /* serialized parameters */; // Hash the arguments using poseidon2 let args_hash = aztec::hash::hash_args(serialized_args); // Create the context with the inputs and args hash let mut context = PrivateContext::new(inputs, args_hash); ``` This hashing is important because the kernel circuit uses it to verify the function received the correct parameters without exposing the input data. ### Public functions[​](#public-functions "Direct link to Public functions") For public functions, context creation uses a lazy evaluation pattern: ``` let mut context = PublicContext::new(|| { // compute args hash when needed hash_args(serialized_args) }); ``` ### Using the context[​](#using-the-context "Direct link to Using the context") The context object provides methods for interacting with the blockchain. Storage access and contract calls are handled through a `ContractSelf` wrapper that the macros generate automatically. ## Private and public input injection[​](#private-and-public-input-injection "Direct link to Private and public input injection") An additional parameter is automatically added to every private function. The injected input is always the first parameter of the transformed function and is of type `PrivateContextInputs` for private functions. Original function definition: ``` fn my_function(param1: Type1, param2: Type2) { ... } ``` Transformed function with injected input: ``` fn my_function(inputs: PrivateContextInputs, param1: Type1, param2: Type2) { ... } ``` The `PrivateContextInputs` struct contains: * `call_context` - information about how the function was called (msg\_sender, contract\_address, function\_selector, is\_static\_call) * `anchor_block_header` - the historical block header used during private execution * `tx_context` - transaction-level data (chain\_id, version, gas\_settings) * `start_side_effect_counter` - the side effect counter at function entry These inputs are made available through the `PrivateContext` object within your function. Public functions run in the AVM and access their context data through AVM opcodes rather than injected inputs. ## Return value handling[​](#return-value-handling "Direct link to Return value handling") Return values in Aztec contracts are processed differently from traditional smart contracts. ### Private functions[​](#private-functions-1 "Direct link to Private functions") For private functions, the return value is serialized, hashed, and stored in the context: ``` // The original return value is captured let macro__returned__values = original_return_expression; // The return value is serialized and hashed let serialized_return: [Field; N] = /* serialized return value */; self.context.set_return_hash(serialized_return); ``` The function's return type is changed to `PrivateCircuitPublicInputs`, which is returned by calling `context.finish()` at the end of the function. This process allows the return values to be included in the function's computation result while maintaining privacy. The actual return values are stored in the execution cache and can be retrieved by the caller using the hash. ### Public functions[​](#public-functions-1 "Direct link to Public functions") In public functions, the return value is handled directly by the AVM and the function's return type remains as specified by the developer. ## Function signature generation[​](#function-signature-generation "Direct link to Function signature generation") Each contract function has a unique 4-byte function selector. The selector is computed by hashing the function's signature string using Poseidon2: ``` impl FunctionSelector { pub fn from_signature(signature: str) -> Self { let bytes = signature.as_bytes(); let hash = poseidon2_hash_bytes(bytes); // hash is truncated to fit within 32 bits (4 bytes) FunctionSelector::from_field(hash) } } ``` The signature string follows the format `function_name(param_types)`. For example, `transfer(Field,Field)`. This approach is inspired by Solidity's function selector mechanism, but uses Poseidon2 instead of Keccak-256 for compatibility with Aztec's circuit-friendly hash functions. ## Contract artifacts[​](#contract-artifacts "Direct link to Contract artifacts") Contract artifacts are automatically generated structures that describe the contract's interface. They preserve the original function signatures (parameters and return types) before macro transformations are applied. For each function in the contract, an ABI export is generated with: 1. A parameters struct containing all function parameters 2. An ABI struct marked with `#[abi(functions)]` containing the parameters and return type For example, given a function: ``` fn increment(owner: AztecAddress) -> Field { ... } ``` The following structs are generated: ``` pub struct increment_parameters { pub owner: AztecAddress } #[abi(functions)] pub struct increment_abi { parameters: increment_parameters, return_type: Field } ``` The `#[abi(functions)]` attribute marks the struct for inclusion in the contract ABI's `outputs.functions` array. This is important because macro processing changes the actual return type of private functions to `PrivateCircuitPublicInputs`, but the toolchain needs access to the original signatures. Contract artifacts enable: * Machine-readable contract interface descriptions * TypeScript binding generation (see [how to compile contracts](/developers/docs/aztec-nr/compiling_contracts.md)) * Function return value decoding in the simulator ## Further reading[​](#further-reading "Direct link to Further reading") * [Function attributes and macros](/developers/docs/aztec-nr/framework-description/functions/attributes.md) * [Aztec.nr macro source code](https://github.com/AztecProtocol/aztec-packages/tree/v4.3.1/noir-projects/aztec-nr/aztec/src/macros) - for those who want to see the actual transformation implementation --- # How to Define Functions ## Overview[​](#overview "Direct link to Overview") This guide shows you how to define different types of functions in your Aztec contracts, each serving specific purposes and execution environments. ## Quick reference[​](#quick-reference "Direct link to Quick reference") | Annotation | Execution | State access | | ------------------------ | ----------------- | ------------------------------------------------------------ | | `#[external("private")]` | User device | Private state (and selected public values via storage types) | | `#[external("public")]` | Sequencer | Public state | | `#[external("utility")]` | Offchain client | Public + private (unconstrained) | | `#[internal("private")]` | N/A | Inlined private helper (non-entrypoint) | | `#[internal("public")]` | N/A | Inlined public helper (non-entrypoint) | | `#[view]` | Private or public | Read-only (no state mutation) | | `#[only_self]` | Private or public | Callable only by the same contract | | `#[initializer]` | Private or public | One-time initialization | ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * An Aztec contract project set up with the `aztec-nr` dependency * Basic understanding of [Noir programming language](https://noir-lang.org/docs) * Familiarity with Aztec Protocol's [call types](/developers/docs/foundational-topics/call_types.md) (private vs public) ## Define private functions[​](#define-private-functions "Direct link to Define private functions") Use `#[external("private")]` to create functions that execute privately on user devices. For example: increment ``` #[external("private")] fn increment(owner: AztecAddress) { debug_log_format("Incrementing counter for owner {0}", [owner.to_field()]); self.storage.counters.at(owner).add(1).deliver(MessageDelivery.ONCHAIN_CONSTRAINED); } ``` > [Source code: docs/examples/contracts/counter\_contract/src/main.nr#L36-L42](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/counter_contract/src/main.nr#L36-L42) Private functions run in a private context, can access private state, and can read certain public values through storage types like [`DelayedPublicMutable`](/developers/docs/aztec-nr/framework-description/state_variables.md#delayedpublicmutable). ## Define public functions[​](#define-public-functions "Direct link to Define public functions") Use `#[external("public")]` to create functions that execute on the sequencer: mint\_public ``` #[external("public")] fn mint_public(employee: AztecAddress, amount: u64) { // Only Giggle can mint tokens assert_eq(self.msg_sender(), self.storage.owner.read(), "Only Giggle can mint BOB tokens"); // Add tokens to employee's public balance let current_balance = self.storage.public_balances.at(employee).read(); self.storage.public_balances.at(employee).write(current_balance + amount); } ``` > [Source code: docs/examples/contracts/bob\_token\_contract/src/main.nr#L41-L51](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/bob_token_contract/src/main.nr#L41-L51) Public functions operate on public state, similar to EVM contracts. They can write to private storage, but any data written from a public function is publicly visible. ## Define utility functions[​](#define-utility-functions "Direct link to Define utility functions") Create offchain query functions using the `#[external("utility")]` annotation with `unconstrained`. Utility functions are standalone unconstrained functions that cannot be called from private or public functions. They are meant to be called by *applications* to perform auxiliary tasks like querying contract state or processing offchain messages. Example: get\_counter ``` #[external("utility")] unconstrained fn get_counter(owner: AztecAddress) -> pub u128 { self.storage.counters.at(owner).balance_of() } ``` > [Source code: docs/examples/contracts/counter\_contract/src/main.nr#L44-L49](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/counter_contract/src/main.nr#L44-L49) Use `aztec.js` `simulate` to execute utility functions and read their return values. For details, see [Call Types](/developers/docs/foundational-topics/call_types.md#simulate). ## Define view functions[​](#define-view-functions "Direct link to Define view functions") Create read-only functions using the `#[view]` annotation combined with `#[external("private")]` or `#[external("public")]`: ``` #[external("public")] #[view] fn get_config_value() -> Field { // logic } ``` View functions cannot modify contract state. They're akin to Ethereum's `view` functions. `#[view]` only applies to `#[external("private")]` and `#[external("public")]` functions. ## Define only-self functions[​](#define-only-self-functions "Direct link to Define only-self functions") Create contract-only functions using the `#[only_self]` annotation: \_assert\_is\_owner ``` #[external("public")] #[only_self] fn _assert_is_owner(address: AztecAddress) { assert_eq(address, self.storage.owner.read(), "Only Giggle can mint BOB tokens"); } ``` > [Source code: docs/examples/contracts/bob\_token\_contract/src/main.nr#L131-L137](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/bob_token_contract/src/main.nr#L131-L137) Only-self functions are only callable by the same contract, which is useful when a private function enqueues a public call that should only be callable internally. ## Define initializer functions[​](#define-initializer-functions "Direct link to Define initializer functions") Create constructor-like functions using the `#[initializer]` annotation: constructor ``` #[initializer] #[external("private")] // We can name our initializer anything we want as long as it's marked as aztec(initializer) fn initialize(headstart: u128, owner: AztecAddress) { self.storage.counters.at(owner).add(headstart).deliver( MessageDelivery.ONCHAIN_CONSTRAINED, ); } ``` > [Source code: docs/examples/contracts/counter\_contract/src/main.nr#L25-L34](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/counter_contract/src/main.nr#L25-L34) ### Use multiple initializers[​](#use-multiple-initializers "Direct link to Use multiple initializers") Define multiple initialization options: 1. Mark each function with `#[initializer]` 2. Choose which one to call during deployment 3. Any initializer marks the contract as initialized ## Define internal functions[​](#define-internal-functions "Direct link to Define internal functions") Create helper functions using `#[internal("private")]` or `#[internal("public")]`. Internal functions are inlined at call sites and do not create separate entrypoints: ``` #[internal("private")] fn _prepare_transfer(to: AztecAddress, amount: u128) -> Field { // helper logic for private functions } #[internal("public")] fn _update_balance(owner: AztecAddress, amount: u128) { // helper logic for public functions } ``` Call internal functions via `self.internal`: ``` let result = self.internal._prepare_transfer(recipient, amount); ``` Key constraints: * Private internal functions can only be called from private external or internal functions * Public internal functions can only be called from public external or internal functions ## Next steps[​](#next-steps "Direct link to Next steps") * [Attributes and Macros](/developers/docs/aztec-nr/framework-description/functions/attributes.md) * [Call Types](/developers/docs/foundational-topics/call_types.md) --- # Visibility In Aztec there are multiple different types of visibility that can be applied to functions. Namely we have `data visibility` and `function visibility`. This page explains these types of visibility. ## Data visibility[​](#data-visibility "Direct link to Data visibility") Data visibility describes whether the data (or state) used in a function is generally accessible (public) or on a need-to-know basis (private). ## Function visibility[​](#function-visibility "Direct link to Function visibility") Function visibility describes whether a function is callable from other contracts, or only from within the same contract. This is similar to the visibility modifiers you may be familiar with from Solidity. ### The `#[external(...)]` attribute[​](#the-external-attribute "Direct link to the-external-attribute") In Aztec.nr, the `#[external(...)]` attribute marks a function as externally callable - meaning it can be invoked via a transaction or by other contracts. The attribute takes a parameter specifying the execution context: * `#[external("private")]` - The function executes in a private context with access to private state * `#[external("public")]` - The function executes in a public context with access to public state ### The `#[only_self]` attribute[​](#the-only_self-attribute "Direct link to the-only_self-attribute") By default, all external functions are callable from other contracts, similar to Solidity's `public` visibility. To restrict a function so it can only be called by the same contract, use the `#[only_self]` attribute: ``` #[external("public")] #[only_self] fn _increase_public_balance(to: AztecAddress, amount: u128) { // This function can only be called by this contract let new_balance = self.storage.public_balances.at(to).read().add(amount); self.storage.public_balances.at(to).write(new_balance); } ``` A common use case for `#[only_self]` is when a private function needs to modify public state. Since private functions cannot directly modify public state, they enqueue calls to public functions. By marking the public function with `#[only_self]`, you ensure that only your contract can call it - preventing external parties from manipulating the public state directly. danger Note that functions without `#[only_self]` can be used directly as an entry-point, which currently means that the `msg_sender` would be `0`. For this reason, using address `0` as a burn address is not recommended. You can learn more about this in the [Accounts concept page](/developers/docs/foundational-topics/accounts/keys.md). ### The `#[internal]` attribute[​](#the-internal-attribute "Direct link to the-internal-attribute") The `#[internal]` attribute is different from `#[only_self]`. While `#[only_self]` restricts *who* can call a function (only the same contract, but still via an external call), `#[internal]` functions are **inlined** into the calling function. This is similar to how Solidity's `internal` functions use EVM's `JUMP` instruction rather than `CALL`. Internal functions: * Cannot be called externally (no transaction can invoke them directly) * Are inlined at compile time into the functions that call them * Have access to the calling function's context To understand how visibility works under the hood, check out the [Inner Workings page](/developers/docs/aztec-nr/framework-description/functions/attributes.md). --- # Global Variables Similar to Solidity's global `block` variable, Aztec exposes contextual values within each function via the `context` object. Aztec has two execution environments—Private and Public—each with different available globals. ## Private Global Variables[​](#private-global-variables "Direct link to Private Global Variables") Private functions access transaction context via `TxContext`: tx-context ``` #[derive(Deserialize, Eq, Serialize)] pub struct TxContext { // The chain ID on which this transaction is executed. pub chain_id: Field, // The version of the L1 Rollup contract. pub version: Field, // The gas settings for the transaction. pub gas_settings: GasSettings, } ``` > [Source code: noir-projects/noir-protocol-circuits/crates/types/src/abis/transaction/tx\_context.nr#L8-L18](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-protocol-circuits/crates/types/src/abis/transaction/tx_context.nr#L8-L18) The following fields are accessible via `context` methods: ### Chain Id[​](#chain-id "Direct link to Chain Id") The unique identifier for the Aztec network instance (not the Ethereum chain the rollup settles to). ``` self.context.chain_id(); ``` ### Version[​](#version "Direct link to Version") The Aztec protocol version number. The genesis block has version 1. ``` self.context.version(); ``` ### Gas Settings[​](#gas-settings "Direct link to Gas Settings") The gas limits, max fees per gas, and inclusion fee set by the user for the transaction. ``` self.context.gas_settings(); ``` ## Public Global Variables[​](#public-global-variables "Direct link to Public Global Variables") Public functions access block-level context via `GlobalVariables`: global-variables ``` #[derive(Deserialize, Eq, Serialize)] pub struct GlobalVariables { pub chain_id: Field, pub version: Field, pub block_number: u32, pub slot_number: Field, pub timestamp: u64, pub coinbase: EthAddress, pub fee_recipient: AztecAddress, pub gas_fees: GasFees, } ``` > [Source code: noir-projects/noir-protocol-circuits/crates/types/src/abis/global\_variables.nr#L7-L19](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-protocol-circuits/crates/types/src/abis/global_variables.nr#L7-L19) note Not all fields in `GlobalVariables` are exposed via context methods. The `coinbase`, `fee_recipient`, and `slot_number` fields are used internally by the protocol. Public functions have access to `chain_id()` and `version()` (same syntax as private), plus the following block-level values: ### Timestamp[​](#timestamp "Direct link to Timestamp") The unix timestamp when the block is executed. Provided by the block proposer, so it may have slight variance. Always increases monotonically. ``` self.context.timestamp(); ``` ### Block Number[​](#block-number "Direct link to Block Number") The sequential block identifier. Genesis block is 1, incrementing by 1 for each subsequent block. ``` self.context.block_number(); ``` ### Gas Fees[​](#gas-fees "Direct link to Gas Fees") The current L2 and DA gas prices for the block. You can access gas-related information via: ``` self.context.l2_gas_left(); // Remaining L2 gas self.context.da_gas_left(); // Remaining DA gas self.context.min_fee_per_l2_gas(); // L2 gas price self.context.min_fee_per_da_gas(); // DA gas price self.context.transaction_fee(); // Final tx fee (only available in teardown phase) ``` Why do available globals differ between environments? Private functions execute on the user's device before the transaction is submitted, so they cannot know which block will include the transaction. Therefore, `timestamp` and `block_number` are unavailable in private context. Public functions execute on a sequencer who knows the current block's timestamp and number, making these values accessible. --- # Immutables via Salt Aztec contracts can commit immutable values directly into the contract's address by encoding them into the deployment salt, removing the need for a separate initialization transaction. ## Overview[​](#overview "Direct link to Overview") Rather than storing immutables in private storage (which requires an initializer function and an extra transaction), the [aztec-immutables-macro](https://github.com/defi-wonderland/aztec-immutables-macro/tree/dev) library encodes them into the contract's salt: ``` salt = poseidon2_hash([actual_salt, constant_0, constant_1, ...]) ``` Since the salt is part of the address derivation, the immutable values become cryptographically bound to the contract's address itself. ## Key benefits[​](#key-benefits "Direct link to Key benefits") * **No initialization transaction** — immutables are committed at deployment time, not in a separate setup call * **Runtime verification** — at execution time, capsule data is loaded and verified against the stored salt, ensuring data integrity * **Persistent storage** — immutables are persisted to the PXE's [CapsuleStore](/developers/docs/aztec-nr/framework-description/advanced/how_to_use_capsules.md) after deployment, so capsules don't need to be attached to every transaction * **Compatible with standard storage** — works alongside `#[storage]` and initializers when needed ## Performance[​](#performance "Direct link to Performance") Initialization cost is completely eliminated (no constructor transaction). The per-transaction overhead is approximately 1,098 gates (+0.2%) in the account entrypoint. ## Getting started[​](#getting-started "Direct link to Getting started") For installation instructions, usage examples, and a reference implementation of an initializerless Schnorr account contract, see the [aztec-immutables-macro README](https://github.com/defi-wonderland/aztec-immutables-macro/tree/dev). --- # Aztec Macros Aztec.nr provides macros (attributes) that transform your code during compilation to handle the complexities of private execution, proof generation, and state management. ## Quick reference[​](#quick-reference "Direct link to Quick reference") ### Contract[​](#contract "Direct link to Contract") | Attribute | Purpose | | ---------- | ----------------------------------- | | `#[aztec]` | Marks a module as an Aztec contract | ### Functions[​](#functions "Direct link to Functions") | Attribute | Purpose | | ------------------------ | ----------------------------------------------------------------- | | `#[external("private")]` | Client-side private execution with proofs | | `#[external("public")]` | Sequencer-side public execution | | `#[external("utility")]` | Unconstrained queries, not included in transactions | | `#[internal("private")]` | Private helper, only callable within the same contract | | `#[internal("public")]` | Public helper, only callable within the same contract | | `#[view]` | Prevents state modification | | `#[initializer]` | Contract constructor | | `#[noinitcheck]` | Callable before contract initialization | | `#[allow_phase_change]` | Allows for phase change to happen during the function's execution | | `#[only_self]` | Only callable by the same contract | | `#[authorize_once]` | Requires authwit authorization with replay protection | Functions can have multiple attributes (e.g., `#[external("public")]` with `#[view]` and `#[only_self]`). ### Structs[​](#structs "Direct link to Structs") | Attribute | Purpose | | -------------------- | ------------------------------------- | | `#[note]` | Defines a private note type | | `#[custom_note]` | Note with custom hash/nullifier logic | | `#[storage]` | Defines contract storage layout | | `#[storage_no_init]` | Storage with manual slot allocation | For detailed explanations and examples, see the [Attributes and Macros reference](/developers/docs/aztec-nr/framework-description/functions/attributes.md). ## Further reading[​](#further-reading "Direct link to Further reading") * [Attributes and Macros reference](/developers/docs/aztec-nr/framework-description/functions/attributes.md) - detailed documentation for each macro * [Inner workings of functions](/developers/docs/aztec-nr/framework-description/functions/function_transforms.md) - how macros transform your code --- # Note Delivery When you create a note in an Aztec smart contract, you must deliver it to the recipient so they can use it. This page explains how note delivery works and how to choose the right delivery mode for your use case. ## Overview[​](#overview "Direct link to Overview") In Aztec, creating a note involves two steps: 1. **Creating the note** - Adding the note hash to the note hash tree 2. **Delivering the note** - Sending the note contents to the recipient so they can decrypt and use it Without delivery, the recipient won't know the note exists or be able to access its contents, even though the note hash is onchain. ## The `.deliver()` Method[​](#the-deliver-method "Direct link to the-deliver-method") When you create a note using state variables like `PrivateMutable`, `PrivateSet`, `BalanceSet`, or `SinglePrivateMutable`, the creation methods return a `NoteMessage` or `MaybeNoteMessage` object. A message contains arbitrary information emitted from a contract - currently this includes notes and private events, though developers may define other message types in the future. You must call `.deliver()` on this object to send the message (containing the note) to the recipient. ``` #[aztec] pub contract PrivateToken { use aztec::messages::message_delivery::MessageDelivery; #[external("private")] fn mint(amount: u128, recipient: AztecAddress) { // Adding to the balance returns a MaybeNoteMessage self.storage.balances.at(recipient).add(amount) .deliver(MessageDelivery.ONCHAIN_CONSTRAINED); } } ``` ## Delivery Modes[​](#delivery-modes "Direct link to Delivery Modes") Aztec provides three delivery modes that offer different tradeoffs between cost, proving time, and guarantees: ### `MessageDelivery.OFFCHAIN`[​](#messagedeliveryoffchain "Direct link to messagedeliveryoffchain") **Fully offchain delivery with no guarantees.** This delivery method encrypts messages without constraints and emits them via an oracle call as offchain effects, rather than through the protocol's log stream (which would post data to Ethereum blobs). With offchain delivery, you must manually handle both message transmission and processing. #### How It Works[​](#how-it-works "Direct link to How It Works") Offchain messages bypass Aztec's default private log infrastructure entirely: 1. **Message emission**: The contract encrypts the message (without constraints) and emits it via an oracle call. This creates an "offchain effect" that is included in the transaction but not posted to L1. 2. **Manual extraction**: When the transaction is sent, you must extract the offchain message from the transaction's offchain effects (available via `provenTx.offchainEffects` in aztec.js). 3. **Manual delivery**: You deliver the message through your own channel - Signal, cloud storage, QR codes, peer-to-peer networks, etc. 4. **Manual processing**: The recipient calls `process_message` on the target contract (as an unconstrained function), passing the ciphertext and message context. This decrypts the message and processes it (e.g., adding notes to the PXE database). The PXE cannot automatically discover offchain messages during private state sync because they are not in the log stream that nodes load from Ethereum blobs. **You are responsible for implementing both the delivery mechanism and ensuring the recipient processes the message.** #### When to Use[​](#when-to-use "Direct link to When to Use") * **Use when:** The sender is incentivized to deliver correctly (e.g., sending to yourself, payment for goods/services where recipient must receive the note to complete the transaction) * **Costs:** Zero delivery fees (no blob space), zero proving time overhead * **Guarantees:** None. The sender can fail to deliver or deliver incorrect content * **Privacy:** Maximum. No onchain data is emitted This is expected to be the most common delivery method when you don't need constrained delivery guarantees, as it completely eliminates blob space costs. #### Example Use Cases[​](#example-use-cases "Direct link to Example Use Cases") * Change notes when transferring tokens (you're sending to yourself) * Payments where the recipient won't provide goods/services without the note * Messages to local accounts controlled by the sender * Low-value use-cases like delivering game state updates to a game server ``` // Change note - sender is motivated to deliver to themselves self.storage.balances.at(sender).add(change_amount) .deliver(MessageDelivery.OFFCHAIN); ``` TODO This section will be updated with a complete TypeScript example showing how to extract offchain messages from transaction effects and manually deliver them once the API in Aztec.js is finalized. The full workflow example will make the offchain delivery pattern clearer. #### JavaScript Implementation[​](#javascript-implementation "Direct link to JavaScript Implementation") When using offchain delivery, extract and manually deliver messages in your application: ``` import { MessageContext } from "@aztec/stdlib/logs" // Prove transaction and get offchain effects const txProvingResult = await wallet.pxe.proveTx(txRequest); const provenTx = new ProvenTx( wallet.node, await txProvingResult.toTx(), txProvingResult.getOffchainEffects(), txProvingResult.stats, ); // Extract offchain message const offchainEffects = provenTx.offchainEffects; const ciphertext = offchainEffects[0].data.slice(2); // Send tx const sentTx = provenTx.send() const tx = await sentTx.wait() const txHash = await sentTx.getTxHash() // Deliver via your chosen channel (e.g., send to recipient via Signal, cloud storage, etc.). This is what you'd have to implement await deliverViaMyChannel(ciphertext, recipient); // Recipient processes the message const txEffect = await aztecNode.getTxEffect(txHash); const messageContext = MessageContext.fromTxEffectAndRecipient(txEffect, recipient); await contract.methods.process_message(ciphertext, messageContext.toNoirStruct()).simulate(); ``` See the [aztec.js documentation](/developers/docs/aztec-js.md) for more details on accessing transaction effects. ### `MessageDelivery.ONCHAIN_UNCONSTRAINED`[​](#messagedeliveryonchain_unconstrained "Direct link to messagedeliveryonchain_unconstrained") **Onchain delivery with no content guarantees.** This mode provides the same low proving time as `OFFCHAIN` while avoiding the need to implement custom delivery infrastructure. The tradeoff: you pay for DA (blob space) without gaining additional guarantees. If you're willing to build offchain delivery, use `OFFCHAIN` instead - it's strictly cheaper with the same guarantees. * **Use when:** The sender is incentivized to deliver correctly but you don't want to implement offchain delivery infrastructure * **Costs:** DA gas fees for the encrypted log, zero proving time overhead * **Guarantees:** Message stored onchain and retrievable, but sender can deliver incorrect content or wrong tag * **Privacy:** High - encrypted log reveals minimal information ``` // Minting to an admin who controls the contract self.storage.balances.at(admin).add(amount) .deliver(MessageDelivery.ONCHAIN_UNCONSTRAINED); ``` ### `MessageDelivery.ONCHAIN_CONSTRAINED`[​](#messagedeliveryonchain_constrained "Direct link to messagedeliveryonchain_constrained") **Onchain delivery with guaranteed correct content.** **WARNING**: This mode is [currently NOT fully constrained](https://github.com/AztecProtocol/aztec-packages/issues/14565). The log's tag is unconstrained, meaning a malicious sender could prevent the recipient from finding the message. * **Use when:** The sender cannot be trusted to deliver correctly (e.g., paying fees, creating notes for others, multisig configuration changes). Use this when you need to prove to a contract that the delivery has been done correctly. You can imagine a private NFT sale escrow contract where the escrow would be holding the NFT (the contract itself would be the NFT note owner) and then the escrow would release the NFT to the buyer once the NFT buyer pays the seller. In this case the `NFTSale::buy(...)` function would trigger the payment token transfer from the buyer to the seller and it would need to use `ONCHAIN_CONSTRAINED` delivery otherwise the escrow contract would be willing to transfer the NFT without the NFT seller actually being able to then spend the money. Note that for the transfer of the NFT from the escrow contract to the buyer you could use `OFFCHAIN` delivery because the delivery and encryption would be done in the buyer's PXE and hence there is alignment. * **Costs:** DA gas fees for the encrypted log, proving time overhead for encryption and tagging * **Guarantees:** Recipient receives correctly encrypted content (once tag constraining is implemented, recipient will be able to find it) * **Privacy:** High - encrypted log reveals minimal information ``` // Minting to an arbitrary recipient - must guarantee delivery self.storage.balances.at(recipient).add(amount) .deliver(MessageDelivery.ONCHAIN_CONSTRAINED); ``` ## Choosing a Delivery Mode[​](#choosing-a-delivery-mode "Direct link to Choosing a Delivery Mode") Ask yourself: **"Is the sender incentivized to deliver this note correctly?"** * **Yes, and they can contact the recipient offchain** Use `OFFCHAIN` * **Yes, but they cannot or prefer not to contact them offchain or you don't want to implement offchain delivery** Use `ONCHAIN_UNCONSTRAINED` * **No, the sender might not deliver correctly** Use `ONCHAIN_CONSTRAINED` ## Note Discovery and the Sender[​](#note-discovery-and-the-sender "Direct link to Note Discovery and the Sender") When a note is delivered, recipients need to discover it among all the encrypted logs on the network. Aztec.nr uses a **tagging system** that requires computing a shared secret between the sender and recipient. ### Who is the "Sender"?[​](#who-is-the-sender "Direct link to Who is the \"Sender\"?") The "sender" for note discovery is **not the contract calling `.deliver()`**. Instead, it's the **account contract** that initiated the transaction. When your wallet submits a transaction, it tells PXE which address to use as the sender for tags (typically the originating account). This sender address is then used along with the recipient address to compute a shared secret (via [Diffie-Hellman key exchange](https://www.geeksforgeeks.org/computer-networks/diffie-hellman-key-exchange-and-perfect-forward-secrecy/)), which generates the tag that allows recipients to efficiently find their notes. Contracts can call `set_sender_for_tags(addr)` to override this for their own call, but the override does not propagate to nested calls, siblings, or parents. **Example:** If Alice uses her account contract to call a token contract that mints tokens to Bob, the "sender for tags" is Alice's account contract address, not the token contract address. ### Discovering Notes from Unknown Senders[​](#discovering-notes-from-unknown-senders "Direct link to Discovering Notes from Unknown Senders") **You cannot receive notes from an unknown sender** without additional mechanisms. The tagging system requires you to know the sender's address in advance to compute the shared secret needed to find the note (i.e., the sender needs to be added to your wallet). There are three approaches to solve this: **a) Brute force search** - Download every log and attempt to decrypt it. This becomes prohibitively expensive as the network grows. **b) Known sender tagging** (current implementation) - Only receive notes from senders whose addresses you've registered in your PXE. This is very fast and allows you to block spammers by removing them from your sender list. However, you must know who might send you notes in advance. **c) Handshaking protocols** (not yet implemented) - A two-phase approach where senders first perform a "handshake" that notifies you of their existence, then use regular tagging afterward. This trades off either privacy (public handshake events) or performance (scanning all handshake logs). **Workarounds for receiving notes from unknown senders:** * Require senders to register in a contract first, then search for notes from all registered senders * Share sender addresses through offchain communication * Implement a custom discovery mechanism in your contract See the [Note Discovery](/developers/docs/foundational-topics/advanced/storage/note_discovery.md) documentation for technical details on the tagging mechanism. ## Delivering to Someone Other Than the Note Owner[​](#delivering-to-someone-other-than-the-note-owner "Direct link to Delivering to Someone Other Than the Note Owner") You can deliver a note to an address other than the note's owner using `.deliver_to()`: ``` // Create a note owned by `owner` but deliver it to `auditor` self.storage.balances.at(owner).add(amount) .deliver_to(auditor, MessageDelivery.ONCHAIN_CONSTRAINED); ``` **Important:** The recipient (e.g. an `auditor`) can see the note was created but **cannot use it** - only the owner can spend the note (this is authorized by the contract logic). The recipient also cannot see when/if the note is nullified. **Use cases:** * Traditional finance model of compliance where the third party sees all the activity (e.g. a bank) * Game servers that track all note creation and then quickly serve you the game state (results in better UX) * Analytics or monitoring services ## Code Examples[​](#code-examples "Direct link to Code Examples") ### Private Token Transfer[​](#private-token-transfer "Direct link to Private Token Transfer") ``` #[external("private")] fn transfer(amount: u128, sender: AztecAddress, recipient: AztecAddress) { // Subtract from sender - unconstrained since sender is the caller self.storage.balances.at(sender) .sub(amount) .deliver(MessageDelivery.ONCHAIN_UNCONSTRAINED); // Add to recipient - constrained delivery for untrusted sender self.storage.balances.at(recipient) .add(amount) .deliver(MessageDelivery.ONCHAIN_CONSTRAINED); } ``` ### Admin Initialization[​](#admin-initialization "Direct link to Admin Initialization") ``` #[external("private")] #[initializer] fn constructor(admin: AztecAddress) { // Admin is the owner of the note and is motivated to receive it // Use unconstrained delivery since we don't know if deployer is incentivized self.storage.admin .initialize(AddressNote { address: admin }, admin) .deliver(MessageDelivery.ONCHAIN_CONSTRAINED); } ``` --- # State Variables A contract's state is defined by multiple values. For example, in a token contract, these include the total supply, user balances, outstanding approvals, accounts with minting permission, etc. Each of these persisting values is called a *state variable*. One of the first design considerations for any smart contract is how it'll store its state. This is doubly true in Aztec due to there being **both public and private state** - the tradeoff space is large, so there's room for lots of decisions. ## Choosing the right storage type[​](#choosing-the-right-storage-type "Direct link to Choosing the right storage type") | Need | Use | | ----------------------------------------------- | ------------------------------ | | Public value anyone can read/write | `PublicMutable` | | Public value set once (contract name, decimals) | `PublicImmutable` | | Public key-value mapping | `Map>` | | Private collection per user (token balances) | `Owned>` | | Single private value per user | `Owned>` | | Immutable private value per user | `Owned>` | | Contract-wide private singleton (admin key) | `SinglePrivateMutable` | | Public value readable in private execution | `DelayedPublicMutable` | ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * An Aztec contract project set up with `aztec-nr` dependency * Understanding of Aztec's private and public state model * Familiarity with Noir struct syntax For storage concepts, see [storage overview](/developers/docs/foundational-topics/state_management.md). ## The Storage Struct[​](#the-storage-struct "Direct link to The Storage Struct") State variables are declared in Solidity by simply listing them inside of the contract, like so: ``` contract MyContract { uint128 public my_public_state_variable; } ``` In Aztec.nr, we define a [`struct`](https://noir-lang.org/docs/noir/concepts/data_types/structs) that holds *all* state variables. This struct is called **the storage struct**, and it is identified by having the [`#[storage]` macro](/aztec-nr-api/mainnet/noir_aztec/macros/storage/fn.storage) applied to it. ``` use aztec::macros::aztec; #[aztec] contract MyContract { use aztec::macros::storage; #[storage] struct Storage { // state variables go here e.g, the admin of the contract admin: PublicMutable, } } ``` This struct must also have a generic type called `C` or `Context` - an unfortunate boilerplate parameter that provides execution mode information. The `#[storage]` macro can only be used once, so all contract state must be in a **single** struct. ### Accessing Storage[​](#accessing-storage "Direct link to Accessing Storage") The contract's storage is accessed via `self.storage` in any contract function. It will automatically be tailored to the execution context of that function, hiding all methods that cannot be invoked there. Consider, for example, a `PublicMutable` state variable, which is a value that is fully accessible in public functions, read-only in utility functions, and not accessible in private functions: ``` #[storage] struct Storage { my_public_variable: PublicMutable, } #[external("public")] fn my_public_function() { let current = self.storage.my_public_variable.read(); self.storage.my_public_variable.write(current + 1); } #[external("private")] fn my_private_function() { let current = self.storage.my_public_variable.read(); // compilation error - 'read' is not available in private self.storage.my_public_variable.write(current + 1); // compilation error - 'write' is not available in private } #[external("utility")] fn my_utility_function() { let current = self.storage.my_public_variable.read(); self.storage.my_public_variable.write(current + 1); // compilation error - 'write' is not available in utility } ``` ## Public State Variables[​](#public-state-variables "Direct link to Public State Variables") These are state variables that have *public* content: everyone on the network can see the values they store. They can be considered to be equivalent to Solidity state variables. ### Choosing a Public State Variable[​](#choosing-a-public-state-variable "Direct link to Choosing a Public State Variable") Public state variables are stored in the network's public storage tree and can only be written to by public contract functions. You can read *historic* values of a public state variable in a private contract function, but the current values in the network's public state tree are not accessible in private functions. This means that most public state variables cannot be read from a private function, though there are some exceptions documented in the table below. Below is a table comparing the key properties of the different public state variables that Aztec.nr offers: | State variable | Mutable? | Readable in private? | Writable in private? | Example use case | | ------------------------------------------------------------------------------------------------- | ------------------- | -------------------- | -------------------- | ---------------------------------------------------------------------------------- | | [`PublicMutable`](/aztec-nr-api/mainnet/noir_aztec/state_vars/struct.PublicMutable) | yes | no | no | Configuration of admins, global state (e.g. token total supply, total votes) | | [`PublicImmutable`](/aztec-nr-api/mainnet/noir_aztec/state_vars/struct.PublicImmutable) | no | yes | no | Fixed configuration, one-way actions (e.g. initialization settings for a proposal) | | [`DelayedPublicMutable`](/aztec-nr-api/mainnet/noir_aztec/state_vars/struct.DelayedPublicMutable) | yes (after a delay) | yes | no | Non time sensitive system configuration | ### PublicMutable[​](#publicmutable "Direct link to PublicMutable") `PublicMutable` is the simplest kind of public state variable: a value that can be read and written. It is essentially the same as a non-`immutable` or `constant` Solidity state variable. It **cannot be read or written to privately**, but it is possible to have private functions enqueue a public call in which a `PublicMutable` is accessed. For example, a voting contract may allow private submission of votes which then enqueue a public call in which the vote count, represented as a `PublicMutable`, is incremented. This would let anyone see how many votes have been cast, while preserving the privacy of the account that cast the vote. #### Declaration[​](#declaration "Direct link to Declaration") Store mutable public state using `PublicMutable` for values that need to be updated throughout the contract's lifecycle. For example, storing the address of the collateral asset in a lending contract: public\_mutable ``` collateral_asset: PublicMutable, ``` > [Source code: noir-projects/noir-contracts/contracts/app/lending\_contract/src/main.nr#L33-L35](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/lending_contract/src/main.nr#L33-L35) #### `read`[​](#read "Direct link to read") `PublicMutable` variables have a `read` method to read the value at the location in storage: public\_mutable\_read ``` #[external("public")] #[view] fn get_assets() -> pub [AztecAddress; 2] { [self.storage.collateral_asset.read(), self.storage.stable_coin.read()] } ``` > [Source code: noir-projects/noir-contracts/contracts/app/lending\_contract/src/main.nr#L260-L266](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/lending_contract/src/main.nr#L260-L266) #### `write`[​](#write "Direct link to write") The `write` method on `PublicMutable` variables takes the value to write as an input and saves this in storage: public\_mutable\_write ``` self.storage.collateral_asset.write(collateral_asset); ``` > [Source code: noir-projects/noir-contracts/contracts/app/lending\_contract/src/main.nr#L61-L63](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/lending_contract/src/main.nr#L61-L63) ### PublicImmutable[​](#publicimmutable "Direct link to PublicImmutable") `PublicImmutable` is a simplified version of `PublicMutable`: it's a public state variable that can only be written (initialized) once, at which point it can only be read. Unlike Solidity `immutable` state variables, which must be set in the contract's constructor, a `PublicImmutable` can be initialized *at any point in time* during the contract's lifecycle. Attempts to read it prior to initialization will revert. Due to the value being immutable, you can also read it during private execution - once a circuit proves that the value was set in the past, it knows it cannot have possibly changed. This makes this state variable suitable for immutable public contract configuration or one-off public actions, such as user registration status. #### Declaration[​](#declaration-1 "Direct link to Declaration") For example, in the `Storage` struct in a simple token contract, the name, symbol, and decimals are `PublicImmutable` variables: public\_immutable ``` symbol: PublicImmutable, name: PublicImmutable, decimals: PublicImmutable, ``` > [Source code: noir-projects/noir-contracts/contracts/app/simple\_token\_contract/src/main.nr#L45-L49](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/simple_token_contract/src/main.nr#L45-L49) #### `initialize`[​](#initialize "Direct link to initialize") This function sets the immutable value. It can only be called once. public\_immutable\_initialize ``` self.storage.name.initialize(FieldCompressedString::from_string(name)); self.storage.symbol.initialize(FieldCompressedString::from_string(symbol)); self.storage.decimals.initialize(decimals); ``` > [Source code: noir-projects/noir-contracts/contracts/app/simple\_token\_contract/src/main.nr#L55-L59](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/simple_token_contract/src/main.nr#L55-L59) warning A `PublicImmutable`'s storage **must** only be set once via `initialize`. Attempting to override this by manually accessing the underlying storage slots breaks all properties of the data structure, rendering it useless. #### `read`[​](#read-1 "Direct link to read-1") Returns the stored immutable value. This function is available in public, private and utility contexts. public\_immutable\_read ``` #[external("public")] #[view] fn public_get_name() -> FieldCompressedString { self.storage.name.read() } ``` > [Source code: noir-projects/noir-contracts/contracts/app/simple\_token\_contract/src/main.nr#L62-L68](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/simple_token_contract/src/main.nr#L62-L68) ### DelayedPublicMutable[​](#delayedpublicmutable "Direct link to DelayedPublicMutable") It is sometimes necessary to read public mutable state in private. For example, a decentralized exchange might have a configurable swap fee that some admin sets, but which needs to be read by users in their private swaps. This is where `DelayedPublicMutable` comes in. `DelayedPublicMutable` is the same as a `PublicMutable` in that it is a public value that can be read and written, but with a caveat: writes only take effect *after some time delay*. These delays are configurable, but they're typically on the order of a couple hours, if not days, making this state variable unsuitable for actions that must be executed immediately - such as an emergency shutdown. It is these very delays that enable private contract functions to *read the current value of a public state variable*, which is otherwise typically impossible. The existence of minimum delays means that a private function that reads a public value at an anchor block has a guarantee that said historical value will remain the current value until *at least* some time in the future - before the delay elapses. As long as the transaction gets included in a block before that time (by using the `expiration_timestamp` tx property), the read value is valid. #### Declaration[​](#declaration-2 "Direct link to Declaration") Unlike other state variables, `DelayedPublicMutable` receives not only a type parameter for the underlying datatype, but also a `DELAY` type parameter with the value change delay as a number of seconds. delayed\_public\_mutable\_storage ``` // Authorizing a new address has a certain delay before it goes into effect. Set to 180 seconds. pub(crate) global CHANGE_AUTHORIZED_DELAY: u64 = 180; #[storage] struct Storage { // Admin can change the value of the authorized address via set_authorized() admin: PublicImmutable, authorized: DelayedPublicMutable, } ``` > [Source code: noir-projects/noir-contracts/contracts/app/auth\_contract/src/main.nr#L16-L26](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/auth_contract/src/main.nr#L16-L26) #### `schedule_value_change`[​](#schedule_value_change "Direct link to schedule_value_change") This is the means by which a `DelayedPublicMutable` variable mutates its contents. It schedules a value change for the variable at a future timestamp after the `DELAY` has elapsed. schedule\_value\_change ``` #[external("public")] fn set_authorized(authorized: AztecAddress) { assert_eq(self.storage.admin.read(), self.msg_sender(), "caller is not admin"); self.storage.authorized.schedule_value_change(authorized); } ``` > [Source code: noir-projects/noir-contracts/contracts/app/auth\_contract/src/main.nr#L35-L41](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/auth_contract/src/main.nr#L35-L41) #### `get_current_value`[​](#get_current_value "Direct link to get_current_value") Returns the current value in a public, private or utility execution context. get\_current\_value ``` #[external("public")] #[view] fn get_authorized() -> AztecAddress { self.storage.authorized.get_current_value() } ``` > [Source code: noir-projects/noir-contracts/contracts/app/auth\_contract/src/main.nr#L43-L49](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/auth_contract/src/main.nr#L43-L49) Privacy Consideration Reading `DelayedPublicMutable` in private sets the `expiration_timestamp` property, which may reveal timing information. Choose delays that align with common values to maximize privacy sets. #### `get_scheduled_value`[​](#get_scheduled_value "Direct link to get_scheduled_value") Returns the scheduled value and when it takes effect: get\_scheduled\_value ``` #[external("public")] #[view] fn get_scheduled_authorized() -> (AztecAddress, u64) { self.storage.authorized.get_scheduled_value() } ``` > [Source code: noir-projects/noir-contracts/contracts/app/auth\_contract/src/main.nr#L51-L57](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/auth_contract/src/main.nr#L51-L57) ## Private State Variables[​](#private-state-variables "Direct link to Private State Variables") Private state variables have *private* content meaning that only some people know what is stored in them. These work *very* differently from public state variables and are unlike anything in languages such as Solidity, since they are built from fundamentally different primitives (UTXO-based notes and nullifiers instead of a key-value updatable public database). Aztec.nr provides three private state variable types: * `Owned, Context>`: Single mutable private value * `Owned, Context>`: Single immutable private value * `Owned, Context>`: Collection of private notes These private state variables are "owned" and must be wrapped in the `Owned<>` container, which enables owner-specific access via the `.at(owner)` method. Each also requires a `NoteType`. To understand this, let's go through notes and nullifiers and how they can be used so we can understand how private state works. ### Notes and Nullifiers[​](#notes-and-nullifiers "Direct link to Notes and Nullifiers") Just as public state is stored in a single public data tree (equivalent to the `key-value` store used for state on the EVM), private state is managed using two separate trees: * **The note hash tree**: stores hashes of the private data, called notes, which are just structs containing private data with some methods. * **The nullifier tree**: the nullifier for a certain note is deterministic, and the presence of the nullifier in the nullifier tree determines that the note has been spent/used. Understanding these primitives and how they can be used is key to understanding how private state works. #### Notes[​](#notes "Direct link to Notes") Notes are user-defined data that can be stored privately on the blockchain. A note can represent any private data, such as an amount (e.g., some token balance), an ID (e.g., a vote proposal ID), or an address (e.g., an authorized account). They also have some metadata, including a storage slot to avoid collisions with other notes, a `randomness` value that helps hide the content, and an `owner` who can nullify the note. The note content plus the metadata are all hashed together, and it is this hash that gets stored onchain in the note hash tree. This hash is called a commitment. The underlying note content (the note hash preimage) is not stored anywhere onchain, so third parties cannot access it and it remains private. The note hash tree is append-only - if it wasn't, when a note was spent, external observers would notice that the tree leaf inserted in some transaction was modified in a second transaction, linking them together and leaking privacy. For example, when a user made a payment to a third party, the recipient would be able to know when they spent the received funds. Nullifiers exist to solve this issue. Note: Aztec.nr comes with some prebuilt note types, including [`UintNote`](https://github.com/AztecProtocol/aztec-packages/tree/v4.3.1/noir-projects/aztec-nr/uint-note) and [`AddressNote`](https://github.com/AztecProtocol/aztec-packages/tree/v4.3.1/noir-projects/aztec-nr/address-note), but users are also free to create their own with the `#[note]` macro. ##### Note Lifecycle[​](#note-lifecycle "Direct link to Note Lifecycle") Notes are more complicated than public state, and so it helps to see the different stages one goes through, and when and where each stage happens: * **Creation**: an account executing a private contract function creates a new note according to contract logic, e.g., transferring tokens to a recipient. Note values (e.g., a token amount) and metadata are set, the note hash is computed, and inserted as one of the effects of the transaction. * **Encryption**: the content of the note is encrypted with a key only the sender and intended recipient know - no other account can decrypt this message. * **Delivery**: the encrypted message is delivered to the recipient via some means. Options include storing it onchain as a transaction log, or sending it offchain, e.g., via email or by having the recipient scan a QR code on the sender's device. * **Insertion**: the transaction is sent to the network and gets included in a block. The note hash is inserted into the note hash tree - this is visible to the entire network, but the content of the note remains private. * **Discovery**: the recipient processes the encrypted message they were sent, decrypting it and finding the note's content (i.e., the hash preimage). They verify that the note's hash exists onchain in the note hash tree. They store the note's content in their own private database and can now spend the note. * **Reading**: while executing a private contract function, the recipient fetches the note's content and metadata from their private database (in their PXE) and shows that its hash exists in the note hash tree as part of the zero-knowledge proof. * **Nullification**: the recipient computes the note's nullifier and inserts it as one of the effects of the transaction, preventing the note from being read again. #### Nullifiers[​](#nullifiers "Direct link to Nullifiers") A nullifier is a value which indicates a resource has been spent. Nullifiers are unique and stored onchain in the nullifier tree. The protocol forbids the same nullifier from being inserted into the tree twice. Spending the same resource therefore results in a duplicate nullifier, which invalidates the transaction. The nullifier tree is **append-only** for the same reason that the note hash tree is append-only. Most often, nullifiers are used to mark a note as being spent, which prevents note double spends. This requires two properties from the function that computes a note's nullifier: * **Deterministic**: the nullifier **must** be deterministic given a note, so that the same nullifier value is computed every time the note is attempted to be spent. A non-deterministic nullifier would result in a note being spendable more than once because the nullifiers would not be duplicates. * **Secret**: the nullifier **must** not be computable by anyone except the owner, *even by someone who knows the full note content*. This is because some third parties *do* know the note content: when paying someone and creating a note for them, the payer creates the note on their device and thus has access to all of its data and metadata. There are multiple ways to compute nullifiers that fulfill this property, but typically they are computed as a **hash of the note contents concatenated with a private key of the note's owner**. These values are **immutable**, and only the owner knows their private keys, ensuring both determinism and secrecy. These nullifiers are sometimes called 'zcash-style nullifiers' because this is the format ZCash uses for their note nullifiers. ### Note Messages and Discovery[​](#note-messages-and-discovery "Direct link to Note Messages and Discovery") Because notes are private, not even the intended recipient is aware of their existence, and therefore they must be somehow notified. For example, when making a payment and creating a note for the payee with the intended amount, they must be shown the preimage of the note that was inserted in the note hash tree in a given transaction in order to acknowledge the payment. Recipients learning about notes created for them is known as 'note discovery', which is a process Aztec.nr handles efficiently and automatically. However, it does mean that when a note is created, a *message* with the content of the note is created and needs to be delivered to a recipient via one of multiple means detailed below. When working with private state variables, many operations return a `NoteMessage` type rather than the note directly. This is a type-safe wrapper that ensures you explicitly decide how to deliver the note to its recipient. #### Delivery Methods[​](#delivery-methods "Direct link to Delivery Methods") Private notes need to be communicated to their recipients so they know the note exists and can use it. The [`NoteMessage`](/aztec-nr-api/mainnet/noir_aztec/note/struct.NoteMessage) wrapper forces you to make an explicit choice about how this happens: * [`MessageDelivery.ONCHAIN_CONSTRAINED`](/aztec-nr-api/mainnet/noir_aztec/messages/message_delivery/struct.MessageDeliveryEnum#structfield.ONCHAIN_CONSTRAINED): Verified in the circuit (most secure, but highest cost) - Use when the sender cannot be trusted to deliver correctly (e.g., protocol fees, multisig config updates). **Warning:** Currently [not fully constrained](https://github.com/AztecProtocol/aztec-packages/issues/14565) - the log's tag is unconstrained. * [`MessageDelivery.ONCHAIN_UNCONSTRAINED`](/aztec-nr-api/mainnet/noir_aztec/messages/message_delivery/struct.MessageDeliveryEnum#structfield.ONCHAIN_UNCONSTRAINED): Message stored onchain but no guarantees on content - Use when the sender is incentivized to deliver correctly but may not have an offchain channel to the recipient. * [`MessageDelivery.OFFCHAIN`](/aztec-nr-api/mainnet/noir_aztec/messages/message_delivery/struct.MessageDeliveryEnum#structfield.OFFCHAIN): Lowest cost, no onchain data - Use when the sender and recipient can communicate and the sender is incentivized to deliver correctly. note\_delivery ``` #[external("private")] fn mint(amount: u128, recipient: AztecAddress) { let replacement_note_message = self.storage.admin.get_note(); let admin = replacement_note_message.get_note().address; assert(admin == self.msg_sender(), "Only admin can mint"); // We deliver the new note message to the admin using unconstrained delivery, since the admin is motivated to // deliver the message to themselves (hence no need to constrain it). replacement_note_message.deliver(MessageDelivery.ONCHAIN_UNCONSTRAINED); // We increase the total supply and once again use unconstrained delivery, since the admin is motivated to // deliver the message (he's the owner of the new note as well). self.storage.total_supply.replace(|current| UintNote { value: current.value + amount }, admin).deliver( MessageDelivery.ONCHAIN_UNCONSTRAINED, ); // At last we mint the tokens to the recipient. self.storage.balances.at(recipient).add(amount).deliver(MessageDelivery.ONCHAIN_CONSTRAINED); } ``` > [Source code: noir-projects/noir-contracts/contracts/app/private\_token\_contract/src/main.nr#L46-L65](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/private_token_contract/src/main.nr#L46-L65) Methods that return `NoteMessage` include `initialize()`, `get_note()`, and `replace()` on `PrivateMutable`, `initialize()` on `PrivateImmutable`, and `insert()` on `PrivateSet` (more on these methods and private state variable types shortly). ### How Aztec.nr Abstracts Private State Variables[​](#how-aztecnr-abstracts-private-state-variables "Direct link to How Aztec.nr Abstracts Private State Variables") Implementing a private state variable requires careful coordination of multiple primitives and concepts (creating notes, encrypting, delivering, discovering and processing messages, reading notes, and computing their nullifiers). Aztec.nr provides convenient types and functions that handle all of these low-level details to allow developers to write safe code without having to understand the nitty-gritty. By applying the `#[note]` [macro](/aztec-nr-api/mainnet/noir_aztec/macros/notes/fn.note) to a [noir struct](https://noir-lang.org/docs/noir/concepts/data_types/structs), users can define values that will be storable in notes. Private state variables can then hold these notes and be used to read, write, and deliver note messages to the intended recipient. note Advanced users can change this default behavior by either defining their [own custom note](/developers/docs/aztec-nr/framework-description/custom_notes.md) hash and nullifier functions, implementing their own state variables, or even accessing the note hash and nullifiers tree directly. The snippet below shows a contract with two private state variables: an admin address (stored in an `AddressNote`) and a counter of how many calls the admin has made (stored in a `UintNote`). These values will be private and therefore not known except by the accounts that own these notes (the admin). In the `perform_admin_action` private function, the contract checks that it is being called by the correct admin and updates the call count by incrementing it by one. (Note that this is not a real snippet, it's missing some small irrelevant details - but the gist of it is correct) ``` #[note] struct AddressNote { value: AztecAddress, } #[note] struct UintNote { value: u128, } #[storage] struct Storage { admin: Owned, Context>, admin_call_count: Owned, Context>, } #[external("private")] fn perform_admin_action() { // Read the contract's admin address and check against the caller let admin = self.storage.admin.get_note().value; assert(self.msg_sender() == admin); // Update the call count by replacing (updating - rename soon) the current note with a new one that equals the // current value + 1 - this requires knowing what the current value is in the first place, i.e., reading the variable. // // We then deliver the encrypted message with the note's content to the admin so that they become aware of the new // value of the counter and can update it again in the future. self.storage.admin_call_count .replace(|current| UintNote{ value: current.value + 1 }) // wouldn't it be great if we didn't have to deal with this wrapping and unwrapping? .deliver(MessageDelivery.ONCHAIN_CONSTRAINED); // ... } ``` ### Choosing a Private State Variable[​](#choosing-a-private-state-variable "Direct link to Choosing a Private State Variable") Due to the complexities of Aztec's private state model, private state variables do not map 1:1 with public state variables. Understanding these differences between the different private state variables is important when it comes to designing private smart contracts. Below is a table comparing certain key properties of the different private state variables Aztec.nr offers: | State variable | Mutable? | Cost to read? | Writable by third parties? | Example use case | | ----------------------------------------------------------------------------------------- | -------- | ------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------- | | [`PrivateMutable`](/aztec-nr-api/mainnet/noir_aztec/state_vars/struct.PrivateMutable) | yes | yes | no | Mutable user state only accessible by them (e.g. user settings or keys) | | [`PrivateImmutable`](/aztec-nr-api/mainnet/noir_aztec/state_vars/struct.PrivateImmutable) | no | no | no | Fixed configuration, one-way actions (e.g. initialization settings for a proposal) | | [`PrivateSet`](/aztec-nr-api/mainnet/noir_aztec/state_vars/struct.PrivateSet) | yes | yes | yes | Aggregated state others can add to, e.g. token balance (set of amount notes), nft collections (set of nft ids) | ### Owned State Variables[​](#owned-state-variables "Direct link to Owned State Variables") Private state variables like `PrivateMutable`, `PrivateImmutable`, and `PrivateSet` implement the `OwnedStateVariable` trait. You must wrap them in `Owned`. Access the underlying state variable for a specific owner using `.at(owner)` ### PrivateMutable[​](#privatemutable "Direct link to PrivateMutable") `PrivateMutable` is conceptually similar to `PublicMutable` and regular Solidity state variables in that it is a variable that has exactly one value at any point in time that can be read and written. However, for `PrivateMutable`: * The value is, of course, *private*, meaning only the account the value belongs to can read it. * *Only ONE account can read and write the state variable*. It is not possible, for example, to use a `PrivateMutable` to store user settings and then have some admin account alter these settings. * Reading the current value results in the state variable being updated, increasing tx costs and requiring delivery of a note message. * There is no `write` function - the current value is instead `replace`d. #### Declaration[​](#declaration-3 "Direct link to Declaration") owned\_private\_mutable ``` subscriptions: Owned, Context>, ``` > [Source code: noir-projects/noir-contracts/contracts/app/app\_subscription\_contract/src/main.nr#L61-L63](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/app_subscription_contract/src/main.nr#L61-L63) #### `is_initialized`[​](#is_initialized "Direct link to is_initialized") An unconstrained method to check whether the `PrivateMutable` has been initialized or not: owned\_private\_mutable\_is\_initialized ``` #[external("utility")] unconstrained fn is_initialized(subscriber: AztecAddress) -> bool { self.storage.subscriptions.at(subscriber).is_initialized() } ``` > [Source code: noir-projects/noir-contracts/contracts/app/app\_subscription\_contract/src/main.nr#L166-L171](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/app_subscription_contract/src/main.nr#L166-L171) #### `initialize` and `initialize_or_replace`[​](#initialize-and-initialize_or_replace "Direct link to initialize-and-initialize_or_replace") The `PrivateMutable` should be initialized to create the first note and value. This can be done with either `initialize` or `initialize_or_replace`: owned\_private\_mutable\_initialize ``` self .storage .subscriptions .at(subscriber) .initialize_or_replace(|_| SubscriptionNote { expiry_block_number, remaining_txs: tx_count }) .deliver(MessageDelivery.ONCHAIN_CONSTRAINED); ``` > [Source code: noir-projects/noir-contracts/contracts/app/app\_subscription\_contract/src/main.nr#L156-L163](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/app_subscription_contract/src/main.nr#L156-L163) #### `get_note`[​](#get_note "Direct link to get_note") This function allows us to get the note of a `PrivateMutable`, essentially reading the value: ``` #[external("private")] fn read_settings() { let owner = self.msg_sender(); self.storage.user_settings.at(owner).get_note().deliver(MessageDelivery.ONCHAIN_CONSTRAINED); } ``` info To ensure that a user's private execution always uses the latest value of a `PrivateMutable`, the `get_note` function will nullify the note that it is reading. This means that if two people are trying to use this function with the same note, only one will succeed. Reading a `PrivateMutable` nullifies and recreates the note. This makes reads indistinguishable from writes and ensures the sequencer cannot learn the note's value. #### `replace`[​](#replace "Direct link to replace") To update the value of a `PrivateMutable`, we can use the `replace` method: owned\_single\_private\_mutable\_replace ``` #[external("private")] fn transfer_admin(new_admin: AztecAddress) { self .storage .admin .replace( |old| { assert(old.address == self.msg_sender(), "Only admin can transfer admin privileges"); AddressNote { address: new_admin } }, new_admin, ) .deliver(MessageDelivery.ONCHAIN_CONSTRAINED); } ``` > [Source code: noir-projects/noir-contracts/contracts/app/private\_token\_contract/src/main.nr#L68-L83](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/private_token_contract/src/main.nr#L68-L83) ### PrivateImmutable[​](#privateimmutable "Direct link to PrivateImmutable") `PrivateImmutable` represents a unique private state variable that, as the name suggests, is immutable. Once initialized, its value cannot be altered. This is the private equivalent of `PublicImmutable`, except the value is only known to its owner. Unlike `PrivateMutable`, the `get_note` function for a `PrivateImmutable` doesn't nullify the current note and returns the `Note` directly (not wrapped in `NoteMessage`). This means that multiple accounts can concurrently call this function to read the value. #### Declaration[​](#declaration-4 "Direct link to Declaration") private\_immutable ``` note_in_private_immutable: Owned, Context>, ``` > [Source code: noir-projects/noir-contracts/contracts/test/test\_contract/src/main.nr#L81-L83](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/test/test_contract/src/main.nr#L81-L83) `PrivateImmutable` variables also have the `initialize` and `get_note` functions on them but no `initialize_or_replace` since they cannot be modified. ### PrivateSet[​](#privateset "Direct link to PrivateSet") `PrivateSet` is used for managing a collection of notes. Like `PrivateMutable`, this is a private state variable that can be modified. There are two key differences: * A `PrivateSet` is not a single value but a *set* (a collection) of values (represented by notes) * Any account can insert values into someone else's set. The set's current value is the collection of notes in the set that have not yet been nullified. These notes can have any type: they could be NFT IDs representing a user's NFT collection, or they might be token amounts, in which case *the sum* of all values in the set would be the user's current balance. #### Declaration[​](#declaration-5 "Direct link to Declaration") For example, to add private token balances to storage: private\_set ``` #[storage] struct Storage { balances: Owned, Context>, } ``` > [Source code: noir-projects/noir-contracts/contracts/test/pending\_note\_hashes\_contract/src/main.nr#L27-L32](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/test/pending_note_hashes_contract/src/main.nr#L27-L32) #### `insert`[​](#insert "Direct link to insert") Allows us to modify the storage by inserting a note into the `PrivateSet`: private\_set\_insert ``` owner_balance.insert(note).deliver(MessageDelivery.ONCHAIN_CONSTRAINED); ``` > [Source code: noir-projects/noir-contracts/contracts/test/pending\_note\_hashes\_contract/src/main.nr#L47-L49](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/test/pending_note_hashes_contract/src/main.nr#L47-L49) Note: The `Owned` wrapper requires calling `.at(owner)` to access the underlying `PrivateSet` for a specific owner. This binds the owner to the state variable instance. #### `get_notes`[​](#get_notes "Direct link to get_notes") Retrieves notes the account has access to. You can optionally provide filtering options. Returns `ConfirmedNote` instances: private\_set\_get\_notes ``` let options = NoteGetterOptions::with_filter(filter_notes_min_sum, amount); // get note (note inserted at bottom of function shouldn't exist yet) let notes = owner_balance.get_notes(options); ``` > [Source code: noir-projects/noir-contracts/contracts/test/pending\_note\_hashes\_contract/src/main.nr#L66-L70](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/test/pending_note_hashes_contract/src/main.nr#L66-L70) #### `pop_notes`[​](#pop_notes "Direct link to pop_notes") This function pops (gets, removes and returns) the notes the account has access to. Unlike `get_notes`, this immediately nullifies the notes and returns them directly (not wrapped in `ConfirmedNote`): private\_set\_pop\_notes ``` let options = NoteGetterOptions::new().set_limit(1); let note = owner_balance.pop_notes(options).get(0); ``` > [Source code: noir-projects/noir-contracts/contracts/test/pending\_note\_hashes\_contract/src/main.nr#L133-L136](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/test/pending_note_hashes_contract/src/main.nr#L133-L136) #### `remove`[​](#remove "Direct link to remove") Will remove a note from the `PrivateSet` if it previously has been read from storage. Takes a `ConfirmedNote` as returned by `get_notes`: ``` let options = NoteGetterOptions::new(); let confirmed_notes = self.storage.balances.at(owner).get_notes(options); // ... select a note to remove ... self.storage.balances.at(owner).remove(confirmed_notes.get(0)); ``` Note that if you obtained the note via `get_notes`, it's much better to use `pop_notes`, as `pop_notes` results in significantly fewer constraints due to avoiding an extra hash and read request check. ### SinglePrivateMutable and SinglePrivateImmutable[​](#singleprivatemutable-and-singleprivateimmutable "Direct link to SinglePrivateMutable and SinglePrivateImmutable") For contract-wide private values (not per-owner), use `SinglePrivateMutable` or `SinglePrivateImmutable`. These store exactly one value for the entire contract - a global singleton - rather than separate values per owner. | Type | Use Case | Access Pattern | | ---------------------------- | --------------------------------------- | ----------------------- | | `Owned>` | Per-owner private state (like balances) | `.at(owner).get_note()` | | `SinglePrivateMutable` | Contract-wide singleton (like admin) | `.get_note()` directly | Since there's only one value at the storage slot, there's no need to specify an owner to look it up: ``` #[storage] struct Storage { admin: SinglePrivateMutable, config: SinglePrivateImmutable, } // Access directly without .at(owner) let note_message = self.storage.admin.get_note(); let config = self.storage.config.get_note(); ``` When initializing, you still pass an owner address, but this specifies who can decrypt the note, not the storage location: ``` // owner_address determines who can see the note, not where it's stored self.storage.admin.initialize(note, owner_address).deliver(MessageDelivery.ONCHAIN_CONSTRAINED); ``` warning `SinglePrivateMutable` uses a nullify-and-recreate pattern when reading. Unless the caller is incentivized to deliver the note message correctly, you should use `MessageDelivery.ONCHAIN_CONSTRAINED` to prevent malicious actors from bricking the contract by failing to deliver the note. ## Containers[​](#containers "Direct link to Containers") ### Map[​](#map "Direct link to Map") A `Map` is a key-value container that maps keys to state variables - just like Solidity's `mapping`. It can be used with any state variable to create independent instances for each key. For example, a `Map>` can be accessed with an address to obtain the `PublicMutable` that corresponds to it. This is exactly equivalent to a Solidity `mapping (address => uint)`. #### Declaration[​](#declaration-6 "Direct link to Declaration") ``` #[storage] struct Storage { // Map of addresses to public balances public_balances: Map, Context>, // Map of addresses to authorized users authorized_users: Map, Context>, } ``` #### Usage[​](#usage "Direct link to Usage") Use the `.at()` method to access values by key: ``` #[external("public")] fn increase_balance(account: AztecAddress, amount: u128) { let current = self.storage.public_balances.at(account).read(); self.storage.public_balances.at(account).write(current + amount); } ``` note Maps can only be used with public state variables (`PublicMutable`, `PublicImmutable`, `DelayedPublicMutable`) or other `Map`s. For private state, use the `Owned` wrapper described above. ### Owned[​](#owned "Direct link to Owned") The `Owned` wrapper is used with private state variables (`PrivateMutable`, `PrivateImmutable`, and `PrivateSet`) to associate them with a specific owner. This is necessary because private state variables need to know which address owns the notes they manage. #### Declaration[​](#declaration-7 "Direct link to Declaration") ``` #[storage] struct Storage { // Single owner's private balance balances: Owned, Context>, // Single owner's private settings user_settings: Owned, Context>, } ``` #### Usage[​](#usage-1 "Direct link to Usage") Use the `.at(owner)` method to access the underlying state variable for a specific owner: ``` #[external("private")] fn transfer(from: AztecAddress, to: AztecAddress, amount: u128) { // Access the balance for the 'from' address let options = NoteGetterOptions::new(); let notes = self.storage.balances.at(from).pop_notes(options); // Access the balance for the 'to' address let new_note = UintNote { value: amount }; self.storage.balances.at(to).insert(new_note).deliver(MessageDelivery.ONCHAIN_UNCONSTRAINED); } ``` The `Owned` wrapper is essential for private state variables because it binds the owner's address to the state variable instance, enabling proper note encryption, nullifier computation, and access control. ## Custom Structs in Public Storage[​](#custom-structs-in-public-storage "Direct link to Custom Structs in Public Storage") Both `PublicMutable` and `PublicImmutable` are generic over any serializable type, which means you can store custom structs in public storage. ### Define a Custom Struct[​](#define-a-custom-struct "Direct link to Define a Custom Struct") To use a custom struct in public storage, it must implement the `Packable` trait: ``` use aztec::protocol::{ address::AztecAddress, traits::{Deserialize, Packable, Serialize} }; #[derive(Deserialize, Packable, Serialize)] pub struct Asset { pub interest_accumulator: u128, pub last_updated_ts: u64, pub loan_to_value: u128, pub oracle: AztecAddress, } ``` ### Store and Use Custom Structs[​](#store-and-use-custom-structs "Direct link to Store and Use Custom Structs") ``` #[storage] struct Storage { assets: Map, Context>, } #[external("public")] fn update_asset(asset_id: Field, new_accumulator: u128) { let mut asset = self.storage.assets.at(asset_id).read(); asset.interest_accumulator = new_accumulator; self.storage.assets.at(asset_id).write(asset); } ``` ## Storage Slots[​](#storage-slots "Direct link to Storage Slots") Each state variable gets assigned a different numerical value for their **storage slot**. How they are used depends on the kind of state variable: * For public state variables, storage slots are related to slots in the public data tree * For private state variables, storage slots are metadata that gets included in the note hash The purpose of slots is the same for both domains: they keep the values of different state variables *separate* so that they do not interfere with one another. Storage slots are a low-level detail that developers don't typically need to concern themselves with. They are automatically allocated to each state variable by Aztec.nr. Utilizing storage slots directly can be dangerous as it may accidentally result in data collisions across state variables or invariants being broken. In some advanced use cases, it can be useful to have access to these low-level details, such as when implementing [contract upgrades](/developers/docs/aztec-nr/framework-description/contract_upgrades.md) or when interacting with protocol contracts. --- # Noir VSCode Extension Install the [Noir Language Support extension](https://marketplace.visualstudio.com/items?itemName=noir-lang.vscode-noir) to get syntax highlighting, syntax error detection, and go-to definitions for your Aztec contracts. The extension drives its language server with `nargo`. The Aztec installer ships a bundled `nargo` and exposes it as the `aztec-nargo` symlink on your `PATH`. Bare `nargo` is intentionally not provided so it does not shadow your own install (if any). Verify the symlink is on your `PATH`: ``` which aztec-nargo # expected: $HOME/.aztec/current/bin/aztec-nargo ``` If you have not installed the Aztec toolchain yet, follow [Getting Started on Local Network](/developers/getting_started_on_local_network.md) first. ## Configure the extension[​](#configure-the-extension "Direct link to Configure the extension") Set the extension's `Noir: Nargo Path` setting to the absolute path printed by `which aztec-nargo` (for example `$HOME/.aztec/current/bin/aztec-nargo`), then reload the window. `aztec-nargo` is a symlink to the bundled `nargo`, so any tool that invokes it speaks plain `nargo` (LSP included). To confirm the extension is using the bundled toolchain, hover over **Nargo** in the VSCode status bar in the bottom right corner: it should show the path you set. If you have your own `nargo` install and want the extension to use that instead, leave `Noir: Nargo Path` empty so the extension auto-discovers `nargo` from your `PATH`. ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") * **LSP reports `startFailed` after setting a custom path**: confirm `aztec-nargo` is executable and that the path is correct, reload the window, and check the **Output** panel for the language server log. * **Extension picks up the wrong `nargo`**: the Aztec installer no longer puts bare `nargo` on `PATH`. Set `Noir: Nargo Path` explicitly to `aztec-nargo` (for the bundled version) or to your own install (for any other version). --- # Aztec Contract Standards Aztec contract standards define shared interfaces and behaviors for common onchain primitives. They serve the same role that ERC standards play on Ethereum: establishing conventions that allow contracts, wallets, and tooling to interoperate without prior coordination. The standards described in this section are maintained by [DeFi Wonderland](https://github.com/defi-wonderland/aztec-standards) in the `aztec-standards` repository. Each standard is identified by an **Aztec Improvement Proposal (AIP)** number that mirrors its Ethereum counterpart where applicable (AIP-20 corresponds to ERC-20, AIP-721 to ERC-721, AIP-4626 to ERC-4626). Because Aztec contracts have both private and public execution contexts, the standards are more involved than their Ethereum equivalents. Transfers can move value between private notes and public balances, and many operations require coordination between encrypted state and transparent state within a single transaction. note The code examples in this section are taken from the [aztec-standards repository](https://github.com/defi-wonderland/aztec-standards) maintained by DeFi Wonderland. They will differ from the reference contract implementations shipped in the [aztec-packages repo](https://github.com/AztecProtocol/aztec-packages) under `noir-projects/noir-contracts/contracts/`. When in doubt, consult the aztec-standards github repo for the canonical standard interfaces. ## Standards[​](#standards "Direct link to Standards") * [AIP-20: Fungible Token](/developers/docs/aztec-nr/standards/aip-20.md) — private and public balances, partial-note transfers, recursive note consumption * [AIP-721: Non-Fungible Token](/developers/docs/aztec-nr/standards/aip-721.md) — private NFT ownership, partial-note support, commitment-based transfers * [AIP-4626: Tokenized Vault](/developers/docs/aztec-nr/standards/aip-4626.md) — yield-bearing vaults with share conversion across private and public contexts * [Escrow](/developers/docs/aztec-nr/standards/escrow.md) — minimal token/NFT custody with salt-based authorization * [Generic Proxy](/developers/docs/aztec-nr/standards/generic-proxy.md) — forwarding layer for account abstraction patterns * [Dripper](/developers/docs/aztec-nr/standards/dripper.md) — development faucet for testing ## Related tutorials[​](#related-tutorials "Direct link to Related tutorials") * [Private Token Contract](/developers/docs/tutorials/contract_tutorials/token_contract.md) — build a privacy-preserving fungible token that closely parallels AIP-20 * [NFT Bridge](/developers/docs/tutorials/js_tutorials/token_bridge.md) — build a private NFT with custom `NFTNote` and `PrivateSet`, covering patterns extended by AIP-721 * [Deploying a Token Contract](/developers/docs/tutorials/js_tutorials/aztecjs-getting-started.md) — deploy and interact with the reference token contract using Aztec.js * [Counter Contract](/developers/docs/tutorials/contract_tutorials/counter_contract.md) — introduces private state, notes, and balance management For the canonical implementations and latest interface specifications, refer to the [aztec-standards repository](https://github.com/defi-wonderland/aztec-standards) maintained by DeFi Wonderland. --- # AIP-20: Fungible Token [Source](https://github.com/defi-wonderland/aztec-standards/tree/dev/src/token_contract) AIP-20 defines a fungible token with support for private balances (stored as notes in the note hash tree), public balances (stored in contract public storage), and a hybrid transfer path between the two. ## Storage layout[​](#storage-layout "Direct link to Storage layout") The token contract stores its name, symbol, and decimals as immutable public fields. Private balances are held in an `Owned` that restricts note access to the balance owner. Public balances use a simple `Map` keyed by address. ``` #[storage] struct Storage { name: PublicImmutable, symbol: PublicImmutable, decimals: PublicImmutable, private_balances: Owned, Context>, total_supply: PublicMutable, public_balances: Map, Context>, minter: PublicImmutable, upgrade_authority: PublicImmutable, asset: PublicImmutable, vault_offset: PublicImmutable, } ``` The `asset` and `vault_offset` fields exist to support the [AIP-4626 vault pattern](/developers/docs/aztec-nr/standards/aip-4626.md). A standalone AIP-20 token that is not used as a vault underlying asset does not need to populate these fields. ## Note count constants[​](#note-count-constants "Direct link to Note count constants") In Aztec, every time a user receives private tokens, a new encrypted note is added to their balance. Over time, a user's balance can be spread across dozens of small notes. A transfer must consume enough of these notes to cover the amount, but each note consumed adds computational overhead (gates) to the zero-knowledge proof the user's device must generate. Without a bound, a single transfer could take minutes to prove. Two constants cap how many notes a single proof handles, keeping proving times practical: ``` global INITIAL_TRANSFER_CALL_MAX_NOTES: u32 = 2; global RECURSIVE_TRANSFER_CALL_MAX_NOTES: u32 = 8; ``` The initial call attempts to settle the transfer with at most two notes. If that is not enough to cover the amount, the contract recurses into itself and tries up to eight notes per recursive call. A **placeholder address** is used in partial-note flows to signal that a transfer destination is not yet known at the time the sender initiates the operation. This distinguishes "recipient not yet determined" from "recipient is the zero address," and allows offchain indexers to detect [partial-note transfers](#partial-note-transfers) in event logs without decrypting the note contents: ``` global PRIVATE_ADDRESS_MAGIC_VALUE: AztecAddress = AztecAddress::from_field(0x1ea7e01501975545617c2e694d931cb576b691a4a867fed81ebd3264); ``` ## Partial-note transfers[​](#partial-note-transfers "Direct link to Partial-note transfers") AIP-20 supports partial-note (or "commitment-based") transfers. In Aztec, private functions execute on the user's device before the transaction reaches the network, so they cannot read public state (like a DEX order book or auction result). Partial notes solve this by splitting the operation: the sender privately locks funds into a commitment, and later a public function — which *can* read public state — completes the transfer to the correct recipient. This is what makes private DeFi composability possible. Concretely, the sender locks funds in a note whose destination address is not yet known. A completer — typically a contract acting as a relayer or settlement layer — later fills in the recipient and finalizes the note. The sender calls `initialize_transfer_commitment` to create the commitment: ``` #[external("private")] fn initialize_transfer_commitment(to: AztecAddress, completer: AztecAddress) -> Field { let commitment = self.internal._initialize_transfer_commitment(to, completer); commitment.to_field() } ``` The returned `Field` is an opaque commitment to the destination and completer. A separate call then subtracts the balance and completes the note: ``` #[external("private")] fn transfer_private_to_commitment( from: AztecAddress, commitment: Field, amount: u128, _nonce: Field, ) { _validate_from_private::<4>(self.context, from); self.internal._decrease_private_balance(from, amount, INITIAL_TRANSFER_CALL_MAX_NOTES); let completer = self.msg_sender(); PartialUintNote::from_field(commitment).complete_from_private( self.context, completer, amount, ); } ``` This two-step design is useful in DeFi protocols where the recipient of funds depends on some offchain or asynchronous computation. ## Recursive balance subtraction[​](#recursive-balance-subtraction "Direct link to Recursive balance subtraction") When `INITIAL_TRANSFER_CALL_MAX_NOTES` notes are insufficient to cover a transfer, the contract calls itself recursively until the full amount is consumed: ``` #[internal("private")] fn _subtract_balance(account: AztecAddress, amount: u128, max_notes: u32) -> u128 { let subtracted = self.storage.private_balances.at(account).try_sub(amount, max_notes); if subtracted >= amount { subtracted - amount } else { assert(subtracted > 0, "Balance too low"); let remaining = amount - subtracted; self.call_self.recurse_subtract_balance_internal(account, remaining) } } ``` The recursion terminates when either the full amount has been deducted or the assertion fires. Without recursion, you would have to either size every circuit for the worst-case note count (making the common case expensive to prove) or fail transfers when the note count exceeds a fixed limit. Recursion gives the best of both worlds: the common case (2 notes) proves fast, while larger balances are handled by chaining multiple smaller proofs. Each recursive call is a separate private kernel circuit, so proving cost scales with the actual note count rather than the worst case. --- # AIP-4626: Tokenized Vault [Source](https://github.com/defi-wonderland/aztec-standards/tree/dev/src/vault_contract) (extends the AIP-20 token contract) AIP-4626 extends [AIP-20](/developers/docs/aztec-nr/standards/aip-20.md) to describe a tokenized vault: a contract that holds an underlying asset and issues shares representing a proportional claim on that asset. It mirrors the design of ERC-4626 but adapts the share conversion arithmetic for Aztec's `u128` integer type. ## Share conversion[​](#share-conversion "Direct link to Share conversion") The vault tracks the total supply of shares and a `vault_offset` that prevents inflation attacks on the initial deposit. The conversion functions use integer arithmetic with configurable rounding direction: ``` #[internal("public")] fn _convert_to_shares(assets: u128, total_assets: u128, rounding: bool) -> u128 { let mul_term = assets * (self.storage.total_supply.read() + self.storage.vault_offset.read()); let denominator = (total_assets + 1); let mut shares = mul_term / denominator; if (rounding == ROUND_UP) & (mul_term % denominator > 0) { shares = shares + 1; } shares } #[internal("public")] fn _convert_to_assets(shares: u128, total_assets: u128, rounding: bool) -> u128 { let mul_term = shares * (total_assets + 1); let denominator = (self.storage.total_supply.read() + self.storage.vault_offset.read()); let mut assets = mul_term / denominator; if (rounding == ROUND_UP) & (mul_term % denominator > 0) { assets = assets + 1; } assets } ``` The `+ 1` in the denominator and the `vault_offset` together implement the "virtual shares" technique that prevents the first depositor from manipulating the exchange rate for subsequent depositors. Without this protection, an attacker could deposit 1 wei, then donate a large amount of the underlying asset directly to the vault, inflating the share price so that the next depositor's deposit rounds down to zero shares. Deposits round shares down (in favor of the vault), while redemptions round assets down (also in favor of the vault). This is consistent with ERC-4626 rounding conventions and prevents rounding-based extraction attacks. ## Deposit flow[​](#deposit-flow "Direct link to Deposit flow") A public-to-public deposit transfers assets from the caller to the vault, computes the shares due, and mints them to the recipient: ``` #[external("public")] fn deposit_public_to_public(from: AztecAddress, to: AztecAddress, assets: u128, _nonce: Field) { self.internal._validate_from_public(from); let total_assets = self.internal._total_assets(); let shares = self.internal._convert_to_shares(assets, total_assets, ROUND_DOWN); // Transfer assets from sender to vault self.call(Token::at(self.storage.asset.read()).transfer_public_to_public( from, self.address, assets, _nonce, )); // Mint shares to the recipient self.internal._mint_to_public(to, shares); } ``` The vault exposes similar entry points for the other combinations of private and public contexts (`deposit_private_to_public`, `deposit_public_to_private`, `deposit_private_to_private`). Each variant transfers assets using the corresponding AIP-20 transfer function and then mints shares into the chosen output context. --- # AIP-721: Non-Fungible Token [Source](https://github.com/defi-wonderland/aztec-standards/tree/dev/src/nft_contract) AIP-721 defines a non-fungible token (NFT). Each token is identified by a unique `token_id` field. Tokens can be held privately in the note hash tree or publicly in a map from `token_id` to owner address. ## Storage layout[​](#storage-layout "Direct link to Storage layout") ``` #[storage] struct Storage { symbol: PublicImmutable, name: PublicImmutable, private_nfts: Owned, Context>, nft_exists: Map, Context>, public_owners: Map, Context>, minter: PublicImmutable, upgrade_authority: PublicImmutable, } ``` `nft_exists` tracks whether a given `token_id` has been minted, while `public_owners` records the current public owner. When an NFT is moved to a private note, the `public_owners` entry is cleared and the NFT is stored as an `NFTNote` in the holder's private set. ## NFTNote and partial-note support[​](#nftnote-and-partial-note-support "Direct link to NFTNote and partial-note support") Each private NFT is represented as an `NFTNote` containing only the `token_id`: ``` #[derive(Eq, Serialize, Packable)] #[custom_note] pub struct NFTNote { pub token_id: Field, } impl NFTNote { pub fn partial( owner: AztecAddress, storage_slot: Field, context: &mut PrivateContext, recipient: AztecAddress, completer: AztecAddress, ) -> PartialNFTNote { let randomness = unsafe { random() }; let commitment = compute_partial_commitment(owner, storage_slot, randomness); // ... creates encrypted log and validity commitment let partial_note = PartialNFTNote { commitment }; let validity_commitment = partial_note.compute_validity_commitment(completer); context.push_nullifier(validity_commitment); partial_note } } ``` The `partial` constructor creates a `PartialNFTNote` whose `commitment` field commits to the future owner and storage slot. Without some form of access control, any party could call the completion function and claim the NFT for themselves. The validity commitment prevents this — it is pushed as a nullifier, and only the designated completer can produce the matching preimage needed to finalize the note. This mirrors the partial-note pattern in [AIP-20](/developers/docs/aztec-nr/standards/aip-20.md) but applies it to NFT transfers. ## Partial-note transfer commitment[​](#partial-note-transfer-commitment "Direct link to Partial-note transfer commitment") The external entry point for initiating a partial NFT transfer is: ``` #[external("private")] fn initialize_transfer_commitment(to: AztecAddress, completer: AztecAddress) -> Field { let commitment = self.internal._initialize_transfer_commitment(to, completer); commitment.commitment() } ``` This function returns `commitment.commitment()` — an opaque `Field` representing the commitment. The AIP-20 equivalent returns `commitment.to_field()` for `PartialUintNote`. --- # Dripper (Development Faucet) [Source](https://github.com/defi-wonderland/aztec-standards/tree/dev/src/dripper) The `aztec-standards` repository also ships a **Dripper** contract — a convenience faucet for minting tokens into private or public balances during development. It is not a formal AIP standard and should not be used in production. --- # Escrow [Source](https://github.com/defi-wonderland/aztec-standards/tree/dev/src/escrow_contract) The Escrow standard provides a minimal contract for holding tokens or NFTs on behalf of a single owner. Rather than storing the owner in mutable private state — which would require note discovery and decryption on every authorization check — the owner is encoded in the contract's own `salt` and thus baked into the contract address at deploy time. This makes authorization a simple field comparison against immutable deployment parameters: cheaper, simpler, and impossible to front-run. ## Escrow contract[​](#escrow-contract "Direct link to Escrow contract") ``` #[aztec] pub contract Escrow { #[external("private")] fn withdraw(token: AztecAddress, amount: u128, recipient: AztecAddress) { self.internal._assert_msg_sender(); self.call(Token::at(token).transfer_private_to_private( self.address, recipient, amount, 0, )); } #[external("private")] fn withdraw_nft(nft: AztecAddress, token_id: Field, recipient: AztecAddress) { self.internal._assert_msg_sender(); self.call(NFT::at(nft).transfer_private_to_private( self.address, recipient, token_id, 0, )); } #[internal("private")] fn _assert_msg_sender() { let msg_sender = self.msg_sender(); let escrow_instance: ContractInstance = get_contract_instance(self.address); assert(AztecAddress::from_field(escrow_instance.salt) == msg_sender, "Not Authorized"); } } ``` The authorization check in `_assert_msg_sender` reads the `salt` field of the escrow's own `ContractInstance` and compares it against `msg_sender`. Because the `ContractInstance` is fixed at deployment time, this check cannot be spoofed by manipulating storage after deployment. ## Escrow logic library[​](#escrow-logic-library "Direct link to Escrow logic library") A DeFi protocol (like a lending market or DEX) often needs to give each user a personal escrow to hold collateral or pending settlements. The standard ships a companion library that lets the parent contract deterministically compute escrow addresses from its own address and the user's keys — no onchain deployment transaction required: ``` #[contract_library_method] pub fn _get_escrow( context: &mut PrivateContext, escrow_class_id: Field, master_secret_keys: MasterSecretKeys, ) -> AztecAddress { let computed_public_keys: PublicKeys = _secret_keys_to_public_keys(master_secret_keys); let escrow_instance = ContractInstance { salt: context.this_address().to_field(), deployer: AztecAddress::from_field(0), contract_class_id: ContractClassId::from_field(escrow_class_id), initialization_hash: 0, public_keys: computed_public_keys, }; escrow_instance.to_address() } #[contract_library_method] pub fn _share_escrow( context: &mut PrivateContext, account: AztecAddress, escrow: AztecAddress, master_secret_keys: MasterSecretKeys, ) { let event_struct = EscrowDetailsLogContent { escrow, master_secret_keys }; emit_event_in_private(context, event_struct).deliver_to( account, MessageDelivery.ONCHAIN_CONSTRAINED, ); } ``` `_get_escrow` reconstructs the escrow address deterministically from the calling contract's address (used as the salt) and a set of master secret keys. `_share_escrow` emits an encrypted log so that the designated `account` can discover the escrow address and the keys needed to access its notes. Without this notification, the user's PXE would have no way to find the escrow or decrypt notes held there. The `ONCHAIN_CONSTRAINED` delivery mode ensures the log is validated against the note hash tree before the recipient's PXE trusts it. --- # Generic Proxy In Aztec, account contracts authorize every transaction the user sends and must be able to forward calls to any contract. However, Noir requires function signatures to be known at compile time, so an account contract cannot call an arbitrary function with an arbitrary number of arguments in a single generic entrypoint. The Generic Proxy contract solves this by providing a fixed set of forwarding functions — one per argument count — that the account contract can call. This avoids hard-coding every possible target function signature while keeping the account contract simple. ``` #[aztec] pub contract GenericProxy { #[external("private")] fn forward_private_0(target: AztecAddress, selector: FunctionSelector) { let _ = self.context.call_private_function_no_args(target, selector); } #[external("private")] fn forward_private_4(target: AztecAddress, selector: FunctionSelector, args: [Field; 4]) { let _ = self.context.call_private_function(target, selector, args); } #[external("private")] fn forward_private_4_and_return( target: AztecAddress, selector: FunctionSelector, args: [Field; 4], ) -> Field { let returns: Field = self.context.call_private_function(target, selector, args).get_preimage(); returns } // ... forward_private_1 through forward_private_8 } ``` The proxy exposes a family of `forward_private_N` functions, each accepting a different fixed argument count. Because Noir's type system requires array lengths to be known at compile time, the contract implements one overload per arity rather than a single variadic function. The `_and_return` variant captures the return value from the callee and passes it back to the caller. note The Generic Proxy does not implement any access control by itself. Callers are responsible for ensuring that forwarding to `target` is appropriate. In most protocols, the proxy is called from within an account contract that enforces its own authorization rules before delegating to the proxy. --- # Testing Contracts This guide shows you how to test your Aztec smart contracts using Noir's `TestEnvironment` for fast, lightweight testing. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * An Aztec contract project with functions to test * Basic understanding of Noir syntax tip For complex cross-chain or integration testing, see the [TypeScript testing guide](/developers/docs/aztec-js/how_to_test.md). ## Write Aztec contract tests[​](#write-aztec-contract-tests "Direct link to Write Aztec contract tests") Use `TestEnvironment` from `aztec-nr` for contract unit testing: * **Fast**: Lightweight environment with mocked components * **Convenient**: Similar to Foundry for simple contract tests * **Limited**: No rollup circuits or cross-chain messaging For complex end-to-end tests, use [TypeScript testing](/developers/docs/aztec-js/how_to_test.md) with `aztec.js`. ## Run your tests[​](#run-your-tests "Direct link to Run your tests") Execute Aztec Noir tests using: ``` aztec test ``` ### Test execution process[​](#test-execution-process "Direct link to Test execution process") 1. Compile contracts 2. Run `aztec test` warning Always use `aztec test` instead of `nargo test`. The `TestEnvironment` requires the test environment oracle resolver provided by the `aztec` CLI. ## Keep tests in the test crate[​](#keep-tests-in-the-test-crate "Direct link to Keep tests in the test crate") `aztec new` and `aztec init` scaffold a workspace with two crates: a contract crate and a separate test crate. For `aztec new my_project`, these are `my_project_contract` and `my_project_test`. Keep all `#[test]` functions in the test crate, not in the contract crate. If tests end up inside a contract crate, `aztec compile` emits a warning: ``` WARNING: Found tests in contract crate(s): my_project_contract::test_something Tests should be in a dedicated test crate, not in the contract crate. ``` The reason is **unnecessary recompilation**: a contract's compiled artifact depends on everything in its crate, so a test-only edit forces the contract to recompile even though its logic has not changed. Keeping tests in the separate test crate lets `aztec test` skip contract recompilation when only test code changed. ## Basic test structure[​](#basic-test-structure "Direct link to Basic test structure") `aztec new my_project` scaffolds a workspace with two crates: a `contract` crate that holds the contract code, and a separate `test` crate that holds your `#[test]` functions: ``` my_project/ ├── Nargo.toml # [workspace] members = ["my_project_contract", "my_project_test"] ├── my_project_contract/ │ ├── Nargo.toml # type = "contract" │ └── src/main.nr └── my_project_test/ ├── Nargo.toml # type = "lib", depends on my_project_contract └── src/lib.nr # #[test] functions go here ``` The motivation for the split of contract and tests into its own crates is **faster iteration**: editing a test does not invalidate the contract's compiled artifact, so `aztec test` skips contract recompilation when only test code changed. `aztec compile` warns if it finds `#[test]` functions inside a contract crate. The generated test crate template imports the contract by package name and then initializes it: ``` // my_project_test/src/lib.nr use aztec::test::helpers::test_environment::TestEnvironment; use my_project_contract::Main; #[test] unconstrained fn test_constructor() { let mut env = TestEnvironment::new(); let deployer = env.create_light_account(); let _contract_address = env.deploy("@my_project_contract/Main") .with_private_initializer(deployer, Main::interface().constructor()); } ``` Because tests live in their own crate, we refer to the contract via its crate name using the `@crate_name/ContractName` syntax. Test execution notes * Tests run in parallel by default * Use `unconstrained` functions for faster execution * See all `TestEnvironment` methods [here](/aztec-nr-api/#api_ref_version/noir_aztec/test/helpers/test_environment/struct.TestEnvironment) * It is always necessary to deploy a contract in order to test it If you'll add arguments to your contract's constructor you pass them directly to the constructor function in the test: ``` let initializer = MyContract::interface().constructor(param1, param2); ``` Since Aztec contracts can be initialized both in private and public or they can be interacted with without any kind of initialization (see [Contract creation](/developers/docs/foundational-topics/contract_creation.md) for how Aztec's deployment model differs from Ethereum's) there are 3 options on the deployer: ``` let contract_address = deployer.with_private_initializer(owner, initializer); let contract_address = deployer.with_public_initializer(owner, initializer); let contract_address = deployer.without_initializer(); ``` Reusable setup functions Create a setup function to avoid repeating initialization code: ``` pub unconstrained fn setup(initial_value: Field) -> (TestEnvironment, AztecAddress, AztecAddress) { let mut env = TestEnvironment::new(); let owner = env.create_light_account(); let initializer = MyContract::interface().constructor(initial_value, owner); let contract_address = env.deploy("@my_project_contract/MyContract").with_private_initializer(owner, initializer); (env, contract_address, owner) } #[test] unconstrained fn test_something() { let (env, contract_address, owner) = setup(42); // Your test logic here } ``` ## Calling contract functions[​](#calling-contract-functions "Direct link to Calling contract functions") TestEnvironment provides methods for different function types: ### Private functions[​](#private-functions "Direct link to Private functions") ``` // Call private function env.call_private(caller, Token::at(token_address).transfer(recipient, 100)); // Returns the result let result = env.call_private(owner, Contract::at(address).get_private_data()); ``` ### Public functions[​](#public-functions "Direct link to Public functions") ``` // Call public function env.call_public(caller, Token::at(token_address).mint_to_public(recipient, 100)); // View public state (read-only) let balance = env.view_public(Token::at(token_address).balance_of_public(owner)); ``` ### Utility/Unconstrained functions[​](#utilityunconstrained-functions "Direct link to Utility/Unconstrained functions") ``` // Simulate utility/view functions (unconstrained) let total = env.execute_utility(Token::at(token_address).balance_of_private(owner)); ``` Helper function pattern Create helper functions for common assertions: ``` pub unconstrained fn check_balance( env: TestEnvironment, token_address: AztecAddress, owner: AztecAddress, expected: u128, ) { assert_eq( env.execute_utility(Token::at(token_address).balance_of_private(owner)), expected ); } ``` ## Creating accounts[​](#creating-accounts "Direct link to Creating accounts") Two types of accounts are available: ``` // Light account - fast, limited features let owner = env.create_light_account(); // Contract account - full features, slower let owner = env.create_contract_account(); ``` Account type comparison **Light accounts:** * Fast to create * Work for simple transfers and tests * Cannot process authwits * No account contract deployed **Contract accounts:** * Required for authwit testing * Support account abstraction features * Slower to create (deploys account contract) * Needed for cross-contract authorization Choosing account types ``` pub unconstrained fn setup(with_authwits: bool) -> (TestEnvironment, AztecAddress, AztecAddress) { let mut env = TestEnvironment::new(); let (owner, recipient) = if with_authwits { (env.create_contract_account(), env.create_contract_account()) } else { (env.create_light_account(), env.create_light_account()) }; // ... deploy contracts ... (env, owner, recipient) } ``` ## Testing with authwits[​](#testing-with-authwits "Direct link to Testing with authwits") [Authwits](/developers/docs/aztec-nr/framework-description/authentication_witnesses.md) allow one account to authorize another to act on its behalf. warning Authwits require **contract accounts**, not light accounts. ### Import authwit helpers[​](#import-authwit-helpers "Direct link to Import authwit helpers") ``` use aztec::test::helpers::authwit::{ add_private_authwit_from_call, add_public_authwit_from_call, }; ``` ### Private authwits[​](#private-authwits "Direct link to Private authwits") ``` #[test] unconstrained fn test_private_authwit() { // Setup with contract accounts (required for authwits) let (env, token_address, owner, spender) = setup(true); // Create the call that needs authorization let amount = 100; let nonce = 7; // Non-zero nonce for authwit let burn_call = Token::at(token_address).burn_private(owner, amount, nonce); // Grant authorization from owner to spender add_private_authwit_from_call(env, owner, spender, burn_call); // Spender can now execute the authorized action env.call_private(spender, burn_call); } ``` ### Public authwits[​](#public-authwits "Direct link to Public authwits") ``` #[test] unconstrained fn test_public_authwit() { let (env, token_address, owner, spender) = setup(true); // Create public action that needs authorization let transfer_call = Token::at(token_address).transfer_in_public(owner, recipient, 100, nonce); // Grant public authorization add_public_authwit_from_call(env, owner, spender, transfer_call); // Execute with authorization env.call_public(spender, transfer_call); } ``` ## Time traveling[​](#time-traveling "Direct link to Time traveling") Contract calls do not advance the timestamp by default, despite each of them resulting in a block with a single transaction. Block timestamp can instead be manually manipulated by any of the following methods: ``` // Sets the timestamp of the next block to be mined, i.e. of the next public execution. Does not affect private execution. env.set_next_block_timestamp(block_timestamp); // Same as `set_next_block_timestamp`, but moving time forward by `duration` instead of advancing to a target timestamp. env.advance_next_block_timestamp_by(duration); // Mines an empty block at a given timestamp, causing the next public execution to occur at this time (like `set_next_block_timestamp`), but also allowing for private execution to happen using this empty block as the anchor block. env.mine_block_at(block_timestamp); ``` ## Testing failure cases[​](#testing-failure-cases "Direct link to Testing failure cases") Test functions that should fail using annotations: ### Generic failure[​](#generic-failure "Direct link to Generic failure") ``` #[test(should_fail)] unconstrained fn test_unauthorized_access() { let (env, contract, owner) = setup(false); let attacker = env.create_light_account(); // This should fail because attacker is not authorized env.call_private(attacker, Contract::at(contract).owner_only_function()); } ``` ### Specific error message[​](#specific-error-message "Direct link to Specific error message") ``` #[test(should_fail_with = "Balance too low")] unconstrained fn test_insufficient_balance() { let (env, token, owner, recipient) = setup(false); // Try to transfer more than available let balance = 100; let transfer_amount = 101; env.call_private(owner, Token::at(token).transfer(recipient, transfer_amount)); } ``` ### Testing authwit failures[​](#testing-authwit-failures "Direct link to Testing authwit failures") ``` #[test(should_fail_with = "Unknown auth witness for message hash")] unconstrained fn test_missing_authwit() { let (env, token, owner, spender) = setup(true); // Try to burn without authorization let burn_call = Token::at(token).burn_private(owner, 100, 1); // No authwit granted - this should fail env.call_private(spender, burn_call); } ``` ## Test environment oracle versioning[​](#test-environment-oracle-versioning "Direct link to Test environment oracle versioning") The test environment uses an oracle interface to communicate between your Noir test code and the `aztec test` CLI. This interface is versioned so that mismatches between the Aztec.nr dependency used to compile the test and the CLI version are detected automatically. The version uses two components, `major.minor`, with the same compatibility rules as [PXE oracle versioning](/developers/docs/foundational-topics/pxe.md#oracle-versioning): * **`major`** must match exactly. A major bump means oracles were removed or had their signatures changed, and a test environment on a different major cannot safely run the test. * **`minor`** indicates additive changes (new oracles). The test environment uses a best-effort approach: a test compiled against a higher `minor` is still allowed to run, and an error is only thrown if the test actually invokes an oracle the test environment does not know about. ### Resolving a version mismatch[​](#resolving-a-version-mismatch "Direct link to Resolving a version mismatch") If you see an error like *"Incompatible test environment version: The test was compiled with a newer version of Aztec.nr than your test environment supports"*, the test uses oracles from a newer Aztec.nr than your `aztec test` CLI supports. To fix it, make sure your `aztec` CLI version and the `aztec` dependency in the test crate's `Nargo.toml` are on the same release. Note that the test crate's Aztec.nr version can differ from the contract crate's version, depending on your project configuration. For example, if your CLI is on `v4.3.1`, the test crate's `Nargo.toml` should reference the matching tag: ``` [dependencies] aztec = { git="https://github.com/AztecProtocol/aztec-nr", tag="v4.3.1", directory="aztec" } ``` If the test environment reports a version that *should* include every oracle the test needs but an oracle is still missing, this is likely a bug rather than a version problem. --- # Aztec CLI Reference *This documentation is auto-generated from the `aztec` CLI help output.* *Generated: Wed 10 Jun 2026 20:29:09 UTC* *Command: `aztec`* ## Table of Contents[​](#table-of-contents "Direct link to Table of Contents") * [aztec](#aztec) * [aztec add-l1-validator](#aztec-add-l1-validator) * [aztec advance-epoch](#aztec-advance-epoch) * [aztec block-number](#aztec-block-number) * [aztec bridge-erc20](#aztec-bridge-erc20) * [aztec codegen](#aztec-codegen) * [aztec compile](#aztec-compile) * [aztec compute-genesis-values](#aztec-compute-genesis-values) * [aztec compute-selector](#aztec-compute-selector) * [aztec debug-rollup](#aztec-debug-rollup) * [aztec decode-enr](#aztec-decode-enr) * [aztec deploy-l1-contracts](#aztec-deploy-l1-contracts) * [aztec deploy-new-rollup](#aztec-deploy-new-rollup) * [aztec deposit-governance-tokens](#aztec-deposit-governance-tokens) * [aztec example-contracts](#aztec-example-contracts) * [aztec execute-governance-proposal](#aztec-execute-governance-proposal) * [aztec fast-forward-epochs](#aztec-fast-forward-epochs) * [aztec generate-bls-keypair](#aztec-generate-bls-keypair) * [aztec generate-bootnode-enr](#aztec-generate-bootnode-enr) * [aztec generate-keys](#aztec-generate-keys) * [aztec generate-l1-account](#aztec-generate-l1-account) * [aztec generate-p2p-private-key](#aztec-generate-p2p-private-key) * [aztec generate-secret-and-hash](#aztec-generate-secret-and-hash) * [aztec get-block](#aztec-get-block) * [aztec get-canonical-sponsored-fpc-address](#aztec-get-canonical-sponsored-fpc-address) * [aztec get-current-min-fee](#aztec-get-current-min-fee) * [aztec get-l1-addresses](#aztec-get-l1-addresses) * [aztec get-l1-balance](#aztec-get-l1-balance) * [aztec get-l1-to-l2-message-witness](#aztec-get-l1-to-l2-message-witness) * [aztec get-logs](#aztec-get-logs) * [aztec get-node-info](#aztec-get-node-info) * [aztec init](#aztec-init) * [aztec inspect-contract](#aztec-inspect-contract) * [aztec migrate-ha-db](#aztec-migrate-ha-db) * [aztec migrate-ha-db down](#aztec-migrate-ha-db-down) * [aztec migrate-ha-db up](#aztec-migrate-ha-db-up) * [aztec new](#aztec-new) * [aztec parse-parameter-struct](#aztec-parse-parameter-struct) * [aztec preload-crs](#aztec-preload-crs) * [aztec profile](#aztec-profile) * [aztec profile flamegraph](#aztec-profile-flamegraph) * [aztec profile gates](#aztec-profile-gates) * [aztec propose-with-lock](#aztec-propose-with-lock) * [aztec prune-rollup](#aztec-prune-rollup) * [aztec remove-l1-validator](#aztec-remove-l1-validator) * [aztec sequencers](#aztec-sequencers) * [aztec setup-protocol-contracts](#aztec-setup-protocol-contracts) * [aztec start](#aztec-start) * [aztec test](#aztec-test) * [aztec trigger-seed-snapshot](#aztec-trigger-seed-snapshot) * [aztec update](#aztec-update) * [aztec validator-keys|valKeys](#aztec-validator-keys%7Cvalkeys) * [aztec vote-on-governance-proposal](#aztec-vote-on-governance-proposal) ## aztec[​](#aztec "Direct link to aztec") Aztec command line interface **Usage:** ``` aztec [options] [command] ``` **Available Commands:** * `add-l1-validator [options]` - Adds a validator to the L1 rollup contract via a direct deposit. * `advance-epoch [options]` - Use L1 cheat codes to warp time until the next epoch. * `block-number [options]` - Gets the current Aztec L2 block number. * `bridge-erc20 [options] ` - Bridges ERC20 tokens to L2. * `codegen [options] ` - Validates and generates an Aztec Contract ABI from Noir ABI. * `compile [nargo-args...]` - Compile Aztec Noir contracts using nargo and postprocess them to generate transpiled artifacts and verification keys. All options are forwarded to nargo compile. * `compute-genesis-values [options]` - Computes genesis values (VK tree root, protocol contracts hash, genesis archive root). * `compute-selector ` - Given a function signature, it computes a selector * `debug-rollup [options]` - Debugs the rollup contract. * `decode-enr ` - Decodes an ENR record * `deploy-l1-contracts [options]` - Deploys all necessary Ethereum contracts for Aztec. * `deploy-new-rollup [options]` - Deploys a new rollup contract and adds it to the registry (if you are the owner). * `deposit-governance-tokens [options]` - Deposits governance tokens to the governance contract. * `example-contracts` - Lists the example contracts available to deploy from @aztec/noir-contracts.js * `execute-governance-proposal [options]` - Executes a governance proposal. * `fast-forward-epochs [options]` - Fast forwards the epoch of the L1 rollup contract. * `generate-bls-keypair [options]` - Generate a BLS keypair with convenience flags * `generate-bootnode-enr [options] ` - Generates the encoded ENR record for a bootnode. * `generate-keys [options]` - Generates encryption and signing private keys. * `generate-l1-account [options]` - Generates a new private key for an account on L1. * `generate-p2p-private-key` - Generates a LibP2P peer private key. * `generate-secret-and-hash` - Generates an arbitrary secret (Fr), and its hash (using aztec-nr defaults) * `get-block [options] [blockNumber]` - Gets info for a given block or latest. * `get-canonical-sponsored-fpc-address` - Gets the canonical SponsoredFPC address for this any testnet running on the same version as this CLI * `get-current-min-fee [options]` - Gets the current base fee. * `get-l1-addresses [options]` - Gets the addresses of the L1 contracts. * `get-l1-balance [options] ` - Gets the balance of an ERC token in L1 for the given Ethereum address. * `get-l1-to-l2-message-witness [options]` - Gets a L1 to L2 message witness. * `get-logs [options]` - Gets all the public logs from an intersection of all the filter params. * `get-node-info [options]` - Gets the information of an Aztec node from a PXE or directly from an Aztec node. * `help [command]` - display help for command * `init` - creates a new Aztec Noir workspace in the current directory. * `inspect-contract ` - Shows list of external callable functions for a contract * `migrate-ha-db` - Run validator-ha-signer database migrations * `new ` - creates a new Aztec Noir workspace in its own directory (or creates a new contract-test crates pair and adds it to the current workspace if run in workspace). * `parse-parameter-struct [options] ` - Helper for parsing an encoded string into a contract's parameter struct. * `preload-crs` - Preload the points data needed for proving and verifying * `profile` - Profile compiled Aztec artifacts. * `propose-with-lock [options]` - Makes a proposal to governance with a lock * `prune-rollup [options]` - Prunes the pending chain on the rollup contract. * `remove-l1-validator [options]` - Removes a validator to the L1 rollup contract. * `sequencers [options] [who]` - Manages or queries registered sequencers on the L1 rollup contract. * `setup-protocol-contracts [options]` - Bootstrap the blockchain by initializing all the protocol contracts * `start [options]` - Starts Aztec modules. Options for each module can be set as key-value pairs (e.g. "option1=value1,option2=value2") or as environment variables. * `test [options]` - starts a TXE and runs "nargo test" using it as the oracle resolver. * `trigger-seed-snapshot [options]` - Triggers a seed snapshot for the next epoch. * `update [options] [projectPath]` - Updates Nodejs and Noir dependencies * `validator-keys|valKeys` - Manage validator keystores for node operators * `vote-on-governance-proposal [options]` - Votes on a governance proposal. **Options:** * `-V --version` - output the version number * `-h --help` - display help for command ### Subcommands[​](#subcommands "Direct link to Subcommands") ### aztec add-l1-validator[​](#aztec-add-l1-validator "Direct link to aztec add-l1-validator") Adds a validator to the L1 rollup contract via a direct deposit. **Usage:** ``` aztec add-l1-validator [options] ``` **Options:** * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \["http\://localhost:8545"], env: ETHEREUM\_HOSTS) * `--network ` - Network to execute against (env: NETWORK) * `-pk, --private-key ` - The private key to use sending the transaction * `-m, --mnemonic ` - The mnemonic to use sending the transaction (default: "test test test test test test test test test test test junk") * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `--attester
` - ethereum address of the attester * `--withdrawer
` - ethereum address of the withdrawer * `--bls-secret-key ` - The BN254 scalar field element used as a secret key for BLS signatures. Will be associated with the attester address. * `--move-with-latest-rollup` - Whether to move with the latest rollup (default: true) * `--rollup ` - Rollup contract address * `-h, --help` - display help for command ### aztec advance-epoch[​](#aztec-advance-epoch "Direct link to aztec advance-epoch") Use L1 cheat codes to warp time until the next epoch. **Usage:** ``` aztec advance-epoch [options] ``` **Options:** * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \["http\://localhost:8545"], env: ETHEREUM\_HOSTS) * `-n, --node-url ` - URL of the Aztec node (default: "", env: AZTEC\_NODE\_URL) * `-h, --help` - display help for command ### aztec block-number[​](#aztec-block-number "Direct link to aztec block-number") Gets the current Aztec L2 block number. **Usage:** ``` aztec block-number [options] ``` **Options:** * `-n, --node-url ` - URL of the Aztec node (default: "", env: AZTEC\_NODE\_URL) * `-h, --help` - display help for command ### aztec bridge-erc20[​](#aztec-bridge-erc20 "Direct link to aztec bridge-erc20") Bridges ERC20 tokens to L2. **Usage:** ``` aztec bridge-erc20 [options] ``` **Options:** * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \["http\://localhost:8545"], env: ETHEREUM\_HOSTS) * `-m, --mnemonic ` - The mnemonic to use for deriving the Ethereum address that will mint and bridge (default: "test test test test test test test test test test test junk") * `--mint` - Mint the tokens on L1 (default: false) * `--private` - If the bridge should use the private flow (default: false) * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `-t, --token ` - The address of the token to bridge * `-p, --portal ` - The address of the portal contract * `-f, --faucet ` - The address of the faucet contract (only used if minting) * `--l1-private-key ` - The private key to use for deployment * `--json` - Output the claim in JSON format * `-h, --help` - display help for command ### aztec codegen[​](#aztec-codegen "Direct link to aztec codegen") Validates and generates an Aztec Contract ABI from Noir ABI. **Usage:** ``` aztec codegen [options] ``` **Options:** * `-o, --outdir ` - Output folder for the generated code. * `-f, --force` - Force code generation even when the contract has not changed. * `-h, --help` - display help for command ### aztec compile[​](#aztec-compile "Direct link to aztec compile") Compile Aztec Noir contracts using nargo and postprocess them to generate transpiled artifacts and verification keys. All options are forwarded to nargo compile. **Usage:** ``` aztec compile [options] [nargo-args...] ``` **Options:** * `-h, --help` - display help for command * `--package ` - The name of the package to run the command on. By default run on the first one found moving up along the ancestors of the current directory * `--workspace` - Run on all packages in the workspace * `--force` - Force a full recompilation * `--print-acir` - Display the ACIR for compiled circuit, including the Brillig bytecode * `--deny-warnings` - Treat all warnings as errors * `--silence-warnings` - Suppress warnings * `--debug-comptime-in-file ` - Enable printing results of comptime evaluation: provide a path suffix for the module to debug, e.g. "package\_name/src/main.nr" * `--skip-underconstrained-check` - Flag to turn off the compiler check for under constrained values. Warning: This can improve compilation speed but can also lead to correctness errors. This check should always be run on production code * `--skip-brillig-constraints-check` - Flag to turn off the compiler check for missing Brillig call constraints. Warning: This can improve compilation speed but can also lead to correctness errors. This check should always be run on production code * `--count-array-copies` - Count the number of arrays that are copied in an unconstrained context for performance debugging * `--inliner-aggressiveness ` - Setting to decide on an inlining strategy for Brillig functions. A more aggressive inliner should generate larger programs but more optimized A less aggressive inliner should generate smaller programs \[default: 9223372036854775807] * `-Z, --unstable-features ` - Unstable features to enable for this current build. If non-empty, it disables unstable features required in crate manifests. * `--no-unstable-features` - Disable any unstable features required in crate manifests * `-h, --help` - Print help (see a summary with '-h') ### aztec compute-genesis-values[​](#aztec-compute-genesis-values "Direct link to aztec compute-genesis-values") Computes genesis values (VK tree root, protocol contracts hash, genesis archive root). **Usage:** ``` aztec compute-genesis-values [options] ``` **Options:** * `--test-accounts ` - Include initial test accounts in genesis state (env: TEST\_ACCOUNTS) * `--sponsored-fpc ` - Include sponsored FPC contract in genesis state (env: SPONSORED\_FPC) * `-h, --help` - display help for command ### aztec compute-selector[​](#aztec-compute-selector "Direct link to aztec compute-selector") Given a function signature, it computes a selector **Usage:** ``` aztec compute-selector [options] ``` **Options:** * `-h, --help` - display help for command ### aztec debug-rollup[​](#aztec-debug-rollup "Direct link to aztec debug-rollup") Debugs the rollup contract. **Usage:** ``` aztec debug-rollup [options] ``` **Options:** * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \["http\://localhost:8545"], env: ETHEREUM\_HOSTS) * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `--rollup
` - ethereum address of the rollup contract * `-h, --help` - display help for command ### aztec decode-enr[​](#aztec-decode-enr "Direct link to aztec decode-enr") Decodes and ENR record **Usage:** ``` aztec decode-enr [options] ``` **Options:** * `-h, --help` - display help for command ### aztec deploy-l1-contracts[​](#aztec-deploy-l1-contracts "Direct link to aztec deploy-l1-contracts") Deploys all necessary Ethereum contracts for Aztec. **Usage:** ``` aztec deploy-l1-contracts [options] ``` **Options:** * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \["http\://localhost:8545"], env: ETHEREUM\_HOSTS) * `-pk, --private-key ` - The private key to use for deployment * `--validators ` - Comma separated list of validators * `-m, --mnemonic ` - The mnemonic to use in deployment (default: "test test test test test test test test test test test junk") * `-i, --mnemonic-index ` - The index of the mnemonic to use in deployment (default: 0) * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `--json` - Output the contract addresses in JSON format * `--test-accounts` - Populate genesis state with initial fee juice for test accounts * `--sponsored-fpc` - Populate genesis state with a testing sponsored FPC contract * `--real-verifier` - Deploy the real verifier (default: false) * `--existing-token
` - Use an existing ERC20 for both fee and staking * `-h, --help` - display help for command ### aztec deploy-new-rollup[​](#aztec-deploy-new-rollup "Direct link to aztec deploy-new-rollup") Deploys a new rollup contract and adds it to the registry (if you are the owner). **Usage:** ``` aztec deploy-new-rollup [options] ``` **Options:** * `-r, --registry-address ` - The address of the registry contract * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \["http\://localhost:8545"], env: ETHEREUM\_HOSTS) * `-pk, --private-key ` - The private key to use for deployment * `--validators ` - Comma separated list of validators * `-m, --mnemonic ` - The mnemonic to use in deployment (default: "test test test test test test test test test test test junk") * `-i, --mnemonic-index ` - The index of the mnemonic to use in deployment (default: 0) * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `--json` - Output the contract addresses in JSON format * `--test-accounts` - Populate genesis state with initial fee juice for test accounts * `--sponsored-fpc` - Populate genesis state with a testing sponsored FPC contract * `--real-verifier` - Deploy the real verifier (default: false) * `-h, --help` - display help for command ### aztec deposit-governance-tokens[​](#aztec-deposit-governance-tokens "Direct link to aztec deposit-governance-tokens") Deposits governance tokens to the governance contract. **Usage:** ``` aztec deposit-governance-tokens [options] ``` **Options:** * `-r, --registry-address ` - The address of the registry contract * `--recipient ` - The recipient of the tokens * `-a, --amount ` - The amount of tokens to deposit * `--mint` - Mint the tokens on L1 (default: false) * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \["http\://localhost:8545"], env: ETHEREUM\_HOSTS) * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `-p, --private-key ` - The private key to use to deposit * `-m, --mnemonic ` - The mnemonic to use to deposit (default: "test test test test test test test test test test test junk") * `-i, --mnemonic-index ` - The index of the mnemonic to use to deposit (default: 0) * `-h, --help` - display help for command ### aztec example-contracts[​](#aztec-example-contracts "Direct link to aztec example-contracts") Lists the example contracts available to deploy from @aztec/noir-contracts.js **Usage:** ``` aztec example-contracts [options] ``` **Options:** * `-h, --help` - display help for command ### aztec execute-governance-proposal[​](#aztec-execute-governance-proposal "Direct link to aztec execute-governance-proposal") Executes a governance proposal. **Usage:** ``` aztec execute-governance-proposal [options] ``` **Options:** * `-p, --proposal-id ` - The ID of the proposal * `-r, --registry-address ` - The address of the registry contract * `--wait ` - Whether to wait until the proposal is executable * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \["http\://localhost:8545"], env: ETHEREUM\_HOSTS) * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `-pk, --private-key ` - The private key to use to vote * `-m, --mnemonic ` - The mnemonic to use to vote (default: "test test test test test test test test test test test junk") * `-i, --mnemonic-index ` - The index of the mnemonic to use to vote (default: 0) * `-h, --help` - display help for command ### aztec fast-forward-epochs[​](#aztec-fast-forward-epochs "Direct link to aztec fast-forward-epochs") *Help for this command is currently unavailable due to a technical issue with option serialization.* ### aztec generate-bls-keypair[​](#aztec-generate-bls-keypair "Direct link to aztec generate-bls-keypair") Generate a BLS keypair with convenience flags **Usage:** ``` aztec generate-bls-keypair [options] ``` **Options:** * `--mnemonic ` - Mnemonic for BLS derivation * `--ikm ` - Initial keying material for BLS (alternative to mnemonic) * `--bls-path ` - EIP-2334 path (default m/12381/3600/0/0/0) * `--g2` - Derive on G2 subgroup * `--compressed` - Output compressed public key * `--json` - Print JSON output to stdout * `--out ` - Write output to file * `-h, --help` - display help for command ### aztec generate-bootnode-enr[​](#aztec-generate-bootnode-enr "Direct link to aztec generate-bootnode-enr") Generates the encoded ENR record for a bootnode. **Usage:** ``` aztec generate-bootnode-enr [options] ``` **Options:** * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `-h, --help` - display help for command ### aztec generate-keys[​](#aztec-generate-keys "Direct link to aztec generate-keys") Generates and encryption and signing private key pair. **Usage:** ``` aztec generate-keys [options] ``` **Options:** * `--json` - Output the keys in JSON format * `-h, --help` - display help for command ### aztec generate-l1-account[​](#aztec-generate-l1-account "Direct link to aztec generate-l1-account") Generates a new private key for an account on L1. **Usage:** ``` aztec generate-l1-account [options] ``` **Options:** * `--json` - Output the private key in JSON format * `-h, --help` - display help for command ### aztec generate-p2p-private-key[​](#aztec-generate-p2p-private-key "Direct link to aztec generate-p2p-private-key") Generates a private key that can be used for running a node on a LibP2P network. **Usage:** ``` aztec generate-p2p-private-key [options] ``` **Options:** * `-h, --help` - display help for command ### aztec generate-secret-and-hash[​](#aztec-generate-secret-and-hash "Direct link to aztec generate-secret-and-hash") Generates an arbitrary secret (Fr), and its hash (using aztec-nr defaults) **Usage:** ``` aztec generate-secret-and-hash [options] ``` **Options:** * `-h, --help` - display help for command ### aztec get-block[​](#aztec-get-block "Direct link to aztec get-block") Gets info for a given block or latest. **Usage:** ``` aztec get-block [options] [blockNumber] ``` **Options:** * `-n, --node-url ` - URL of the Aztec node (default: "", env: AZTEC\_NODE\_URL) * `-h, --help` - display help for command ### aztec get-canonical-sponsored-fpc-address[​](#aztec-get-canonical-sponsored-fpc-address "Direct link to aztec get-canonical-sponsored-fpc-address") Gets the canonical SponsoredFPC address for this any testnet running on the same version as this CLI **Usage:** ``` aztec get-canonical-sponsored-fpc-address [options] ``` **Options:** * `-h, --help` - display help for command ### aztec get-current-min-fee[​](#aztec-get-current-min-fee "Direct link to aztec get-current-min-fee") Gets the current base fee. **Usage:** ``` aztec get-current-min-fee [options] ``` **Options:** * `-n, --node-url ` - URL of the Aztec node (default: "", env: AZTEC\_NODE\_URL) * `-h, --help` - display help for command ### aztec get-l1-addresses[​](#aztec-get-l1-addresses "Direct link to aztec get-l1-addresses") Gets the addresses of the L1 contracts. **Usage:** ``` aztec get-l1-addresses [options] ``` **Options:** * `-r, --registry-address ` - The address of the registry contract * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \["http\://localhost:8545"], env: ETHEREUM\_HOSTS) * `-v, --rollup-version ` - The version of the rollup * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `--json` - Output the addresses in JSON format * `-h, --help` - display help for command ### aztec get-l1-balance[​](#aztec-get-l1-balance "Direct link to aztec get-l1-balance") Gets the balance of an ERC token in L1 for the given Ethereum address. **Usage:** ``` aztec get-l1-balance [options] ``` **Options:** * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \["http\://localhost:8545"], env: ETHEREUM\_HOSTS) * `-t, --token ` - The address of the token to check the balance of * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `--json` - Output the balance in JSON format * `-h, --help` - display help for command ### aztec get-l1-to-l2-message-witness[​](#aztec-get-l1-to-l2-message-witness "Direct link to aztec get-l1-to-l2-message-witness") Gets a L1 to L2 message witness. **Usage:** ``` aztec get-l1-to-l2-message-witness [options] ``` **Options:** * `-ca, --contract-address
` - Aztec address of the contract. * `--message-hash ` - The L1 to L2 message hash. * `--secret ` - The secret used to claim the L1 to L2 message * `-n, --node-url ` - URL of the Aztec node (default: "", env: AZTEC\_NODE\_URL) * `-h, --help` - display help for command ### aztec get-logs[​](#aztec-get-logs "Direct link to aztec get-logs") Gets all the public logs from an intersection of all the filter params. **Usage:** ``` aztec get-logs [options] ``` **Options:** * `-tx, --tx-hash ` - A transaction hash to get the receipt for. * `-fb, --from-block ` - Initial block number for getting logs (defaults to 1). * `-tb, --to-block ` - Up to which block to fetch logs (defaults to latest). * `-ca, --contract-address
` - Contract address to filter logs by. * `-n, --node-url ` - URL of the Aztec node (default: "", env: AZTEC\_NODE\_URL) * `--follow` - If set, will keep polling for new logs until interrupted. * `-h, --help` - display help for command ### aztec get-node-info[​](#aztec-get-node-info "Direct link to aztec get-node-info") Gets the information of an Aztec node from a PXE or directly from an Aztec node. **Usage:** ``` aztec get-node-info [options] ``` **Options:** * `--json` - Emit output as json * `-n, --node-url ` - URL of the Aztec node (default: "", env: AZTEC\_NODE\_URL) * `-h, --help` - display help for command ### aztec init[​](#aztec-init "Direct link to aztec init") Aztec Init - Create a new Aztec Noir project in the current directory **Usage:** ``` aztec init ``` **Options:** * `-h, --help` - Print help ### aztec inspect-contract[​](#aztec-inspect-contract "Direct link to aztec inspect-contract") Shows list of external callable functions for a contract **Usage:** ``` aztec inspect-contract [options] ``` **Options:** * `-h, --help` - display help for command ### aztec migrate-ha-db[​](#aztec-migrate-ha-db "Direct link to aztec migrate-ha-db") Run validator-ha-signer database migrations **Usage:** ``` aztec migrate-ha-db [options] [command] ``` **Available Commands:** * `down [options]` - Rollback the last migration * `help [command]` - display help for command * `up [options]` - Apply pending migrations **Options:** * `-h --help` - display help for command #### Subcommands[​](#subcommands-1 "Direct link to Subcommands") #### aztec migrate-ha-db down[​](#aztec-migrate-ha-db-down "Direct link to aztec migrate-ha-db down") Rollback the last migration **Usage:** ``` aztec migrate-ha-db down [options] ``` **Options:** * `--database-url ` - PostgreSQL connection string * `--verbose` - Enable verbose output (default: false) * `-h, --help` - display help for command #### aztec migrate-ha-db up[​](#aztec-migrate-ha-db-up "Direct link to aztec migrate-ha-db up") Apply pending migrations **Usage:** ``` aztec migrate-ha-db up [options] ``` **Options:** * `--database-url ` - PostgreSQL connection string * `--verbose` - Enable verbose output (default: false) * `-h, --help` - display help for command ### aztec new[​](#aztec-new "Direct link to aztec new") Aztec New - Create a new Aztec Noir project or add a contract to an existing workspace **Usage:** ``` aztec new ``` **Options:** * `-h, --help` - Print help ### aztec parse-parameter-struct[​](#aztec-parse-parameter-struct "Direct link to aztec parse-parameter-struct") Helper for parsing an encoded string into a contract's parameter struct. **Usage:** ``` aztec parse-parameter-struct [options] ``` **Options:** * `-c, --contract-artifact ` - A compiled Aztec.nr contract's ABI in JSON format or name of a contract ABI exported by @aztec/noir-contracts.js * `-p, --parameter ` - The name of the struct parameter to decode into * `-h, --help` - display help for command ### aztec preload-crs[​](#aztec-preload-crs "Direct link to aztec preload-crs") Preload the points data needed for proving and verifying **Usage:** ``` aztec preload-crs [options] ``` **Options:** * `-h, --help` - display help for command ### aztec profile[​](#aztec-profile "Direct link to aztec profile") Profile compiled Aztec artifacts. **Usage:** ``` aztec profile [options] [command] ``` **Available Commands:** * `flamegraph ` - Generate a gate count flamegraph SVG for a contract function. * `gates [options] [target-dir]` - Display gate counts for all compiled Aztec artifacts in a target directory. * `help [command]` - display help for command **Options:** * `-h --help` - display help for command #### Subcommands[​](#subcommands-2 "Direct link to Subcommands") #### aztec profile flamegraph[​](#aztec-profile-flamegraph "Direct link to aztec profile flamegraph") Generate a gate count flamegraph SVG for a contract function. **Usage:** ``` aztec profile flamegraph [options] ``` **Options:** * `-h, --help` - display help for command #### aztec profile gates[​](#aztec-profile-gates "Direct link to aztec profile gates") Display gate counts for all compiled Aztec artifacts in a target directory. **Usage:** ``` aztec profile gates [options] [target-dir] ``` **Options:** * `--json` - Output gate counts as JSON instead of a table (default: false) * `-h, --help` - display help for command ### aztec propose-with-lock[​](#aztec-propose-with-lock "Direct link to aztec propose-with-lock") Makes a proposal to governance with a lock **Usage:** ``` aztec propose-with-lock [options] ``` **Options:** * `-r, --registry-address ` - The address of the registry contract * `-p, --payload-address ` - The address of the payload contract * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \["http\://localhost:8545"], env: ETHEREUM\_HOSTS) * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `-pk, --private-key ` - The private key to use to propose * `-m, --mnemonic ` - The mnemonic to use to propose (default: "test test test test test test test test test test test junk") * `-i, --mnemonic-index ` - The index of the mnemonic to use to propose (default: 0) * `--json` - Output the proposal ID in JSON format * `-h, --help` - display help for command ### aztec prune-rollup[​](#aztec-prune-rollup "Direct link to aztec prune-rollup") Prunes the pending chain on the rollup contract. **Usage:** ``` aztec prune-rollup [options] ``` **Options:** * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \["http\://localhost:8545"], env: ETHEREUM\_HOSTS) * `-pk, --private-key ` - The private key to use for deployment * `-m, --mnemonic ` - The mnemonic to use in deployment (default: "test test test test test test test test test test test junk") * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `--rollup
` - ethereum address of the rollup contract * `-h, --help` - display help for command ### aztec remove-l1-validator[​](#aztec-remove-l1-validator "Direct link to aztec remove-l1-validator") Removes a validator to the L1 rollup contract. **Usage:** ``` aztec remove-l1-validator [options] ``` **Options:** * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \["http\://localhost:8545"], env: ETHEREUM\_HOSTS) * `-pk, --private-key ` - The private key to use for deployment * `-m, --mnemonic ` - The mnemonic to use in deployment (default: "test test test test test test test test test test test junk") * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `--validator
` - ethereum address of the validator * `--rollup
` - ethereum address of the rollup contract * `-h, --help` - display help for command ### aztec sequencers[​](#aztec-sequencers "Direct link to aztec sequencers") Manages or queries registered sequencers on the L1 rollup contract. **Usage:** ``` aztec sequencers [options] [who] ``` **Options:** * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \["http\://localhost:8545"]) * `-m, --mnemonic ` - The mnemonic for the sender of the tx (default: "test test test test test test test test test test test junk") * `--block-number ` - Block number to query next sequencer for * `-n, --node-url ` - URL of the Aztec node (default: "", env: AZTEC\_NODE\_URL) * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `-h, --help` - display help for command ### aztec setup-protocol-contracts[​](#aztec-setup-protocol-contracts "Direct link to aztec setup-protocol-contracts") Bootstrap the blockchain by initializing all the protocol contracts **Usage:** ``` aztec setup-protocol-contracts [options] ``` **Options:** * `-n, --node-url ` - URL of the Aztec node (default: "", env: AZTEC\_NODE\_URL) * `--testAccounts` - Deploy funded test accounts. * `--json` - Output the contract addresses in JSON format * `-h, --help` - display help for command ### aztec start[​](#aztec-start "Direct link to aztec start") **MISC** * `--network ` Network to run Aztec on *Environment: `$NETWORK`* * `--enable-version-check` (default: `true`) Check if the node is running the latest version and is following the latest rollup *Environment: `$ENABLE_VERSION_CHECK`* * `--sync-mode ` (default: `snapshot`) Set sync mode to `full` to always sync via L1, `snapshot` to download a snapshot if there is no local data, `force-snapshot` to download even if there is local data. *Environment: `$SYNC_MODE`* * `--snapshots-urls ` Base URLs for snapshots index, comma-separated. *Environment: `$SYNC_SNAPSHOTS_URLS`* * `--fisherman-mode` Whether to run in fisherman mode. *Environment: `$FISHERMAN_MODE`* * `--local-network` Starts Aztec Local Network * `--local-network.l1Mnemonic ` (default: `test test test test test test test test test test test junk`) Mnemonic for L1 accounts. Will be used *Environment: `$MNEMONIC`* * `--local-network.testAccounts` (default: `true`) Deploy test accounts on local network start *Environment: `$TEST_ACCOUNTS`* **API** * `--port ` (default: `8080`) Port to run the Aztec Services on *Environment: `$AZTEC_PORT`* * `--admin-port ` (default: `8880`) Port to run admin APIs of Aztec Services on *Environment: `$AZTEC_ADMIN_PORT`* * `--admin-api-key-hash ` SHA-256 hex hash of a pre-generated admin API key. When set, the node uses this hash for authentication instead of auto-generating a key. *Environment: `$AZTEC_ADMIN_API_KEY_HASH`* * `--disable-admin-api-key` Disable API key authentication on the admin RPC endpoint. By default, a key is auto-generated, displayed once, and its hash is persisted. *Environment: `$AZTEC_DISABLE_ADMIN_API_KEY`* * `--reset-admin-api-key` Force-generate a new admin API key, replacing any previously persisted key hash. The new key is displayed once at startup. *Environment: `$AZTEC_RESET_ADMIN_API_KEY`* * `--node-debug` Expose debug endpoints (e.g. mineBlock) on the main RPC port *Environment: `$AZTEC_NODE_DEBUG`* * `--api-prefix ` Prefix for API routes on any service that is started *Environment: `$API_PREFIX`* * `--rpcMaxBatchSize ` (default: `100`) Maximum allowed batch size for JSON RPC batch requests. *Environment: `$RPC_MAX_BATCH_SIZE`* * `--rpcMaxBodySize ` (default: `1mb`) Maximum allowed batch size for JSON RPC batch requests. *Environment: `$RPC_MAX_BODY_SIZE`* **ETHEREUM** * `--l1-chain-id ` The chain ID of the ethereum host. *Environment: `$L1_CHAIN_ID`* * `--l1-rpc-urls ` List of URLs of Ethereum RPC nodes that services will connect to (comma separated). *Environment: `$ETHEREUM_HOSTS`* * `--l1-consensus-host-urls ` List of URLs of the Ethereum consensus nodes that services will connect to (comma separated) *Environment: `$L1_CONSENSUS_HOST_URLS`* * `--l1-consensus-host-api-keys ` List of API keys for the corresponding L1 consensus clients, if needed. Added to the end of the corresponding URL as "?key=\" unless a header is defined *Environment: `$L1_CONSENSUS_HOST_API_KEYS`* * `--l1-consensus-host-api-key-headers ` List of header names for the corresponding L1 consensus client API keys, if needed. Added to the corresponding request as "\: \" *Environment: `$L1_CONSENSUS_HOST_API_KEY_HEADERS`* * `--registry-address ` The deployed L1 registry contract address. *Environment: `$REGISTRY_CONTRACT_ADDRESS`* * `--rollup-version ` The version of the rollup. *Environment: `$ROLLUP_VERSION`* **STORAGE** * `--data-directory ` Optional dir to store data. If omitted will store in memory. *Environment: `$DATA_DIRECTORY`* * `--data-store-map-size-kb ` (default: `134217728`) The maximum possible size of a data store DB in KB. Can be overridden by component-specific options. *Environment: `$DATA_STORE_MAP_SIZE_KB`* **WORLD STATE** * `--world-state-data-directory ` Optional directory for the world state database *Environment: `$WS_DATA_DIRECTORY`* * `--world-state-db-map-size-kb ` The maximum possible size of the world state DB in KB. Overwrites the general dataStoreMapSizeKb. *Environment: `$WS_DB_MAP_SIZE_KB`* * `--world-state-checkpoint-history ` (default: `64`) The number of historic checkpoints worth of blocks to maintain. Values less than 1 mean all history is maintained *Environment: `$WS_NUM_HISTORIC_CHECKPOINTS`* **AZTEC NODE** * `--node` Starts Aztec Node with options **ARCHIVER** * `--archiver` Starts Aztec Archiver with options * `--archiver.blobSinkMapSizeKb ` The maximum possible size of the blob sink DB in KB. Overwrites the general dataStoreMapSizeKb. *Environment: `$BLOB_SINK_MAP_SIZE_KB`* * `--archiver.blobAllowEmptySources ` Whether to allow having no blob sources configured during startup *Environment: `$BLOB_ALLOW_EMPTY_SOURCES`* * `--archiver.blobFileStoreUrls ` URLs for filestore blob archive, comma-separated. Tried in order until blobs are found. *Environment: `$BLOB_FILE_STORE_URLS`* * `--archiver.blobFileStoreUploadUrl ` URL for uploading blobs to filestore (s3://, gs\://, file://) *Environment: `$BLOB_FILE_STORE_UPLOAD_URL`* * `--archiver.blobHealthcheckUploadIntervalMinutes ` Interval in minutes for uploading healthcheck file to file store (default: 60 = 1 hour) *Environment: `$BLOB_HEALTHCHECK_UPLOAD_INTERVAL_MINUTES`* * `--archiver.archiveApiUrl ` The URL of the archive API *Environment: `$BLOB_ARCHIVE_API_URL`* * `--archiver.archiverPollingIntervalMS ` (default: `500`) The polling interval in ms for retrieving new L2 blocks and encrypted logs. *Environment: `$ARCHIVER_POLLING_INTERVAL_MS`* * `--archiver.archiverBatchSize ` (default: `100`) The number of L2 blocks the archiver will attempt to download at a time. *Environment: `$ARCHIVER_BATCH_SIZE`* * `--archiver.maxLogs ` (default: `1000`) The max number of logs that can be obtained in 1 "getPublicLogs" call. *Environment: `$ARCHIVER_MAX_LOGS`* * `--archiver.archiverStoreMapSizeKb ` The maximum possible size of the archiver DB in KB. Overwrites the general dataStoreMapSizeKb. *Environment: `$ARCHIVER_STORE_MAP_SIZE_KB`* * `--archiver.skipValidateCheckpointAttestations ` Skip validating checkpoint attestations (for testing purposes only) * `--archiver.maxAllowedEthClientDriftSeconds ` (default: `300`) Maximum allowed drift in seconds between the Ethereum client and current time. *Environment: `$MAX_ALLOWED_ETH_CLIENT_DRIFT_SECONDS`* * `--archiver.ethereumAllowNoDebugHosts ` (default: `true`) Whether to allow starting the archiver without debug/trace method support on Ethereum hosts *Environment: `$ETHEREUM_ALLOW_NO_DEBUG_HOSTS`* **SEQUENCER** * `--sequencer` Starts Aztec Sequencer with options * `--sequencer.validatorPrivateKeys ` (default: `[Redacted]`) List of private keys of the validators participating in attestation duties *Environment: `$VALIDATOR_PRIVATE_KEYS`* * `--sequencer.validatorAddresses ` List of addresses of the validators to use with remote signers *Environment: `$VALIDATOR_ADDRESSES`* * `--sequencer.disableValidator ` Do not run the validator *Environment: `$VALIDATOR_DISABLED`* * `--sequencer.disabledValidators ` Temporarily disable these specific validator addresses * `--sequencer.attestationPollingIntervalMs ` (default: `200`) Interval between polling for new attestations *Environment: `$VALIDATOR_ATTESTATIONS_POLLING_INTERVAL_MS`* * `--sequencer.validatorReexecute ` (default: `true`) Re-execute transactions before attesting *Environment: `$VALIDATOR_REEXECUTE`* * `--sequencer.alwaysReexecuteBlockProposals ` (default: `true`) Whether to always reexecute block proposals, even for non-validator nodes (useful for monitoring network status). * `--sequencer.skipCheckpointProposalValidation ` Skip checkpoint proposal validation and always attest (default: false) * `--sequencer.skipPushProposedBlocksToArchiver ` Skip pushing proposed blocks to archiver (default: true) * `--sequencer.attestToEquivocatedProposals ` Agree to attest to equivocated checkpoint proposals (for testing purposes only) * `--sequencer.validateMaxL2BlockGas ` Maximum L2 block gas for validation. Proposals exceeding this limit are rejected. *Environment: `$VALIDATOR_MAX_L2_BLOCK_GAS`* * `--sequencer.validateMaxDABlockGas ` Maximum DA block gas for validation. Proposals exceeding this limit are rejected. *Environment: `$VALIDATOR_MAX_DA_BLOCK_GAS`* * `--sequencer.validateMaxTxsPerBlock ` Maximum transactions per block for validation. Proposals exceeding this limit are rejected. *Environment: `$VALIDATOR_MAX_TX_PER_BLOCK`* * `--sequencer.validateMaxTxsPerCheckpoint ` Maximum transactions per checkpoint for validation. Proposals exceeding this limit are rejected. *Environment: `$VALIDATOR_MAX_TX_PER_CHECKPOINT`* * `--sequencer.haSigningEnabled ` Whether HA signing / slashing protection is enabled *Environment: `$VALIDATOR_HA_SIGNING_ENABLED`* * `--sequencer.nodeId ` The unique identifier for this node *Environment: `$VALIDATOR_HA_NODE_ID`* * `--sequencer.pollingIntervalMs ` (default: `100`) The number of ms to wait between polls when a duty is being signed *Environment: `$VALIDATOR_HA_POLLING_INTERVAL_MS`* * `--sequencer.signingTimeoutMs ` (default: `3000`) The maximum time to wait for a duty being signed to complete *Environment: `$VALIDATOR_HA_SIGNING_TIMEOUT_MS`* * `--sequencer.maxStuckDutiesAgeMs ` The maximum age of a stuck duty in ms (defaults to 2x Aztec slot duration) *Environment: `$VALIDATOR_HA_MAX_STUCK_DUTIES_AGE_MS`* * `--sequencer.cleanupOldDutiesAfterHours ` Optional: clean up old duties after this many hours (disabled if not set) *Environment: `$VALIDATOR_HA_OLD_DUTIES_MAX_AGE_H`* * `--sequencer.databaseUrl ` PostgreSQL connection string for validator HA signer (format: postgresql://user:password@host:port/database) *Environment: `$VALIDATOR_HA_DATABASE_URL`* * `--sequencer.poolMaxCount ` (default: `10`) Maximum number of clients in the pool *Environment: `$VALIDATOR_HA_POOL_MAX`* * `--sequencer.poolMinCount ` Minimum number of clients in the pool *Environment: `$VALIDATOR_HA_POOL_MIN`* * `--sequencer.poolIdleTimeoutMs ` (default: `10000`) Idle timeout in milliseconds *Environment: `$VALIDATOR_HA_POOL_IDLE_TIMEOUT_MS`* * `--sequencer.poolConnectionTimeoutMs ` Connection timeout in milliseconds (0 means no timeout) *Environment: `$VALIDATOR_HA_POOL_CONNECTION_TIMEOUT_MS`* * `--sequencer.sequencerPollingIntervalMS ` (default: `500`) The number of ms to wait between polling for checking to build on the next slot. *Environment: `$SEQ_POLLING_INTERVAL_MS`* * `--sequencer.maxTxsPerCheckpoint ` The maximum number of txs across all blocks in a checkpoint. *Environment: `$SEQ_MAX_TX_PER_CHECKPOINT`* * `--sequencer.minTxsPerBlock ` (default: `1`) The minimum number of txs to include in a block. *Environment: `$SEQ_MIN_TX_PER_BLOCK`* * `--sequencer.minValidTxsPerBlock ` The minimum number of valid txs (after execution) to include in a block. If not set, falls back to minTxsPerBlock. * `--sequencer.publishTxsWithProposals ` Whether to publish txs with proposals. *Environment: `$SEQ_PUBLISH_TXS_WITH_PROPOSALS`* * `--sequencer.maxL2BlockGas ` The maximum L2 block gas. *Environment: `$SEQ_MAX_L2_BLOCK_GAS`* * `--sequencer.maxDABlockGas ` The maximum DA block gas. *Environment: `$SEQ_MAX_DA_BLOCK_GAS`* * `--sequencer.perBlockAllocationMultiplier ` (default: `1.2`) Per-block gas budget multiplier for both L2 and DA gas. Budget per block is (checkpointLimit / maxBlocks) \* multiplier. Values greater than one allow early blocks to use more than their even share, relying on checkpoint-level capping for later blocks. *Environment: `$SEQ_PER_BLOCK_ALLOCATION_MULTIPLIER`* * `--sequencer.redistributeCheckpointBudget ` (default: `true`) Redistribute remaining checkpoint budget evenly across remaining blocks instead of allowing a single block to consume the entire remaining budget. *Environment: `$SEQ_REDISTRIBUTE_CHECKPOINT_BUDGET`* * `--sequencer.coinbase ` Recipient of block reward. *Environment: `$COINBASE`* * `--sequencer.feeRecipient ` Address to receive fees. *Environment: `$FEE_RECIPIENT`* * `--sequencer.acvmWorkingDirectory ` The working directory to use for simulation/proving *Environment: `$ACVM_WORKING_DIRECTORY`* * `--sequencer.acvmBinaryPath ` The path to the ACVM binary *Environment: `$ACVM_BINARY_PATH`* * `--sequencer.enforceTimeTable ` (default: `true`) Whether to enforce the time table when building blocks *Environment: `$SEQ_ENFORCE_TIME_TABLE`* * `--sequencer.governanceProposerPayload ` The address of the payload for the governanceProposer *Environment: `$GOVERNANCE_PROPOSER_PAYLOAD_ADDRESS`* * `--sequencer.l1PublishingTime ` How much time (in seconds) we allow in the slot for publishing the L1 tx (defaults to 1 L1 slot). *Environment: `$SEQ_L1_PUBLISHING_TIME_ALLOWANCE_IN_SLOT`* * `--sequencer.attestationPropagationTime ` (default: `2`) How many seconds it takes for proposals and attestations to travel across the p2p layer (one-way) *Environment: `$SEQ_ATTESTATION_PROPAGATION_TIME`* * `--sequencer.secondsBeforeInvalidatingBlockAsCommitteeMember ` (default: `144`) How many seconds to wait before trying to invalidate a block from the pending chain as a committee member (zero to never invalidate). The next proposer is expected to invalidate, so the committee acts as a fallback. *Environment: `$SEQ_SECONDS_BEFORE_INVALIDATING_BLOCK_AS_COMMITTEE_MEMBER`* * `--sequencer.secondsBeforeInvalidatingBlockAsNonCommitteeMember ` (default: `432`) How many seconds to wait before trying to invalidate a block from the pending chain as a non-committee member (zero to never invalidate). The next proposer is expected to invalidate, then the committee, so other sequencers act as a fallback. *Environment: `$SEQ_SECONDS_BEFORE_INVALIDATING_BLOCK_AS_NON_COMMITTEE_MEMBER`* * `--sequencer.broadcastInvalidBlockProposal ` Broadcast invalid block proposals with corrupted state (for testing only) * `--sequencer.injectFakeAttestation ` Inject a fake attestation (for testing only) * `--sequencer.injectHighSValueAttestation ` Inject a malleable attestation with a high-s value (for testing only) * `--sequencer.injectUnrecoverableSignatureAttestation ` Inject an attestation with an unrecoverable signature (for testing only) * `--sequencer.shuffleAttestationOrdering ` Shuffle attestation ordering to create invalid ordering (for testing only) * `--sequencer.blockDurationMs ` Duration per block in milliseconds when building multiple blocks per slot. If undefined (default), builds a single block per slot using the full slot duration. *Environment: `$SEQ_BLOCK_DURATION_MS`* * `--sequencer.expectedBlockProposalsPerSlot ` Expected number of block proposals per slot for P2P peer scoring. 0 (default) disables block proposal scoring. Set to a positive value to enable. *Environment: `$SEQ_EXPECTED_BLOCK_PROPOSALS_PER_SLOT`* * `--sequencer.maxTxsPerBlock ` The maximum number of txs to include in a block. *Environment: `$SEQ_MAX_TX_PER_BLOCK`* * `--sequencer.buildCheckpointIfEmpty ` Have sequencer build and publish an empty checkpoint if there are no txs *Environment: `$SEQ_BUILD_CHECKPOINT_IF_EMPTY`* * `--sequencer.minBlocksForCheckpoint ` Minimum number of blocks required for a checkpoint proposal (test only) * `--sequencer.skipPublishingCheckpointsPercent ` Percent probability (0 - 100) of sequencer skipping checkpoint publishing (testing only) *Environment: `$SEQ_SKIP_CHECKPOINT_PUBLISH_PERCENT`* * `--sequencer.txPublicSetupAllowListExtend ` Additional entries to extend the default setup allow list. Format: `I:address:selector[:flags],C:classId:selector[:flags]`. Flags: os (onlySelf), rn (rejectNullMsgSender), cl=N (calldataLength), joined with +. *Environment: `$TX_PUBLIC_SETUP_ALLOWLIST`* * `--sequencer.keyStoreDirectory ` Location of key store directory *Environment: `$KEY_STORE_DIRECTORY`* * `--sequencer.sequencerPublisherPrivateKeys ` The private keys to be used by the sequencer publisher. *Environment: `$SEQ_PUBLISHER_PRIVATE_KEYS`* * `--sequencer.sequencerPublisherAddresses ` The addresses of the publishers to use with remote signers *Environment: `$SEQ_PUBLISHER_ADDRESSES`* * `--sequencer.blobAllowEmptySources ` Whether to allow having no blob sources configured during startup *Environment: `$BLOB_ALLOW_EMPTY_SOURCES`* * `--sequencer.blobFileStoreUrls ` URLs for filestore blob archive, comma-separated. Tried in order until blobs are found. *Environment: `$BLOB_FILE_STORE_URLS`* * `--sequencer.blobFileStoreUploadUrl ` URL for uploading blobs to filestore (s3://, gs\://, file://) *Environment: `$BLOB_FILE_STORE_UPLOAD_URL`* * `--sequencer.blobHealthcheckUploadIntervalMinutes ` Interval in minutes for uploading healthcheck file to file store (default: 60 = 1 hour) *Environment: `$BLOB_HEALTHCHECK_UPLOAD_INTERVAL_MINUTES`* * `--sequencer.archiveApiUrl ` The URL of the archive API *Environment: `$BLOB_ARCHIVE_API_URL`* * `--sequencer.sequencerPublisherAllowInvalidStates ` (default: `true`) True to use publishers in invalid states (timed out, cancelled, etc) if no other is available *Environment: `$SEQ_PUBLISHER_ALLOW_INVALID_STATES`* * `--sequencer.sequencerPublisherForwarderAddress ` Address of the forwarder contract to wrap all L1 transactions through (for testing purposes only) *Environment: `$SEQ_PUBLISHER_FORWARDER_ADDRESS`* **PROVER NODE** * `--prover-node` Starts Aztec Prover Node with options * `--proverNode.keyStoreDirectory ` Location of key store directory *Environment: `$KEY_STORE_DIRECTORY`* * `--proverNode.acvmWorkingDirectory ` The working directory to use for simulation/proving *Environment: `$ACVM_WORKING_DIRECTORY`* * `--proverNode.acvmBinaryPath ` The path to the ACVM binary *Environment: `$ACVM_BINARY_PATH`* * `--proverNode.bbWorkingDirectory ` The working directory to use for proving *Environment: `$BB_WORKING_DIRECTORY`* * `--proverNode.bbBinaryPath ` The path to the bb binary *Environment: `$BB_BINARY_PATH`* * `--proverNode.bbSkipCleanup ` Whether to skip cleanup of bb temporary files *Environment: `$BB_SKIP_CLEANUP`* * `--proverNode.numConcurrentIVCVerifiers ` (default: `8`) Max number of chonk verifiers to run concurrently *Environment: `$BB_NUM_IVC_VERIFIERS`* * `--proverNode.bbIVCConcurrency ` (default: `1`) Number of threads to use for IVC verification *Environment: `$BB_IVC_CONCURRENCY`* * `--proverNode.nodeUrl ` The URL to the Aztec node to take proving jobs from *Environment: `$AZTEC_NODE_URL`* * `--proverNode.proverId ` Hex value that identifies the prover. Defaults to the address used for submitting proofs if not set. *Environment: `$PROVER_ID`* * `--proverNode.failedProofStore ` Store for failed proof inputs. Google cloud storage is only supported at the moment. Set this value as gs\://bucket-name/path/to/store. *Environment: `$PROVER_FAILED_PROOF_STORE`* * `--proverNode.enqueueConcurrency ` (default: `50`) Max concurrent jobs the orchestrator serializes and enqueues to the broker. *Environment: `$PROVER_ENQUEUE_CONCURRENCY`* * `--proverNode.blobSinkMapSizeKb ` The maximum possible size of the blob sink DB in KB. Overwrites the general dataStoreMapSizeKb. *Environment: `$BLOB_SINK_MAP_SIZE_KB`* * `--proverNode.blobAllowEmptySources ` Whether to allow having no blob sources configured during startup *Environment: `$BLOB_ALLOW_EMPTY_SOURCES`* * `--proverNode.blobFileStoreUrls ` URLs for filestore blob archive, comma-separated. Tried in order until blobs are found. *Environment: `$BLOB_FILE_STORE_URLS`* * `--proverNode.blobFileStoreUploadUrl ` URL for uploading blobs to filestore (s3://, gs\://, file://) *Environment: `$BLOB_FILE_STORE_UPLOAD_URL`* * `--proverNode.blobHealthcheckUploadIntervalMinutes ` Interval in minutes for uploading healthcheck file to file store (default: 60 = 1 hour) *Environment: `$BLOB_HEALTHCHECK_UPLOAD_INTERVAL_MINUTES`* * `--proverNode.archiveApiUrl ` The URL of the archive API *Environment: `$BLOB_ARCHIVE_API_URL`* * `--proverNode.proverPublisherAllowInvalidStates ` (default: `true`) True to use publishers in invalid states (timed out, cancelled, etc) if no other is available *Environment: `$PROVER_PUBLISHER_ALLOW_INVALID_STATES`* * `--proverNode.proverPublisherForwarderAddress ` Address of the forwarder contract to wrap all L1 transactions through (for testing purposes only) *Environment: `$PROVER_PUBLISHER_FORWARDER_ADDRESS`* * `--proverNode.proverPublisherPrivateKeys ` The private keys to be used by the prover publisher. *Environment: `$PROVER_PUBLISHER_PRIVATE_KEYS`* * `--proverNode.proverPublisherAddresses ` The addresses of the publishers to use with remote signers *Environment: `$PROVER_PUBLISHER_ADDRESSES`* * `--proverNode.proverNodeMaxPendingJobs ` (default: `10`) The maximum number of pending jobs for the prover node *Environment: `$PROVER_NODE_MAX_PENDING_JOBS`* * `--proverNode.proverNodePollingIntervalMs ` (default: `1000`) The interval in milliseconds to poll for new jobs *Environment: `$PROVER_NODE_POLLING_INTERVAL_MS`* * `--proverNode.proverNodeMaxParallelBlocksPerEpoch ` The Maximum number of blocks to process in parallel while proving an epoch *Environment: `$PROVER_NODE_MAX_PARALLEL_BLOCKS_PER_EPOCH`* * `--proverNode.proverNodeFailedEpochStore ` File store where to upload node state when an epoch fails to be proven *Environment: `$PROVER_NODE_FAILED_EPOCH_STORE`* * `--proverNode.proverNodeEpochProvingDelayMs ` Optional delay in milliseconds to wait before proving a new epoch * `--proverNode.txGatheringIntervalMs ` (default: `1000`) How often to check that tx data is available *Environment: `$PROVER_NODE_TX_GATHERING_INTERVAL_MS`* * `--proverNode.txGatheringBatchSize ` (default: `10`) How many transactions to gather from a node in a single request *Environment: `$PROVER_NODE_TX_GATHERING_BATCH_SIZE`* * `--proverNode.txGatheringMaxParallelRequestsPerNode ` (default: `100`) How many tx requests to make in parallel to each node *Environment: `$PROVER_NODE_TX_GATHERING_MAX_PARALLEL_REQUESTS_PER_NODE`* * `--proverNode.txGatheringTimeoutMs ` (default: `120000`) How long to wait for tx data to be available before giving up *Environment: `$PROVER_NODE_TX_GATHERING_TIMEOUT_MS`* * `--proverNode.proverNodeDisableProofPublish ` Whether the prover node skips publishing proofs to L1 *Environment: `$PROVER_NODE_DISABLE_PROOF_PUBLISH`* * `--proverNode.web3SignerUrl ` URL of the Web3Signer instance *Environment: `$WEB3_SIGNER_URL`* **PROVER BROKER** * `--prover-broker` Starts Aztec proving job broker * `--proverBroker.proverBrokerJobTimeoutMs ` (default: `30000`) Jobs are retried if not kept alive for this long *Environment: `$PROVER_BROKER_JOB_TIMEOUT_MS`* * `--proverBroker.proverBrokerPollIntervalMs ` (default: `1000`) The interval to check job health status *Environment: `$PROVER_BROKER_POLL_INTERVAL_MS`* * `--proverBroker.proverBrokerJobMaxRetries ` (default: `3`) If starting a prover broker locally, the max number of retries per proving job *Environment: `$PROVER_BROKER_JOB_MAX_RETRIES`* * `--proverBroker.proverBrokerBatchSize ` (default: `100`) The prover broker writes jobs to disk in batches *Environment: `$PROVER_BROKER_BATCH_SIZE`* * `--proverBroker.proverBrokerBatchIntervalMs ` (default: `50`) How often to flush batches to disk *Environment: `$PROVER_BROKER_BATCH_INTERVAL_MS`* * `--proverBroker.proverBrokerMaxEpochsToKeepResultsFor ` (default: `1`) The maximum number of epochs to keep results for *Environment: `$PROVER_BROKER_MAX_EPOCHS_TO_KEEP_RESULTS_FOR`* * `--proverBroker.proverBrokerStoreMapSizeKb ` The size of the prover broker's database. Will override the dataStoreMapSizeKb if set. *Environment: `$PROVER_BROKER_STORE_MAP_SIZE_KB`* * `--proverBroker.proverBrokerDebugReplayEnabled ` Enable debug replay mode for replaying proving jobs from stored inputs *Environment: `$PROVER_BROKER_DEBUG_REPLAY_ENABLED`* **PROVER AGENT** * `--prover-agent` Starts Aztec Prover Agent with options * `--proverAgent.proverAgentCount ` (default: `1`) Whether this prover has a local prover agent *Environment: `$PROVER_AGENT_COUNT`* * `--proverAgent.proverAgentPollIntervalMs ` (default: `1000`) The interval agents poll for jobs at *Environment: `$PROVER_AGENT_POLL_INTERVAL_MS`* * `--proverAgent.proverAgentProofTypes ` The types of proofs the prover agent can generate *Environment: `$PROVER_AGENT_PROOF_TYPES`* * `--proverAgent.proverBrokerUrl ` The URL where this agent takes jobs from *Environment: `$PROVER_BROKER_HOST`* * `--proverAgent.realProofs ` (default: `true`) Whether to construct real proofs *Environment: `$PROVER_REAL_PROOFS`* * `--proverAgent.proverTestDelayType ` (default: `fixed`) The type of artificial delay to introduce *Environment: `$PROVER_TEST_DELAY_TYPE`* * `--proverAgent.proverTestDelayMs ` Artificial delay to introduce to all operations to the test prover. *Environment: `$PROVER_TEST_DELAY_MS`* * `--proverAgent.proverTestDelayFactor ` (default: `1`) If using realistic delays, what percentage of realistic times to apply. *Environment: `$PROVER_TEST_DELAY_FACTOR`* * `--proverAgent.proverTestVerificationDelayMs ` (default: `10`) The delay (ms) to inject during fake proof verification *Environment: `$PROVER_TEST_VERIFICATION_DELAY_MS`* * `--proverAgent.cancelJobsOnStop ` Whether to abort pending proving jobs when the orchestrator is cancelled. When false (default), jobs remain in the broker queue and can be reused on restart/reorg. *Environment: `$PROVER_CANCEL_JOBS_ON_STOP`* * `--proverAgent.proofStore ` Optional proof input store for the prover *Environment: `$PROVER_PROOF_STORE`* **P2P SUBSYSTEM** * `--p2p-enabled [value]` Enable P2P subsystem *Environment: `$P2P_ENABLED`* * `--p2p.validateMaxTxsPerBlock ` Maximum transactions per block for validation. Overrides maxTxsPerBlock for gossip validation when set. *Environment: `$VALIDATOR_MAX_TX_PER_BLOCK`* * `--p2p.validateMaxTxsPerCheckpoint ` Maximum transactions per checkpoint for validation. Used as fallback for maxTxsPerBlock when that is not set. *Environment: `$VALIDATOR_MAX_TX_PER_CHECKPOINT`* * `--p2p.validateMaxL2BlockGas ` Maximum L2 gas per block for validation. When set, txs exceeding this limit are rejected. *Environment: `$VALIDATOR_MAX_L2_BLOCK_GAS`* * `--p2p.validateMaxDABlockGas ` Maximum DA gas per block for validation. When set, txs exceeding this limit are rejected. *Environment: `$VALIDATOR_MAX_DA_BLOCK_GAS`* * `--p2p.p2pDiscoveryDisabled ` A flag dictating whether the P2P discovery system should be disabled. *Environment: `$P2P_DISCOVERY_DISABLED`* * `--p2p.blockCheckIntervalMS ` (default: `100`) The frequency in which to check for new L2 blocks. *Environment: `$P2P_BLOCK_CHECK_INTERVAL_MS`* * `--p2p.slotCheckIntervalMS ` (default: `1000`) The frequency in which to check for new L2 slots. *Environment: `$P2P_SLOT_CHECK_INTERVAL_MS`* * `--p2p.debugDisableColocationPenalty ` DEBUG: Disable colocation penalty - NEVER set to true in production *Environment: `$DEBUG_P2P_DISABLE_COLOCATION_PENALTY`* * `--p2p.peerCheckIntervalMS ` (default: `30000`) The frequency in which to check for new peers. *Environment: `$P2P_PEER_CHECK_INTERVAL_MS`* * `--p2p.l2QueueSize ` (default: `1000`) Size of queue of L2 blocks to store. *Environment: `$P2P_L2_QUEUE_SIZE`* * `--p2p.listenAddress ` (default: `0.0.0.0`) The listen address. ipv4 address. *Environment: `$P2P_LISTEN_ADDR`* * `--p2p.p2pPort ` (default: `40400`) The port for the P2P service. Defaults to 40400 *Environment: `$P2P_PORT`* * `--p2p.p2pBroadcastPort ` The port to broadcast the P2P service on (included in the node's ENR). Defaults to P2P\_PORT. *Environment: `$P2P_BROADCAST_PORT`* * `--p2p.p2pIp ` The IP address for the P2P service. ipv4 address. *Environment: `$P2P_IP`* * `--p2p.peerIdPrivateKey ` An optional peer id private key. If blank, will generate a random key. *Environment: `$PEER_ID_PRIVATE_KEY`* * `--p2p.peerIdPrivateKeyPath ` An optional path to store generated peer id private keys. If blank, will default to storing any generated keys in the root of the data directory. *Environment: `$PEER_ID_PRIVATE_KEY_PATH`* * `--p2p.bootstrapNodes ` A list of bootstrap peer ENRs to connect to. Separated by commas. *Environment: `$BOOTSTRAP_NODES`* * `--p2p.bootstrapNodeEnrVersionCheck ` Whether to check the version of the bootstrap node ENR. *Environment: `$P2P_BOOTSTRAP_NODE_ENR_VERSION_CHECK`* * `--p2p.bootstrapNodesAsFullPeers ` Whether to consider our configured bootnodes as full peers *Environment: `$P2P_BOOTSTRAP_NODES_AS_FULL_PEERS`* * `--p2p.maxPeerCount ` (default: `100`) The maximum number of peers to connect to. *Environment: `$P2P_MAX_PEERS`* * `--p2p.queryForIp ` If announceUdpAddress or announceTcpAddress are not provided, query for the IP address of the machine. Default is false. *Environment: `$P2P_QUERY_FOR_IP`* * `--p2p.gossipsubInterval ` (default: `700`) The interval of the gossipsub heartbeat to perform maintenance tasks. *Environment: `$P2P_GOSSIPSUB_INTERVAL_MS`* * `--p2p.gossipsubD ` (default: `8`) The D parameter for the gossipsub protocol. *Environment: `$P2P_GOSSIPSUB_D`* * `--p2p.gossipsubDlo ` (default: `4`) The Dlo parameter for the gossipsub protocol. *Environment: `$P2P_GOSSIPSUB_DLO`* * `--p2p.gossipsubDhi ` (default: `12`) The Dhi parameter for the gossipsub protocol. *Environment: `$P2P_GOSSIPSUB_DHI`* * `--p2p.gossipsubDLazy ` (default: `8`) The Dlazy parameter for the gossipsub protocol. *Environment: `$P2P_GOSSIPSUB_DLAZY`* * `--p2p.gossipsubFloodPublish ` Whether to flood publish messages. - For testing purposes only *Environment: `$P2P_GOSSIPSUB_FLOOD_PUBLISH`* * `--p2p.gossipsubMcacheLength ` (default: `6`) The number of gossipsub interval message cache windows to keep. *Environment: `$P2P_GOSSIPSUB_MCACHE_LENGTH`* * `--p2p.gossipsubMcacheGossip ` (default: `3`) How many message cache windows to include when gossiping with other peers. *Environment: `$P2P_GOSSIPSUB_MCACHE_GOSSIP`* * `--p2p.gossipsubSeenTTL ` (default: `1200000`) How long to keep message IDs in the seen cache. *Environment: `$P2P_GOSSIPSUB_SEEN_TTL`* * `--p2p.gossipsubTxTopicWeight ` (default: `1`) The weight of the tx topic for the gossipsub protocol. *Environment: `$P2P_GOSSIPSUB_TX_TOPIC_WEIGHT`* * `--p2p.gossipsubTxInvalidMessageDeliveriesWeight ` (default: `-20`) The weight of the tx invalid message deliveries for the gossipsub protocol. *Environment: `$P2P_GOSSIPSUB_TX_INVALID_MESSAGE_DELIVERIES_WEIGHT`* * `--p2p.gossipsubTxInvalidMessageDeliveriesDecay ` (default: `0.5`) Determines how quickly the penalty for invalid message deliveries decays over time. Between 0 and 1. *Environment: `$P2P_GOSSIPSUB_TX_INVALID_MESSAGE_DELIVERIES_DECAY`* * `--p2p.peerPenaltyValues ` (default: `2,10,50`) The values for the peer scoring system. Passed as a comma separated list of values in order: low, mid, high tolerance errors. *Environment: `$P2P_PEER_PENALTY_VALUES`* * `--p2p.doubleSpendSeverePeerPenaltyWindow ` (default: `30`) The "age" (in L2 blocks) of a tx after which we heavily penalize a peer for sending it. *Environment: `$P2P_DOUBLE_SPEND_SEVERE_PEER_PENALTY_WINDOW`* * `--p2p.blockRequestBatchSize ` (default: `20`) The number of blocks to fetch in a single batch. *Environment: `$P2P_BLOCK_REQUEST_BATCH_SIZE`* * `--p2p.archivedTxLimit ` The number of transactions that will be archived. If the limit is set to 0 then archiving will be disabled. *Environment: `$P2P_ARCHIVED_TX_LIMIT`* * `--p2p.trustedPeers ` A list of trusted peer ENRs that will always be persisted. Separated by commas. *Environment: `$P2P_TRUSTED_PEERS`* * `--p2p.privatePeers ` A list of private peer ENRs that will always be persisted and not be used for discovery. Separated by commas. *Environment: `$P2P_PRIVATE_PEERS`* * `--p2p.preferredPeers ` A list of preferred peer ENRs that will always be persisted and not be used for discovery. Separated by commas. *Environment: `$P2P_PREFERRED_PEERS`* * `--p2p.p2pStoreMapSizeKb ` The maximum possible size of the P2P DB in KB. Overwrites the general dataStoreMapSizeKb. *Environment: `$P2P_STORE_MAP_SIZE_KB`* * `--p2p.txPublicSetupAllowListExtend ` Additional entries to extend the default setup allow list. Format: `I:address:selector[:flags],C:classId:selector[:flags]`. Flags: os (onlySelf), rn (rejectNullMsgSender), cl=N (calldataLength), joined with +. *Environment: `$TX_PUBLIC_SETUP_ALLOWLIST`* * `--p2p.maxPendingTxCount ` (default: `1000`) The maximum number of pending txs before evicting lower priority txs. *Environment: `$P2P_MAX_PENDING_TX_COUNT`* * `--p2p.seenMessageCacheSize ` (default: `100000`) The number of messages to keep in the seen message cache *Environment: `$P2P_SEEN_MSG_CACHE_SIZE`* * `--p2p.p2pDisableStatusHandshake ` True to disable the status handshake on peer connected. *Environment: `$P2P_DISABLE_STATUS_HANDSHAKE`* * `--p2p.p2pAllowOnlyValidators ` True to only permit validators to connect. *Environment: `$P2P_ALLOW_ONLY_VALIDATORS`* * `--p2p.p2pMaxFailedAuthAttemptsAllowed ` (default: `3`) Number of auth attempts to allow before peer is banned. Number is inclusive *Environment: `$P2P_MAX_AUTH_FAILED_ATTEMPTS_ALLOWED`* * `--p2p.dropTransactions ` True to simulate discarding transactions. - For testing purposes only *Environment: `$P2P_DROP_TX`* * `--p2p.dropTransactionsProbability ` The probability that a transaction is discarded (0 - 1). - For testing purposes only *Environment: `$P2P_DROP_TX_CHANCE`* * `--p2p.disableTransactions ` Whether transactions are disabled for this node. This means transactions will be rejected at the RPC and P2P layers. *Environment: `$TRANSACTIONS_DISABLED`* * `--p2p.txPoolDeleteTxsAfterReorg ` Whether to delete transactions from the pool after a reorg instead of moving them back to pending. *Environment: `$P2P_TX_POOL_DELETE_TXS_AFTER_REORG`* * `--p2p.debugP2PInstrumentMessages ` Alters the format of p2p messages to include things like broadcast timestamp FOR TESTING ONLY *Environment: `$DEBUG_P2P_INSTRUMENT_MESSAGES`* * `--p2p.broadcastEquivocatedProposals ` Broadcast block proposals even when a conflicting proposal for the same slot already exists in the pool (for testing purposes only). * `--p2p.minTxPoolAgeMs ` (default: `2000`) Minimum age (ms) a transaction must have been in the pool before it is eligible for block building. *Environment: `$P2P_MIN_TX_POOL_AGE_MS`* * `--p2p.priceBumpPercentage ` (default: `10`) Minimum percentage fee increase required to replace an existing tx via RPC. Even at 0%, replacement still requires paying at least 1 unit more. *Environment: `$P2P_RPC_PRICE_BUMP_PERCENTAGE`* * `--p2p.blockDurationMs ` Duration per block in milliseconds when building multiple blocks per slot. If undefined (default), builds a single block per slot using the full slot duration. *Environment: `$SEQ_BLOCK_DURATION_MS`* * `--p2p.expectedBlockProposalsPerSlot ` Expected number of block proposals per slot for P2P peer scoring. 0 (default) disables block proposal scoring. Set to a positive value to enable. *Environment: `$SEQ_EXPECTED_BLOCK_PROPOSALS_PER_SLOT`* * `--p2p.maxTxsPerBlock ` The maximum number of txs to include in a block. *Environment: `$SEQ_MAX_TX_PER_BLOCK`* * `--p2p.overallRequestTimeoutMs ` (default: `10000`) The overall timeout for a request response operation. *Environment: `$P2P_REQRESP_OVERALL_REQUEST_TIMEOUT_MS`* * `--p2p.individualRequestTimeoutMs ` (default: `10000`) The timeout for an individual request response peer interaction. *Environment: `$P2P_REQRESP_INDIVIDUAL_REQUEST_TIMEOUT_MS`* * `--p2p.dialTimeoutMs ` (default: `5000`) How long to wait for the dial protocol to establish a connection *Environment: `$P2P_REQRESP_DIAL_TIMEOUT_MS`* * `--p2p.p2pOptimisticNegotiation ` Whether to use optimistic protocol negotiation when dialing to another peer (opposite of `negotiateFully`). *Environment: `$P2P_REQRESP_OPTIMISTIC_NEGOTIATION`* * `--p2p.batchTxRequesterSmartParallelWorkerCount ` (default: `10`) Max concurrent requests to smart peers for batch tx requester. *Environment: `$P2P_BATCH_TX_REQUESTER_SMART_PARALLEL_WORKER_COUNT`* * `--p2p.batchTxRequesterDumbParallelWorkerCount ` (default: `10`) Max concurrent requests to dumb peers for batch tx requester. *Environment: `$P2P_BATCH_TX_REQUESTER_DUMB_PARALLEL_WORKER_COUNT`* * `--p2p.batchTxRequesterTxBatchSize ` (default: `8`) Max transactions per request / chunk size for batch tx requester. *Environment: `$P2P_BATCH_TX_REQUESTER_TX_BATCH_SIZE`* * `--p2p.batchTxRequesterBadPeerThreshold ` (default: `2`) Failures before a peer is considered bad (see > threshold logic). *Environment: `$P2P_BATCH_TX_REQUESTER_BAD_PEER_THRESHOLD`* * `--p2p.txCollectionFastNodesTimeoutBeforeReqRespMs ` (default: `200`) How long to wait before starting reqresp for fast collection *Environment: `$TX_COLLECTION_FAST_NODES_TIMEOUT_BEFORE_REQ_RESP_MS`* * `--p2p.txCollectionSlowNodesIntervalMs ` (default: `12000`) How often to collect from configured nodes in the slow collection loop *Environment: `$TX_COLLECTION_SLOW_NODES_INTERVAL_MS`* * `--p2p.txCollectionSlowReqRespIntervalMs ` (default: `12000`) How often to collect from peers via reqresp in the slow collection loop *Environment: `$TX_COLLECTION_SLOW_REQ_RESP_INTERVAL_MS`* * `--p2p.txCollectionSlowReqRespTimeoutMs ` (default: `20000`) How long to wait for a reqresp response during slow collection *Environment: `$TX_COLLECTION_SLOW_REQ_RESP_TIMEOUT_MS`* * `--p2p.txCollectionReconcileIntervalMs ` (default: `60000`) How often to reconcile found txs from the tx pool *Environment: `$TX_COLLECTION_RECONCILE_INTERVAL_MS`* * `--p2p.txCollectionDisableSlowDuringFastRequests ` (default: `true`) Whether to disable the slow collection loop if we are dealing with any immediate requests *Environment: `$TX_COLLECTION_DISABLE_SLOW_DURING_FAST_REQUESTS`* * `--p2p.txCollectionFastNodeIntervalMs ` (default: `500`) How many ms to wait between retried request to a node via RPC during fast collection *Environment: `$TX_COLLECTION_FAST_NODE_INTERVAL_MS`* * `--p2p.txCollectionNodeRpcUrls ` A comma-separated list of Aztec node RPC URLs to use for tx collection *Environment: `$TX_COLLECTION_NODE_RPC_URLS`* * `--p2p.txCollectionFastMaxParallelRequestsPerNode ` (default: `4`) Maximum number of parallel requests to make to a node during fast collection *Environment: `$TX_COLLECTION_FAST_MAX_PARALLEL_REQUESTS_PER_NODE`* * `--p2p.txCollectionNodeRpcMaxBatchSize ` (default: `50`) Maximum number of transactions to request from a node in a single batch *Environment: `$TX_COLLECTION_NODE_RPC_MAX_BATCH_SIZE`* * `--p2p.txCollectionMissingTxsCollectorType ` (default: `new`) Which collector implementation to use for missing txs collection (new or old) *Environment: `$TX_COLLECTION_MISSING_TXS_COLLECTOR_TYPE`* * `--p2p.txCollectionFileStoreUrls ` A comma-separated list of file store URLs (s3://, gs\://, file://, http\://) for tx collection *Environment: `$TX_COLLECTION_FILE_STORE_URLS`* * `--p2p.txCollectionFileStoreSlowDelayMs ` (default: `24000`) Delay before file store collection starts after slow collection *Environment: `$TX_COLLECTION_FILE_STORE_SLOW_DELAY_MS`* * `--p2p.txCollectionFileStoreFastDelayMs ` (default: `2000`) Delay before file store collection starts after fast collection *Environment: `$TX_COLLECTION_FILE_STORE_FAST_DELAY_MS`* * `--p2p.txCollectionFileStoreFastWorkerCount ` (default: `5`) Number of concurrent workers for fast file store collection *Environment: `$TX_COLLECTION_FILE_STORE_FAST_WORKER_COUNT`* * `--p2p.txCollectionFileStoreSlowWorkerCount ` (default: `2`) Number of concurrent workers for slow file store collection *Environment: `$TX_COLLECTION_FILE_STORE_SLOW_WORKER_COUNT`* * `--p2p.txCollectionFileStoreFastBackoffBaseMs ` (default: `1000`) Base backoff time in ms for fast file store collection retries *Environment: `$TX_COLLECTION_FILE_STORE_FAST_BACKOFF_BASE_MS`* * `--p2p.txCollectionFileStoreSlowBackoffBaseMs ` (default: `5000`) Base backoff time in ms for slow file store collection retries *Environment: `$TX_COLLECTION_FILE_STORE_SLOW_BACKOFF_BASE_MS`* * `--p2p.txCollectionFileStoreFastBackoffMaxMs ` (default: `5000`) Max backoff time in ms for fast file store collection retries *Environment: `$TX_COLLECTION_FILE_STORE_FAST_BACKOFF_MAX_MS`* * `--p2p.txCollectionFileStoreSlowBackoffMaxMs ` (default: `30000`) Max backoff time in ms for slow file store collection retries *Environment: `$TX_COLLECTION_FILE_STORE_SLOW_BACKOFF_MAX_MS`* * `--p2p.txFileStoreUrl ` URL for uploading txs to file storage (s3://, gs\://, file://) *Environment: `$TX_FILE_STORE_URL`* * `--p2p.txFileStoreUploadConcurrency ` (default: `10`) Maximum number of concurrent tx uploads *Environment: `$TX_FILE_STORE_UPLOAD_CONCURRENCY`* * `--p2p.txFileStoreMaxQueueSize ` (default: `1000`) Maximum queue size for pending uploads (oldest dropped when exceeded) *Environment: `$TX_FILE_STORE_MAX_QUEUE_SIZE`* * `--p2p.txFileStoreEnabled ` Enable uploading transactions to file storage *Environment: `$TX_FILE_STORE_ENABLED`* **P2P BOOTSTRAP** * `--p2p-bootstrap` Starts Aztec P2P Bootstrap with options * `--p2pBootstrap.p2pBroadcastPort ` The port to broadcast the P2P service on (included in the node's ENR). Defaults to P2P\_PORT. *Environment: `$P2P_BROADCAST_PORT`* * `--p2pBootstrap.peerIdPrivateKeyPath ` An optional path to store generated peer id private keys. If blank, will default to storing any generated keys in the root of the data directory. *Environment: `$PEER_ID_PRIVATE_KEY_PATH`* * `--p2pBootstrap.queryForIp ` If announceUdpAddress or announceTcpAddress are not provided, query for the IP address of the machine. Default is false. *Environment: `$P2P_QUERY_FOR_IP`* **TELEMETRY** * `--tel.metricsCollectorUrl ` The URL of the telemetry collector for metrics *Environment: `$OTEL_EXPORTER_OTLP_METRICS_ENDPOINT`* * `--tel.tracesCollectorUrl ` The URL of the telemetry collector for traces *Environment: `$OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`* * `--tel.logsCollectorUrl ` The URL of the telemetry collector for logs *Environment: `$OTEL_EXPORTER_OTLP_LOGS_ENDPOINT`* * `--tel.otelCollectIntervalMs ` (default: `60000`) The interval at which to collect metrics *Environment: `$OTEL_COLLECT_INTERVAL_MS`* * `--tel.otelExportTimeoutMs ` (default: `30000`) The timeout for exporting metrics *Environment: `$OTEL_EXPORT_TIMEOUT_MS`* * `--tel.otelExcludeMetrics ` A list of metric prefixes to exclude from export *Environment: `$OTEL_EXCLUDE_METRICS`* * `--tel.otelIncludeMetrics ` A list of metric prefixes to include in export (ignored if OTEL\_EXCLUDE\_METRICS is set) *Environment: `$OTEL_INCLUDE_METRICS`* * `--tel.publicMetricsCollectorUrl ` A URL to publish a subset of metrics for public consumption *Environment: `$PUBLIC_OTEL_EXPORTER_OTLP_METRICS_ENDPOINT`* * `--tel.publicMetricsCollectFrom ` The role types to collect metrics from *Environment: `$PUBLIC_OTEL_COLLECT_FROM`* * `--tel.publicIncludeMetrics ` A list of metric prefixes to publicly export *Environment: `$PUBLIC_OTEL_INCLUDE_METRICS`* * `--tel.publicMetricsOptOut ` (default: `true`) Whether to opt out of sharing optional telemetry *Environment: `$PUBLIC_OTEL_OPT_OUT`* **BOT** * `--bot` Starts Aztec Bot with options * `--bot.nodeUrl ` The URL to the Aztec node to check for tx pool status. *Environment: `$AZTEC_NODE_URL`* * `--bot.nodeAdminUrl ` The URL to the Aztec node admin API to force-flush txs if configured. *Environment: `$AZTEC_NODE_ADMIN_URL`* * `--bot.l1Mnemonic ` The mnemonic for the account to bridge fee juice from L1. *Environment: `$BOT_L1_MNEMONIC`* * `--bot.l1PrivateKey ` The private key for the account to bridge fee juice from L1. *Environment: `$BOT_L1_PRIVATE_KEY`* * `--bot.l1ToL2MessageTimeoutSeconds ` (default: `3600`) How long to wait for L1 to L2 messages to become available on L2 *Environment: `$BOT_L1_TO_L2_TIMEOUT_SECONDS`* * `--bot.senderPrivateKey ` Signing private key for the sender account. *Environment: `$BOT_PRIVATE_KEY`* * `--bot.senderSalt ` The salt to use to deploy the sender account. *Environment: `$BOT_ACCOUNT_SALT`* * `--bot.tokenSalt ` (default: `0x0000000000000000000000000000000000000000000000000000000000000001`) The salt to use to deploy the token contract. *Environment: `$BOT_TOKEN_SALT`* * `--bot.txIntervalSeconds ` (default: `60`) Every how many seconds should a new tx be sent. *Environment: `$BOT_TX_INTERVAL_SECONDS`* * `--bot.privateTransfersPerTx ` (default: `1`) How many private token transfers are executed per tx. *Environment: `$BOT_PRIVATE_TRANSFERS_PER_TX`* * `--bot.publicTransfersPerTx ` (default: `1`) How many public token transfers are executed per tx. *Environment: `$BOT_PUBLIC_TRANSFERS_PER_TX`* * `--bot.feePaymentMethod ` (default: `fee_juice`) How to handle fee payments. (Options: fee\_juice) *Environment: `$BOT_FEE_PAYMENT_METHOD`* * `--bot.minFeePadding ` (default: `3`) How much is the bot willing to overpay vs. the current base fee *Environment: `$BOT_MIN_FEE_PADDING`* * `--bot.noStart ` True to not automatically setup or start the bot on initialization. *Environment: `$BOT_NO_START`* * `--bot.txMinedWaitSeconds ` (default: `180`) How long to wait for a tx to be mined before reporting an error. *Environment: `$BOT_TX_MINED_WAIT_SECONDS`* * `--bot.followChain ` (default: `NONE`) Which chain the bot follows *Environment: `$BOT_FOLLOW_CHAIN`* * `--bot.maxPendingTxs ` (default: `128`) Do not send a tx if the node's tx pool already has this many pending txs. *Environment: `$BOT_MAX_PENDING_TXS`* * `--bot.flushSetupTransactions ` Make a request for the sequencer to build a block after each setup transaction. *Environment: `$BOT_FLUSH_SETUP_TRANSACTIONS`* * `--bot.l2GasLimit ` L2 gas limit for the tx (empty to let the bot's wallet estimate). *Environment: `$BOT_L2_GAS_LIMIT`* * `--bot.daGasLimit ` DA gas limit for the tx (empty to let the bot's wallet estimate). *Environment: `$BOT_DA_GAS_LIMIT`* * `--bot.contract ` (default: `TokenContract`) Token contract to use *Environment: `$BOT_TOKEN_CONTRACT`* * `--bot.maxConsecutiveErrors ` The maximum number of consecutive errors before the bot shuts down *Environment: `$BOT_MAX_CONSECUTIVE_ERRORS`* * `--bot.stopWhenUnhealthy ` Stops the bot if service becomes unhealthy *Environment: `$BOT_STOP_WHEN_UNHEALTHY`* * `--bot.botMode ` (default: `transfer`) Bot mode: transfer, amm, or crosschain *Environment: `$BOT_MODE`* * `--bot.l2ToL1MessagesPerTx ` (default: `1`) Number of L2→L1 messages per tx (crosschain mode) *Environment: `$BOT_L2_TO_L1_MESSAGES_PER_TX`* * `--bot.l1ToL2SeedCount ` (default: `1`) Max L1→L2 messages to keep in-flight (crosschain mode) *Environment: `$BOT_L1_TO_L2_SEED_COUNT`* **PXE** * `--pxe.l2BlockBatchSize ` (default: `50`) Maximum amount of blocks to pull from the stream in one request when synchronizing *Environment: `$PXE_L2_BLOCK_BATCH_SIZE`* * `--pxe.proverEnabled ` (default: `true`) Enable real proofs *Environment: `$PXE_PROVER_ENABLED`* * `--pxe.syncChainTip ` (default: `proposed`) Which chain tip to sync to (proposed, checkpointed, proven, finalized) *Environment: `$PXE_SYNC_CHAIN_TIP`* * `--pxe.nodeUrl ` Custom Aztec Node URL to connect to *Environment: `$AZTEC_NODE_URL`* **TXE** * `--txe` Starts Aztec TXE with options ### aztec test[​](#aztec-test "Direct link to aztec test") *Help for this command is currently unavailable.* ### aztec trigger-seed-snapshot[​](#aztec-trigger-seed-snapshot "Direct link to aztec trigger-seed-snapshot") Triggers a seed snapshot for the next epoch. **Usage:** ``` aztec trigger-seed-snapshot [options] ``` **Options:** * `-pk, --private-key ` - The private key to use for deployment * `-m, --mnemonic ` - The mnemonic to use in deployment (default: "test test test test test test test test test test test junk") * `--rollup
` - ethereum address of the rollup contract * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \["http\://localhost:8545"], env: ETHEREUM\_HOSTS) * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `-h, --help` - display help for command ### aztec update[​](#aztec-update "Direct link to aztec update") Updates Nodejs and Noir dependencies **Usage:** ``` aztec update [options] [projectPath] ``` **Options:** * `--contract [paths...]` - Paths to contracts to update dependencies (default: \[]) * `--aztec-version ` - The version to update Aztec packages to. Defaults to latest (default: "latest") * `-h, --help` - display help for command ### aztec validator-keys|valKeys[​](#aztec-validator-keysvalkeys "Direct link to aztec validator-keys|valKeys") *This subcommand does not provide its own help information.* ### aztec vote-on-governance-proposal[​](#aztec-vote-on-governance-proposal "Direct link to aztec vote-on-governance-proposal") Votes on a governance proposal. **Usage:** ``` aztec vote-on-governance-proposal [options] ``` **Options:** * `-p, --proposal-id ` - The ID of the proposal * `-a, --vote-amount ` - The amount of tokens to vote * `--in-favor ` - Whether to vote in favor of the proposal. Use "yea" for true, any other value for false. * `--wait ` - Whether to wait until the proposal is active * `-r, --registry-address ` - The address of the registry contract * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: \["http\://localhost:8545"], env: ETHEREUM\_HOSTS) * `-c, --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, env: L1\_CHAIN\_ID) * `-pk, --private-key ` - The private key to use to vote * `-m, --mnemonic ` - The mnemonic to use to vote (default: "test test test test test test test test test test test junk") * `-i, --mnemonic-index ` - The index of the mnemonic to use to vote (default: 0) * `-h, --help` - display help for command --- # Aztec Up CLI Reference *This documentation is auto-generated from the `aztec-up` CLI help output.* *Generated: Mon 18 May 2026 14:25:52 UTC* *Command: `aztec-up`* ## Table of Contents[​](#table-of-contents "Direct link to Table of Contents") * [aztec-up](#aztec-up) * [aztec-up env](#aztec-up-env) * [aztec-up install](#aztec-up-install) * [aztec-up list](#aztec-up-list) * [aztec-up self-update](#aztec-up-self-update) * [aztec-up uninstall](#aztec-up-uninstall) * [aztec-up use](#aztec-up-use) ## aztec-up[​](#aztec-up "Direct link to aztec-up") aztec-up - Aztec version manager **Usage:** ``` aztec-up [command] [options] ``` **Available Commands:** * `env` - Output PATH for .aztecrc version (for eval) * `install ` - Install a version and switch to it * `list` - List installed versions * `self-update` - Update aztec-up itself to the latest version * `uninstall ` - Remove an installed version * `use []` - Switch to an installed version (or read from .aztecrc) **Options:** * `-h --help` - Show this help message **Examples:** ``` aztec-up install 0.85.0 Install a specific version aztec-up install nightly Install the nightly version aztec-up use 0.85.0 Switch to version 0.85.0 aztec-up use Read version from .aztecrc and switch to it aztec-up list Show all installed versions aztec-up self-update Update aztec-up to latest ``` ### Subcommands[​](#subcommands "Direct link to Subcommands") ### aztec-up env[​](#aztec-up-env "Direct link to aztec-up env") Output PATH export for the version specified in .aztecrc **Usage:** ``` aztec-up env ``` **Options:** * `-h, --help` - Print help ### aztec-up install[​](#aztec-up-install "Direct link to aztec-up install") Install a version of Aztec and switch to it **Usage:** ``` aztec-up install ``` **Options:** * `-h, --help` - Print help ### aztec-up list[​](#aztec-up-list "Direct link to aztec-up list") List installed Aztec versions and available aliases **Usage:** ``` aztec-up list ``` **Options:** * `-h, --help` - Print help ### aztec-up self-update[​](#aztec-up-self-update "Direct link to aztec-up self-update") Update aztec-up itself to the latest version **Usage:** ``` aztec-up self-update ``` **Options:** * `-h, --help` - Print help ### aztec-up uninstall[​](#aztec-up-uninstall "Direct link to aztec-up uninstall") Remove an installed version of Aztec **Usage:** ``` aztec-up uninstall ``` **Options:** * `-h, --help` - Print help ### aztec-up use[​](#aztec-up-use "Direct link to aztec-up use") Switch to an installed version of Aztec **Usage:** ``` aztec-up use [VERSION] ``` **Options:** * `-h, --help` - Print help --- # Aztec Wallet CLI Reference *This documentation is auto-generated from the `aztec-wallet` CLI help output.* *Generated: Mon 18 May 2026 14:25:52 UTC* *Command: `aztec-wallet`* ## Table of Contents[​](#table-of-contents "Direct link to Table of Contents") * [aztec-wallet](#aztec-wallet) * [aztec-wallet alias](#aztec-wallet-alias) * [aztec-wallet authorize-action](#aztec-wallet-authorize-action) * [aztec-wallet bridge-fee-juice](#aztec-wallet-bridge-fee-juice) * [aztec-wallet create-account](#aztec-wallet-create-account) * [aztec-wallet create-authwit](#aztec-wallet-create-authwit) * [aztec-wallet create-secret](#aztec-wallet-create-secret) * [aztec-wallet deploy](#aztec-wallet-deploy) * [aztec-wallet deploy-account](#aztec-wallet-deploy-account) * [aztec-wallet get-alias](#aztec-wallet-get-alias) * [aztec-wallet get-fee-juice-balance](#aztec-wallet-get-fee-juice-balance) * [aztec-wallet get-tx](#aztec-wallet-get-tx) * [aztec-wallet import-test-accounts](#aztec-wallet-import-test-accounts) * [aztec-wallet profile](#aztec-wallet-profile) * [aztec-wallet register-contract](#aztec-wallet-register-contract) * [aztec-wallet register-sender](#aztec-wallet-register-sender) * [aztec-wallet send](#aztec-wallet-send) * [aztec-wallet simulate](#aztec-wallet-simulate) ## aztec-wallet[​](#aztec-wallet "Direct link to aztec-wallet") Aztec wallet **Usage:** ``` wallet [options] [command] ``` **Available Commands:** * `alias ` - Aliases information for easy reference. * `authorize-action [options] ` - Authorizes a public call on the caller, so they can perform an action on behalf of the provided account * `bridge-fee-juice [options] ` - Mints L1 Fee Juice and pushes them to L2. * `create-account [options]` - Creates an aztec account that can be used for sending transactions. * `create-authwit [options] ` - Creates an authorization witness that can be privately sent to a caller so they can perform an action on behalf of the provided account * `create-secret [options]` - Creates an aliased secret to use in other commands * `deploy [options] [artifact]` - Deploys a compiled Aztec.nr contract to Aztec. * `deploy-account [options]
` - Deploys an already registered aztec account that can be used for sending transactions. * `get-alias [alias]` - Shows stored aliases * `get-fee-juice-balance [options]
` - Checks the Fee Juice balance for a given address. * `get-tx [options] [txHash]` - Gets the status of the recent txs, or a detailed view if a specific transaction hash is provided * `help [command]` - display help for command * `import-test-accounts [options]` - Import test accounts from pxe. * `profile [options] ` - Profiles a private function by counting the unconditional operations in its execution steps * `register-contract [options] [address] [artifact]` - Registers a contract in this wallet's PXE * `register-sender [options] [address]` - Registers a sender's address in the wallet, so the note synching process will look for notes sent by them * `send [options] ` - Calls a function on an Aztec contract. * `simulate [options] ` - Simulates the execution of a function on an Aztec contract. **Options:** * `-V --version` - output the version number * `-d --data-dir ` - Storage directory for wallet data (default: "\~/.aztec/wallet") * `-p --prover ` - The type of prover the wallet uses (choices: "wasm", "native", "none", default: "native", env: PXE\_PROVER) * `-n --node-url ` - URL of the Aztec node to connect to (default: "", env: AZTEC\_NODE\_URL) * `-h --help` - display help for command ### Subcommands[​](#subcommands "Direct link to Subcommands") ### aztec-wallet alias[​](#aztec-wallet-alias "Direct link to aztec-wallet alias") Aliases information for easy reference. **Usage:** ``` wallet alias [options] ``` **Options:** * `-h --help` - display help for command ### aztec-wallet authorize-action[​](#aztec-wallet-authorize-action "Direct link to aztec-wallet authorize-action") Authorizes a public call on the caller, so they can perform an action on behalf **Usage:** ``` wallet authorize-action [options] ``` **Options:** * `--args` - \[args...] Function arguments (default: \[]) * `-ca --contract-address
` - Aztec address of the contract. * `-c --contract-artifact ` - Path to a compiled Aztec contract's artifact in JSON format. If executed inside a nargo workspace, a package and contract name can be specified as package\@contract * `-f --from ` - Alias or address of the account to simulate from * `-h --help` - display help for command ### aztec-wallet bridge-fee-juice[​](#aztec-wallet-bridge-fee-juice "Direct link to aztec-wallet bridge-fee-juice") Mints L1 Fee Juice and pushes them to L2. **Usage:** ``` wallet bridge-fee-juice [options] ``` **Options:** * `--l1-rpc-urls ` - List of Ethereum host URLs. Chain identifiers * `-m --mnemonic ` - The mnemonic to use for deriving the Ethereum * `--mint` - Mint the tokens on L1 (default: false) * `--l1-private-key ` - The private key to the eth account bridging * `-c --l1-chain-id ` - Chain ID of the ethereum host (default: 31337, * `--json` - Output the claim in JSON format * `--no-wait` - Wait for the bridged funds to be available in L2, * `--interval ` - The polling interval in seconds for the bridged * `-h --help` - display help for command ### aztec-wallet create-account[​](#aztec-wallet-create-account "Direct link to aztec-wallet create-account") Creates an aztec account that can be used for sending transactions. Registers **Usage:** ``` wallet create-account [options] ``` **Options:** * `-f --from ` - Alias or address of the account performing the deployment * `--skip-initialization` - Skip initializing the account contract. Useful for publicly deploying an existing account. * `--public-deploy` - Publishes the account contract instance (and the class, if needed). Needed if the contract contains public functions. * `--register-class` - Register the contract class (useful for when the contract class has not been deployed yet). * `-p --public-key ` - Public key that identifies a private signing key stored outside of the wallet. Used for ECDSA SSH accounts over the secp256r1 curve. * `-sk --secret-key ` - Secret key for account. Uses random by default. (env: SECRET\_KEY) * `-a --alias ` - Alias for the account. Used for easy reference in subsequent commands. * `-t --type ` - Type of account to create (choices: "schnorr", "ecdsasecp256r1", "ecdsasecp256r1ssh", "ecdsasecp256k1", default: "schnorr") * `-s --salt ` - Optional deployment salt as a hex string for generating the deployment address. Defaults to 0. * `--register-only` - Just register the account on the Wallet. Do not deploy or initialize the account contract. * `--json` - Emit output as json * `--no-wait` - Skip waiting for the contract to be deployed. Print the hash of deployment transaction * `--wait-for-status ` - Tx status to wait for: 'proposed' or 'checkpointed' (default: "proposed") * `-v --verbose` - Provide timings on all executed operations (synching, simulating, proving) (default: false) * `--payment ` - Fee payment method and arguments. * `--gas-limits ` - Gas limits for the tx. * `--max-fees-per-gas ` - Maximum fees per gas unit for DA and L2 computation. * `--max-priority-fees-per-gas ` - Maximum priority fees per gas unit for DA and L2 computation. * `--estimate-gas-only` - Only report gas estimation for the tx, do not send it. * `-h --help` - display help for command ### aztec-wallet create-authwit[​](#aztec-wallet-create-authwit "Direct link to aztec-wallet create-authwit") Creates an authorization witness that can be privately sent to a caller so they **Usage:** ``` wallet create-authwit [options] ``` **Options:** * `--args` - \[args...] Function arguments (default: \[]) * `-ca --contract-address
` - Aztec address of the contract. * `-c --contract-artifact ` - Path to a compiled Aztec contract's artifact in JSON format. If executed inside a nargo workspace, a package and contract name can be specified as package\@contract * `-f --from ` - Alias or address of the account to simulate from * `-a --alias ` - Alias for the authorization witness. Used for easy reference in subsequent commands. * `-h --help` - display help for command ### aztec-wallet create-secret[​](#aztec-wallet-create-secret "Direct link to aztec-wallet create-secret") Creates an aliased secret to use in other commands **Usage:** ``` wallet create-secret [options] ``` **Options:** * `-a --alias ` - Key to alias the secret with * `-h --help` - display help for command ### aztec-wallet deploy[​](#aztec-wallet-deploy "Direct link to aztec-wallet deploy") Deploys a compiled Aztec.nr contract to Aztec. **Usage:** ``` wallet deploy [options] [artifact] ``` **Options:** * `--init ` - The contract initializer function to call (default: "constructor") * `--no-init` - Leave the contract uninitialized * `-k --public-key ` - Optional encryption public key for this address. Set this value only if this contract is expected to receive private notes, which will be encrypted using this public key. * `-s --salt ` - Optional deployment salt as a hex string for generating the deployment address. Defaults to random. * `--universal` - Do not mix the sender address into the deployment. * `--args` - \[args...] Constructor arguments (default: \[]) * `-f --from ` - Alias or address of the account to deploy from * `-a --alias ` - Alias for the contract. Used for easy reference subsequent commands. * `--json` - Emit output as json * `--no-wait` - Skip waiting for the contract to be deployed. Print the hash of deployment transaction * `--no-class-registration` - Don't register this contract class * `--no-public-deployment` - Don't emit this contract's public bytecode * `--timeout ` - The amount of time in seconds to wait for the deployment to post to L2 * `--wait-for-status ` - Tx status to wait for: 'proposed' or 'checkpointed' (default: "proposed") * `-v --verbose` - Provide timings on all executed operations (synching, simulating, proving) (default: false) * `--payment ` - Fee payment method and arguments. * `--gas-limits ` - Gas limits for the tx. * `--max-fees-per-gas ` - Maximum fees per gas unit for DA and L2 computation. * `--max-priority-fees-per-gas ` - Maximum priority fees per gas unit for DA and L2 computation. * `--estimate-gas-only` - Only report gas estimation for the tx, do not send it. * `-h --help` - display help for command ### aztec-wallet deploy-account[​](#aztec-wallet-deploy-account "Direct link to aztec-wallet deploy-account") Deploys an already registered aztec account that can be used for sending **Usage:** ``` wallet deploy-account [options]
``` **Options:** * `-f --from ` - Alias or address of the account performing the deployment * `--json` - Emit output as json * `--no-wait` - Skip waiting for the contract to be deployed. Print the hash of deployment transaction * `--register-class` - Register the contract class (useful for when the contract class has not been deployed yet). * `--public-deploy` - Publishes the account contract instance (and the class, if needed). Needed if the contract contains public functions. * `--skip-initialization` - Skip initializing the account contract. Useful for publicly deploying an existing account. * `--wait-for-status ` - Tx status to wait for: 'proposed' or 'checkpointed' (default: "proposed") * `-v --verbose` - Provide timings on all executed operations (synching, simulating, proving) (default: false) * `--payment ` - Fee payment method and arguments. * `--gas-limits ` - Gas limits for the tx. * `--max-fees-per-gas ` - Maximum fees per gas unit for DA and L2 computation. * `--max-priority-fees-per-gas ` - Maximum priority fees per gas unit for DA and L2 computation. * `--estimate-gas-only` - Only report gas estimation for the tx, do not send it. * `-h --help` - display help for command ### aztec-wallet get-alias[​](#aztec-wallet-get-alias "Direct link to aztec-wallet get-alias") Shows stored aliases **Usage:** ``` wallet get-alias [options] [alias] ``` **Options:** * `-h --help` - display help for command ### aztec-wallet get-fee-juice-balance[​](#aztec-wallet-get-fee-juice-balance "Direct link to aztec-wallet get-fee-juice-balance") Checks the Fee Juice balance for a given address. **Usage:** ``` wallet get-fee-juice-balance [options]
``` **Options:** * `--json` - Emit output as json * `--exact` - Show exact balance with all 18 decimal places * `-h --help` - display help for command ### aztec-wallet get-tx[​](#aztec-wallet-get-tx "Direct link to aztec-wallet get-tx") Gets the status of the recent txs, or a detailed view if a specific transaction **Usage:** ``` wallet get-tx [options] [txHash] ``` **Options:** * `-p --page ` - The page number to display (default: 1) * `-s --page-size ` - The number of transactions to display per page * `-h --help` - display help for command ### aztec-wallet import-test-accounts[​](#aztec-wallet-import-test-accounts "Direct link to aztec-wallet import-test-accounts") Import test accounts from pxe. **Usage:** ``` wallet import-test-accounts [options] ``` **Options:** * `--json` - Emit output as json * `-h --help` - display help for command ### aztec-wallet profile[​](#aztec-wallet-profile "Direct link to aztec-wallet profile") Profiles a private function by counting the unconditional operations in its **Usage:** ``` wallet profile [options] ``` **Options:** * `--args` - \[args...] Function arguments (default: \[]) * `-ca --contract-address
` - Aztec address of the contract. * `-c --contract-artifact ` - Path to a compiled Aztec contract's artifact in JSON format. If executed inside a nargo workspace, a package and contract name can be specified as package\@contract * `--debug-execution-steps-dir
` - Directory to write execution step artifacts for bb profiling/debugging. * `-aw --auth-witness ` - Authorization witness to use for the simulation * `-f --from ` - Alias or address of the account to simulate from * `--payment ` - Fee payment method and arguments. * `--gas-limits ` - Gas limits for the tx. * `--max-fees-per-gas ` - Maximum fees per gas unit for DA and L2 computation. * `--max-priority-fees-per-gas ` - Maximum priority fees per gas unit for DA and L2 computation. * `--estimate-gas-only` - Only report gas estimation for the tx, do not send it. * `-h --help` - display help for command ### aztec-wallet register-contract[​](#aztec-wallet-register-contract "Direct link to aztec-wallet register-contract") Registers a contract in this wallet's PXE **Usage:** ``` wallet register-contract [options] [address] [artifact] ``` **Options:** * `--init ` - The contract initializer function to call * `-k --public-key ` - Optional encryption public key for this address. * `-s --salt ` - Optional deployment salt as a hex string for * `--deployer ` - The address of the account that deployed the * `--args` - \[args...] Constructor arguments (default: \[]) * `-a --alias ` - Alias for the contact. Used for easy reference in * `-h --help` - display help for command ### aztec-wallet register-sender[​](#aztec-wallet-register-sender "Direct link to aztec-wallet register-sender") Registers a sender's address in the wallet, so the note synching process will **Usage:** ``` wallet register-sender [options] [address] ``` **Options:** * `-a --alias ` - Alias for the sender. Used for easy reference in * `-h --help` - display help for command ### aztec-wallet send[​](#aztec-wallet-send "Direct link to aztec-wallet send") Calls a function on an Aztec contract. **Usage:** ``` wallet send [options] ``` **Options:** * `--args` - \[args...] Function arguments (default: \[]) * `-c --contract-artifact ` - Path to a compiled Aztec contract's artifact in JSON format. If executed inside a nargo workspace, a package and contract name can be specified as package\@contract * `-ca --contract-address
` - Aztec address of the contract. * `-a --alias ` - Alias for the transaction hash. Used for easy reference in subsequent commands. * `-aw --auth-witness ` - Authorization witness to use for the transaction. If using multiple, pass a comma separated string * `-f --from ` - Alias or address of the account to send the transaction from * `--no-wait` - Print transaction hash without waiting for it to be mined * `--wait-for-status ` - Tx status to wait for: 'proposed' or 'checkpointed' (default: "proposed") * `-v --verbose` - Provide timings on all executed operations (synching, simulating, proving) (default: false) * `--payment ` - Fee payment method and arguments. * `--gas-limits ` - Gas limits for the tx. * `--max-fees-per-gas ` - Maximum fees per gas unit for DA and L2 computation. * `--max-priority-fees-per-gas ` - Maximum priority fees per gas unit for DA and L2 computation. * `--estimate-gas-only` - Only report gas estimation for the tx, do not send it. * `-h --help` - display help for command ### aztec-wallet simulate[​](#aztec-wallet-simulate "Direct link to aztec-wallet simulate") Simulates the execution of a function on an Aztec contract. **Usage:** ``` wallet simulate [options] ``` **Options:** * `--args` - \[args...] Function arguments (default: \[]) * `-ca --contract-address
` - Aztec address of the contract. * `-c --contract-artifact ` - Path to a compiled Aztec contract's artifact in JSON format. If executed inside a nargo workspace, a package and contract name can be specified as package\@contract * `-sk --secret-key ` - The sender's secret key (env: SECRET\_KEY) * `-aw --auth-witness ` - Authorization witness to use for the simulation * `-f --from ` - Alias or address of the account to simulate from * `-v --verbose` - Provide timings on all executed operations (synching, simulating, proving) (default: false) * `--payment ` - Fee payment method and arguments. * `--gas-limits ` - Gas limits for the tx. * `--max-fees-per-gas ` - Maximum fees per gas unit for DA and L2 computation. * `--max-priority-fees-per-gas ` - Maximum priority fees per gas unit for DA and L2 computation. * `--estimate-gas-only` - Only report gas estimation for the tx, do not send it. * `-h --help` - display help for command --- # Aztec Overview This page outlines Aztec's fundamental technical concepts. It is recommended to read this before diving into building on Aztec. ## What is Aztec?[​](#what-is-aztec "Direct link to What is Aztec?") Aztec is a privacy-first Layer 2 on Ethereum. It supports smart contracts with both private & public state and private & public execution. Prefer video? This explainer covers the core idea in under 90 seconds, and there are more [video lessons](/developers/docs/resources/video_lessons.md) available. [What is Aztec: Explained in Under 90 Seconds](https://www.youtube-nocookie.com/embed/urcBvo2QJp0) ![](/assets/ideal-img/Aztec_overview.4d3e9fb.640.png) ## High level view[​](#high-level-view "Direct link to High level view") ![](/assets/ideal-img/aztec-high-level.4ac0d53.640.png) 1. A user interacts with Aztec through Aztec.js (like web3js or ethersjs) 2. Private functions are executed in the PXE, which is client-side 3. Proofs and tree updates are sent to the Public VM (running on an Aztec node) 4. Public functions are executed in the Public VM 5. The Public VM rolls up the transactions that include private and public state updates into blocks 6. The block data and proof of a correct state transition are submitted to Ethereum for verification ## Private and public execution[​](#private-and-public-execution "Direct link to Private and public execution") Private functions are executed client side, on user devices to maintain maximum privacy. Public functions are executed by a remote network of nodes, similar to other blockchains. These distinct execution environments create a directional execution flow for a single transaction--a transaction begins in the private context on the user's device then moves to the public network. This means that private functions executed by a transaction can enqueue public functions to be executed later in the transaction life cycle, but public functions cannot call private functions. ### Private Execution Environment (PXE)[​](#private-execution-environment-pxe "Direct link to Private Execution Environment (PXE)") Private functions are executed on the user's device in the Private Execution Environment (PXE, pronounced 'pixie'), then it generates proofs for onchain verification. It is a client-side library for execution and proof-generation of private operations. It holds keys, notes, and generates proofs. It is included in aztec.js, a TypeScript library, and can be run within Node or the browser. Note: It is easy for private functions to be written in a detrimentally unoptimized way, because many intuitions of regular program execution do not apply to proving. For more about writing performant private functions in Noir, see [this page](https://noir-lang.org/docs/explainers/explainer-writing-noir) of the Noir documentation. ### Aztec Virtual Machine (AVM)[​](#aztec-virtual-machine-avm "Direct link to Aztec Virtual Machine (AVM)") Public functions are executed by the Aztec Virtual Machine (AVM), which is conceptually similar to the Ethereum Virtual Machine (EVM). As such, writing efficient public functions follow the same intuition as gas-efficient solidity contracts. The PXE is unaware of the Public VM. And the Public VM is unaware of the PXE. They are completely separate execution environments. This means: * The PXE and the Public VM cannot directly communicate with each other * Private transactions in the PXE are executed first, followed by public transactions ## Private and public state[​](#private-and-public-state "Direct link to Private and public state") Private state works with UTXOs, which are chunks of data that we call notes. To keep things private, notes are stored in an [append-only UTXO tree](/developers/docs/foundational-topics/advanced/storage/indexed_merkle_tree.md), and a nullifier is created when notes are invalidated (aka deleted). Nullifiers are stored in their own [nullifier tree](/developers/docs/foundational-topics/advanced/storage/indexed_merkle_tree.md). Public state works similarly to other chains like Ethereum, behaving like a public ledger. Public data is stored in a public data tree. ![Public vs private state](/assets/images/public-and-private-state-diagram-ff88262b40b259d4fe4c8b7d667924aa.png) Aztec [smart contract](/developers/docs/aztec-nr/framework-description/contract_structure.md) developers should keep in mind that different data types are used when manipulating private or public state. Working with private state is creating commitments and nullifiers to state, whereas working with public state is directly updating state. ## Accounts and keys[​](#accounts-and-keys "Direct link to Accounts and keys") ### Account abstraction[​](#account-abstraction "Direct link to Account abstraction") Every account in Aztec is a smart contract (account abstraction). This allows implementing different schemes for authorizing transactions, nonce management, and fee payments. Developers can write their own account contract to define the rules by which user transactions are authorized and paid for, as well as how user keys are managed. Learn more about account contracts [here](/developers/docs/foundational-topics/accounts.md). ### Key pairs[​](#key-pairs "Direct link to Key pairs") Each account in Aztec is backed by 3 key pairs: * A **nullifier key pair** used for note nullifier computation * A **incoming viewing key pair** used to encrypt a note for the recipient * A **outgoing viewing key pair** used to encrypt a note for the sender As Aztec has native account abstraction, accounts do not automatically have a signing key pair to authenticate transactions. This is up to the account contract developer to implement. ## Noir[​](#noir "Direct link to Noir") Noir is a zero-knowledge domain specific language used for writing smart contracts for the Aztec network. It is also possible to write circuits with Noir that can be verified on or offchain. For more in-depth docs into the features of Noir, go to the [Noir documentation](https://noir-lang.org/). --- # Understanding Accounts in Aztec This page provides a comprehensive understanding of how accounts work in Aztec. We'll explore the architecture, implementation details, and the powerful features enabled by Aztec's native account abstraction. ## What is Account Abstraction?[​](#what-is-account-abstraction "Direct link to What is Account Abstraction?") Account abstraction fundamentally changes how we think about blockchain accounts. Instead of accounts being simple key pairs (like in Bitcoin or traditional Ethereum EOAs), accounts become programmable smart contracts that can define their own rules for authentication, authorization, and transaction execution. ### Why Account Abstraction Matters[​](#why-account-abstraction-matters "Direct link to Why Account Abstraction Matters") Traditional blockchain accounts have significant limitations: * **Rigid authentication**: You lose your private key, you lose everything * **Limited authorization**: Can't easily implement multi-signature schemes or time-locked transactions * **Fixed fee payment**: Must pay fees in the native token from the same account * **No customization**: Can't adapt to different security requirements or use cases Account abstraction solves these problems by making accounts programmable. This enables: * **Recovery mechanisms**: Social recovery, hardware wallet backups, time-delayed recovery * **Flexible authentication**: Biometrics, passkeys, multi-factor authentication, custom signature schemes * **Fee abstraction**: Pay fees in any token, or have someone else pay for you * **Custom authorization**: Complex permission systems, spending limits, automated transactions ## Aztec's Native Account Abstraction[​](#aztecs-native-account-abstraction "Direct link to Aztec's Native Account Abstraction") Unlike Ethereum where account abstraction is implemented at the application layer (ex. ERC-4337), Aztec has **native account abstraction** at the protocol level. This means: 1. **Every account is a smart contract** - There are no externally owned accounts (EOAs) 2. **Unified experience** - All accounts have the same capabilities and flexibility 3. **Protocol-level support** - The entire network is designed around smart contract accounts 4. **Privacy-first design** - Account abstraction works seamlessly with Aztec's privacy features ### Breaking the DoS Attack Problem[​](#breaking-the-dos-attack-problem "Direct link to Breaking the DoS Attack Problem") One of the biggest challenges in account abstraction is preventing denial-of-service (DoS) attacks. If accounts can have arbitrary validation logic, malicious actors could flood the network with transactions that are expensive to validate but ultimately invalid. Other account abstraction systems (like ERC-4337) solve this by restricting what validation logic can do - limiting opcodes, storage access, and gas. This works but limits flexibility. Aztec takes a different approach: validation happens client-side with ZK proofs, so the sequencer only verifies a constant-size proof regardless of validation complexity: With this approach: * **Client performs validation**: All complex logic runs on the user's device * **Proof generation**: The client generates a succinct ZK proof that validation succeeded * **Constant verification cost**: The sequencer only verifies the proof - a constant-time operation regardless of validation complexity This means we can have: * **Unlimited validation complexity** without affecting network performance * **Free complex operations** like verifying 100 signatures or checking complex conditions * **Better privacy** as validation logic isn't visible onchain ## How Aztec Accounts Work[​](#how-aztec-accounts-work "Direct link to How Aztec Accounts Work") ### Account Architecture[​](#account-architecture "Direct link to Account Architecture") Every Aztec account is a smart contract with a specific structure. At its core, an account contract must: 1. **Authenticate transactions** - Verify that the transaction is authorized by the account owner 2. **Execute calls** - Perform the requested operations (transfers, contract calls, etc.) 3. **Manage keys** - Handle the various keys used for privacy and authentication 4. **Handle fees** - Determine how transaction fees are paid ### The Account Contract Structure[​](#the-account-contract-structure "Direct link to The Account Contract Structure") Here's the essential structure of an Aztec account contract: The entrypoint function follows this pattern: 1. **Authentication** - Verify the transaction is authorized (signatures, multisig, etc.) 2. **Fee Payer Setup** - Set the account as fee payer if using its own balance 3. **Application Execution** - Execute the requested function calls 4. **Cancellation Handling** - Optionally emit a nullifier for transaction cancellation ### Address Derivation[​](#address-derivation "Direct link to Address Derivation") Aztec addresses are **deterministic** - they can be computed before deployment. An address is derived from: ``` Address = hash( public_keys_hash, // All the account's public keys partial_address // Contract deployment information ) ``` Where: * **public\_keys\_hash** = Combined hash of nullifier, incoming viewing, and other keys * **partial\_address** = Hash of the contract code and deployment parameters This deterministic addressing enables powerful features: * **Pre-funding**: Send funds to an address before the account is deployed * **Counterfactual deployment**: Interact with an account as if it exists, deploy it later * **Address recovery**: Recompute addresses from known keys #### Complete Address[​](#complete-address "Direct link to Complete Address") While an address alone is sufficient for receiving funds, spending notes requires a **complete address** which includes: * All the user's public keys (nullifier, incoming viewing, etc.) * The partial address (contract deployment information) * The contract address itself The complete address proves that the nullifier key inside the address is correct, enabling the user to spend their notes. ## The Entrypoint Pattern[​](#the-entrypoint-pattern "Direct link to The Entrypoint Pattern") The entrypoint is the gateway to your account. When someone wants to execute a transaction from your account, they call the entrypoint with a payload describing what to do. ### Transaction Flow[​](#transaction-flow "Direct link to Transaction Flow") Here's how a transaction flows through an account: ### Non-Standard Entrypoints[​](#non-standard-entrypoints "Direct link to Non-Standard Entrypoints") The beauty of account abstraction is that not every contract needs authentication. Some contracts can have **permissionless entrypoints**. For example, a lottery contract where anyone can trigger the payout: * No authentication required * Anyone can call the function * The contract itself handles the logic and constraints This pattern is useful for: * **Automated operations**: Keepers can trigger time-based actions * **Public goods**: Anyone can advance the state of a protocol * **Gasless transactions**: Users don't need to hold fee tokens ## Account Lifecycle[​](#account-lifecycle "Direct link to Account Lifecycle") ### 1. Pre-deployment (Counterfactual State)[​](#1-pre-deployment-counterfactual-state "Direct link to 1. Pre-deployment (Counterfactual State)") Before deployment, an account exists in a **counterfactual state**: * The address can be computed deterministically * Can receive funds (notes can be encrypted to the address) * Cannot send transactions (no code deployed) ### 2. Deployment[​](#2-deployment "Direct link to 2. Deployment") Deploying an account involves: 1. Submitting the account contract code 2. Registering in the contract instance registry 3. Paying deployment fees (either self-funded or sponsored) See [Creating Accounts](/developers/docs/aztec-js/how_to_create_account.md) for code examples. ### 3. Initialization[​](#3-initialization "Direct link to 3. Initialization") Accounts can be initialized for different purposes: * **Private-only**: Just needs initialization, no public deployment * **Public interaction**: Requires both initialization and deployment The contract is initialized when one of the functions marked with the `#[initializer]` annotation has been invoked. Multiple functions in the contract can be marked as initializers. Contracts may have functions that skip the initialization check (marked with `#[noinitcheck]`). note Account deployment and initialization are not required to receive notes. The user address is deterministically derived, so funds can be sent to an account that hasn't been deployed yet. ### 4. Active Use[​](#4-active-use "Direct link to 4. Active Use") Once deployed and initialized, accounts can: * Send and receive private notes * Interact with public and private functions * Authorize actions via authentication witnesses * Pay fees in various ways ## Authentication Witnesses (AuthWit)[​](#authentication-witnesses-authwit "Direct link to Authentication Witnesses (AuthWit)") Aztec replaces Ethereum's dangerous "infinite approval" pattern with **Authentication Witnesses** - a more secure authorization scheme where users sign specific actions rather than granting blanket permissions. Instead of approving unlimited token transfers, users authorize exact actions with precise parameters. This eliminates persistent security risks while enabling better UX through batched operations. For detailed information about how AuthWit works in both private and public contexts, see the [Authentication Witness documentation](/developers/docs/foundational-topics/advanced/authwit.md). ## Transaction Abstractions[​](#transaction-abstractions "Direct link to Transaction Abstractions") Aztec abstracts two critical components of transactions that are typically rigid in other blockchains: nonces and fees. ### Nonce Abstraction[​](#nonce-abstraction "Direct link to Nonce Abstraction") Unlike Ethereum where nonces are sequential counters enforced by the protocol, Aztec lets account contracts implement their own replay protection. **Different nonce strategies possible:** | Strategy | How it Works | Benefits | | ------------------------------ | ------------------------------------------ | ---------------------------------- | | **Sequential** (like Ethereum) | Must use nonces in order (1, 2, 3...) | Simple, predictable ordering | | **Unordered** (like Bitcoin) | Any unused nonce is valid | Parallel transactions, no blocking | | **Time-windowed** | Nonces valid only in specific time periods | Automatic expiration, batching | | **Merkle-tree based** | Nonces from a pre-committed set | Privacy, batch pre-authorization | This enables: * **Parallel transactions**: No need to wait for one tx to complete before sending another * **Custom cancellation**: Define your own rules for replacing/cancelling transactions * **Flexible ordering**: Implement priority queues, batching, or time-based ordering ### Fee Abstraction[​](#fee-abstraction "Direct link to Fee Abstraction") Unlike traditional blockchains where users must pay fees in the native token, Aztec accounts can implement custom fee payment logic: * **Pay with any token** through integrated swaps * **Sponsored transactions** where applications pay for users * **Meta-transactions** with relayer networks * **Custom payment models** like subscriptions or paymasters This flexibility is crucial for user onboarding and enables gasless experiences. For detailed information about fee mechanics and payment options, see the [Fees documentation](/developers/docs/foundational-topics/fees.md). ## Account Contracts[​](#account-contracts "Direct link to Account Contracts") Aztec provides several account contract implementations: * **Schnorr Account** - Single-key account using Schnorr signatures (default) * **ECDSA Account** - Single-key account using ECDSA signatures (secp256k1 or secp256r1) These implement the simple signature pattern where a single key controls the account. The flexibility of account abstraction also enables more complex patterns like multisig, social recovery, or session keys - these can be implemented as custom account contracts. ## Summary[​](#summary "Direct link to Summary") Aztec's native account abstraction means every account is a smart contract with customizable authentication, fee payment, and authorization logic. Because validation happens client-side with ZK proofs, complex validation doesn't increase network costs - enabling patterns that aren't practical on other blockchains. --- # Keys ## Account Keys in Aztec[​](#account-keys-in-aztec "Direct link to Account Keys in Aztec") Unlike traditional blockchains where accounts use a single key pair, Aztec accounts use **multiple specialized key pairs**, each serving a distinct cryptographic purpose. This separation is fundamental to Aztec's privacy model and enables powerful security features that aren't possible with single-key systems. ## Why Multiple Keys?[​](#why-multiple-keys "Direct link to Why Multiple Keys?") The separation of keys in Aztec serves critical purposes: * **Privacy isolation**: Different keys for different operations prevent correlation attacks * **Selective disclosure**: Share viewing access without compromising spending ability * **Damage limitation**: If one key is compromised, others remain secure * **Flexible authorization**: Choose any authentication method without affecting core protocol keys * **Per-application security**: Keys can be scoped to specific contracts to minimize exposure This multi-key architecture is what enables Aztec to provide strong privacy guarantees while maintaining flexibility and security. ## Key Types[​](#key-types "Direct link to Key Types") Each Aztec account uses multiple key pairs: | Key Type | Purpose | Protocol Managed | Rotatable | | ------------------------- | ----------------------------------------- | ---------------- | --------- | | **Nullifier Keys** | Spending notes (destroying private state) | Yes | No | | **Incoming Viewing Keys** | Decrypting received notes | Yes | No | | **Signing Keys** | Transaction authorization | No (app-defined) | Yes | Protocol keys (nullifier and incoming viewing) are embedded into the protocol and cannot be changed once an account is created. The signing key is abstracted to the account contract developer, allowing complete flexibility in authentication methods. ### Nullifier Keys[​](#nullifier-keys "Direct link to Nullifier Keys") **Purpose**: Spending notes (private state consumption) Nullifier keys enable spending private notes. When using a note (like spending a token), the spender must prove they have the right to nullify it - essentially marking it as "spent" without revealing which note is being spent. **How it works:** 1. Each account has a master nullifier key pair (`Npk_m`, `nhk_m`) 2. For each application, an **app-siloed** key is derived: `nhk_app = hash(nhk_m, app_contract_address)` 3. To spend a note, compute its nullifier using the note hash and app-siloed key 4. The protocol verifies the app-siloed key comes from your master key and that your master public key is in your address This ensures only the rightful owner can spend notes, while the app-siloing provides additional security isolation between contracts. tip This last point could be confusing for most developers: how could a protocol verify a secret key is derived from another secret key without knowing it? Well, *you* make that derivation, generating a ZK proof for it. The protocol just verifies that ZK proof! #### Accessing nullifier keys in code[​](#accessing-nullifier-keys-in-code "Direct link to Accessing nullifier keys in code") The nullifier hiding key (`nhk`) — sometimes referred to in older documentation as the "nullifier secret key" (`nsk`) — is the secret scalar used to compute nullifiers. You should **never** derive or construct this key manually. Use the framework-provided functions: | Context | Function | Import / Access | | ----------------------- | ------------------------------------------- | ----------------------------------------------------------------------- | | Private (constrained) | `context.request_nhk_app(owner_npk_m_hash)` | Called on `&mut PrivateContext` | | Unconstrained | `get_nhk_app(owner_npk_m_hash)` | `use aztec::keys::getters::get_nhk_app` | | TypeScript (master key) | `deriveMasterNullifierHidingKey(secretKey)` | `import { deriveMasterNullifierHidingKey } from '@aztec/aztec.js/keys'` | | TypeScript (app-siloed) | `computeAppNullifierHidingKey(nhkM, app)` | `import { computeAppNullifierHidingKey } from '@aztec/aztec.js/keys'` | To get the owner's master nullifier public key hash (needed as input): ``` let owner_npk_m_hash = get_public_keys(owner).npk_m.hash(); ``` warning Do not compute nullifier keys by hand or derive custom blinding factors. The protocol kernel validates that `nhk_app` derives correctly from the master key — a hand-rolled value will fail verification. ### Incoming Viewing Keys[​](#incoming-viewing-keys "Direct link to Incoming Viewing Keys") **Purpose**: Receiving and decrypting private notes Incoming viewing keys enable private information to be shared with recipients. The sender uses the recipient's public viewing key (`Ivpk`) to encrypt notes, and the recipient uses their secret viewing key (`ivsk`) to decrypt them. **The encryption flow:** This uses elliptic curve Diffie-Hellman: both parties compute the same shared secret `S`, but only the recipient has the private key needed to decrypt. ### Signing Keys[​](#signing-keys "Direct link to Signing Keys") **Purpose**: Transaction authorization (optional, application-defined) Unlike nullifier and incoming viewing keys which are protocol-mandated, signing keys are **completely abstracted** - thanks to [native account abstraction](/developers/docs/foundational-topics/accounts.md), any authorization method can be implemented: * **Signature-based**: ECDSA, Schnorr, BLS, multi-signature * **Biometric**: Face ID, fingerprint * **Web2 credentials**: Google OAuth, passkeys * **Custom logic**: Time locks, spending limits, multi-party authorization **Traditional signature approach:** When using signatures, the account contract validates the signature against a stored public key. Here's an example from the Schnorr account contract: is\_valid\_impl ``` // Load public key from storage let storage = Storage::init(context); let public_key = storage.signing_public_key.get_note(); // Safety: The witness is only used as a "magical value" that makes the signature verification below pass. // Hence it's safe. let signature: [u8; 64] = unsafe { get_auth_witness_as_bytes(outer_hash) }; let pub_key = std::embedded_curve_ops::EmbeddedCurvePoint { x: public_key.x, y: public_key.y }; // Verify signature of the payload bytes schnorr::verify_signature(pub_key, signature, outer_hash.to_be_bytes::<32>()) ``` > [Source code: noir-projects/noir-contracts/contracts/account/schnorr\_account\_contract/src/main.nr#L72-L84](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/account/schnorr_account_contract/src/main.nr#L72-L84) The flexibility of signing key storage and rotation is entirely up to your account contract implementation. ## Address Derivation[​](#address-derivation "Direct link to Address Derivation") Your Aztec address is deterministically computed from your public keys and account contract. This enables anyone to encrypt notes to your address without needing additional information. ![]() ``` pre_address = hash(public_keys_hash, partial_address) where: public_keys_hash = hash(Npk_m, Ivpk_m, Ovpk_m, Tpk_m) partial_address = hash(contract_class_id, salted_initialization_hash) contract_class_id = hash(artifact_hash, fn_tree_root, public_bytecode_commitment) salted_initialization_hash = hash(deployer_address, salt, constructor_hash) ``` The final address is derived as `address = (pre_address * G + Ivpk_m).x` - only the x-coordinate of the resulting elliptic curve point. This derivation ensures: * Your address is deterministic (can be computed before deployment) * Keys and contract code are cryptographically bound to the address * The address proves ownership of the nullifier key needed to spend notes note The `Ovpk` (outgoing viewing key) and `Tpk` (tagging key) exist in the protocol's `PublicKeys` struct but are not currently used. They're reserved for future protocol upgrades. ## Key Management[​](#key-management "Direct link to Key Management") ### Key Generation and Derivation[​](#key-generation-and-derivation "Direct link to Key Generation and Derivation") Protocol keys (nullifier and incoming viewing) are automatically generated by the [Private Execution Environment (PXE)](/developers/docs/foundational-topics/pxe.md) when creating an account. The PXE handles: * Initial key pair generation * App-siloed key derivation * Secure key storage and oracle access * Key material never leaves the client All keys use elliptic curve cryptography on the Grumpkin curve: * Secret keys are scalars * Public keys are elliptic curve points (secret × generator point) Signing keys are application-defined and managed by your account contract logic. ### App-Siloed Keys[​](#app-siloed-keys "Direct link to App-Siloed Keys") Nullifier keys are **app-siloed** - scoped to each contract that uses them. This provides crucial security isolation: **How it works:** ``` nhk_app = hash(nhk_m, app_contract_address) ``` **Security benefits:** 1. **Damage containment**: If a nullifier key for one app leaks, other apps remain secure 2. **Privacy preservation**: Activity in different apps cannot be correlated via nullifier keys ### Key Rotation[​](#key-rotation "Direct link to Key Rotation") **Protocol keys (nullifier, incoming viewing):** Cannot be rotated. They are embedded in the address, which is immutable. If compromised, a new account must be deployed. **Signing keys:** Fully rotatable, depending on the account contract implementation. Options include: * Change keys on a schedule * Rotate after suspicious activity * Implement time-delayed rotation for security ## Summary[​](#summary "Direct link to Summary") Aztec's multi-key architecture is fundamental to its privacy and security model: | Key Type | Purpose | App-Siloed | Rotatable | Managed By | | -------------------- | ------------------------- | ---------- | --------- | -------------- | | **Nullifier** | Spend notes | Yes | No | Protocol (PXE) | | **Incoming Viewing** | Decrypt received notes | No | No | Protocol (PXE) | | **Signing** | Transaction authorization | N/A | Yes | Application | **Key takeaways:** * **Separation enables privacy**: Different keys for different operations prevent correlation and limit damage from compromise * **App-siloing adds security**: Per-contract nullifier keys isolate risk * **Flexibility in authorization**: Signing keys are completely abstracted - use any authentication method * **Protocol keys are permanent**: Nullifier and viewing keys are embedded in your address and cannot be changed * **Client-side security**: All key material is generated and managed in the PXE, never exposed to the network This architecture allows Aztec to provide strong privacy guarantees while maintaining the flexibility needed for various security models and use cases. --- # Authentication Witness (Authwit) Authentication Witness is a scheme for authenticating actions on Aztec, allowing users to authorize third-parties (protocols or other users) to execute actions on their behalf. For a video walkthrough of how authwits work, including both the private and public flows, watch this explainer (find more on the [video lessons](/developers/docs/resources/video_lessons.md) page): [How Authorization Works on Aztec](https://www.youtube-nocookie.com/embed/VRZVOCdjGZ4) ## Summary[​](#summary "Direct link to Summary") * **Authwits authorize specific actions**, not blanket allowances like ERC20 approvals * **Two-level hash structure**: inner hash (caller, selector, args) wrapped in message hash (consumer, chain\_id, version, inner\_hash) * **Private authwits** are verified via static calls to the account contract, with witnesses provided through oracles * **Public authwits** use a shared registry where authorizations are stored and consumed * **Single-use enforcement** through nullifiers prevents replay attacks * **Implementation**: Use the `#[authorize_once]` macro in your contracts (see [implementation guide](/developers/docs/aztec-nr/framework-description/authentication_witnesses.md)) ## Background[​](#background "Direct link to Background") In traditional EVM contracts, users authorize third-party actions through `approve` (setting allowances) or `permit` (signed approvals). Both approaches have drawbacks: infinite approvals create security risks, two-transaction flows hurt UX, and smart contract wallets struggle with signature-based permits. Aztec's private state model makes traditional approvals even more problematic. Even if you approve an allowance, the recipient can't spend your private tokens without knowing the note secrets. See [Hybrid State model](/developers/docs/foundational-topics/state_management.md) and [keys](/developers/docs/foundational-topics/accounts/keys.md) for more on private state. Authwits solve this by authorizing specific actions rather than blanket allowances, with verification happening through account contracts. ## How authwits work[​](#how-authwits-work "Direct link to How authwits work") Since private execution happens on the user's device, we can use oracles to provide authorization data mid-execution. The user provides a "witness" (proof of authorization) that the account contract validates. Witness vs signature We use "witness" instead of "signature" because authorization doesn't require a cryptographic signature. Depending on the account contract implementation, it could be a password or other mechanism. ### Hash structure[​](#hash-structure "Direct link to Hash structure") Authwits use a two-level hash structure: **Inner hash** encodes the specific action being authorized: ``` inner_hash = H(caller, selector, args_hash) ``` **Message hash** wraps the inner hash with context to prevent cross-chain replay attacks: ``` message_hash = H(consumer, chain_id, version, inner_hash) ``` Where * `caller` is the address attempting the action (e.g., a DeFi contract) * `selector` is the function selector being called * `args_hash` is the hash of the function arguments * `consumer` is the contract verifying the authorization (e.g., the token contract) * `chain_id` and `version` prevent cross-chain replay attacks **Example:** Authorizing a DeFi contract to transfer tokens: ``` inner_hash = H(defi, transfer_selector, H(alice_account, defi, 1000)); message_hash = H(token, chain_id, version, inner_hash); ``` This reads as "defi is allowed to call the token's transfer function with arguments (alice\_account, defi, 1000) on this specific chain". ### Private authwit flow[​](#private-authwit-flow "Direct link to Private authwit flow") In private execution, the Token contract asks Alice's account contract to verify the authwit. The account contract requests the witness from Alice via an oracle, validates it, and returns the result. Static calls for security The authwit verification uses a static call to the account contract. This prevents the account from re-entering the flow and modifying state during verification. ### Public authwit flow[​](#public-authwit-flow "Direct link to Public authwit flow") In public execution, oracles aren't available since the sequencer runs the code. Instead, authorizations are stored in a shared registry before use. The registry approach has a gas optimization: if authorization is set and consumed in the same transaction, the state changes cancel out, saving gas. Why use Auth Registry for signatures in public? ECDSA signature verification is not directly available in public functions due to AVM limitations. The public authwit flow above is the recommended pattern: verify signatures in private, store approvals in the Auth Registry, and consume them in public. See [AVM Cryptographic Compatibility](/developers/docs/foundational-topics/advanced/circuits/avm_compatibility.md) for more details. ### Replay prevention[​](#replay-prevention "Direct link to Replay prevention") Each authwit can only be used once. The consuming contract emits a nullifier for the action, preventing reuse. This is similar to how notes work. To allow the same action multiple times (e.g., repeated transfers of the same amount), include a nonce in the arguments: ``` inner_hash = H(defi, transfer_selector, H(alice_account, defi, 1000, nonce)); ``` The account contract cannot emit the nullifier (it's called via static call), so the consuming contract handles this. The authwit library manages this automatically. ### Cancelling authwits[​](#cancelling-authwits "Direct link to Cancelling authwits") You can cancel an authwit before it's used by emitting its nullifier directly. This invalidates the authwit without executing the authorized action: ``` fn cancel_authwit(inner_hash: Field) { let on_behalf_of = self.msg_sender(); let nullifier = compute_authwit_nullifier(on_behalf_of, inner_hash); self.context.push_nullifier(nullifier); } ``` ## Differences from ERC20 approvals[​](#differences-from-erc20-approvals "Direct link to Differences from ERC20 approvals") | Aspect | ERC20 Approve | Authwit | | -------------- | ------------------------------- | -------------------------- | | Scope | Blanket allowance | Specific action | | User awareness | Often unclear amounts | Exact action visible | | Revocation | Requires transaction | Can cancel with nullifier | | Private state | Cannot work (need note secrets) | Works via account contract | Private authwits and note secrets While authwits authorize a contract to perform an action, spending private notes still requires knowledge of the note secrets. For private tokens, the note owner must be involved in the transaction—they cannot simply give another user an authwit and have that user spend the notes independently. ## Use cases[​](#use-cases "Direct link to Use cases") Authwits work for any function requiring third-party authorization: * Token transfers and burns * DeFi deposits and withdrawals * Governance voting * Bridge operations (public to private transfers) * Any contract interaction requiring user approval ## Implementation[​](#implementation "Direct link to Implementation") Use the `#[authorize_once]` macro to add authwit verification to your contract functions: ``` #[authorize_once("from", "authwit_nonce")] #[external("private")] fn transfer_in_private( from: AztecAddress, to: AztecAddress, amount: u128, authwit_nonce: Field, ) { // Transfer logic here } ``` The macro handles authwit verification and nullifier emission automatically. For complete implementation details, see the [developer documentation](/developers/docs/aztec-nr/framework-description/authentication_witnesses.md). --- # Circuits Central to Aztec's operations are 'circuits' derived both from the core protocol and the developer-written Aztec.nr contracts. The core circuits enhance privacy by adding additional security checks and preserving transaction details - a characteristic Ethereum lacks. On this page, you’ll learn a bit more about these circuits and their integral role in promoting secure and efficient transactions within Aztec's privacy-centric framework. ## Motivation[​](#motivation "Direct link to Motivation") In Aztec, circuits come from two sources: 1. Core protocol circuits 2. User-written circuits (written as Aztec.nr Contracts and deployed to the network) This page focuses on the core protocol circuits. These circuits check that the rules of the protocol are being adhered to. When a function in an Ethereum smart contract is executed, the EVM performs checks to ensure that Ethereum's transaction rules are being adhered-to correctly. Stuff like: * "Does this tx have a valid signature?" * "Does this contract address contain deployed code?" * "Does this function exist in the requested contract?" * "Is this function allowed to call this function?" * "How much gas has been paid, and how much is left?" * "Is this contract allowed to read/update this state variable?" * "Perform the state read / state write" * "Execute these opcodes" All of these checks have a computational cost, for which users are charged gas. Many existing L2s move this logic offchain, as a way of saving their users gas costs, and as a way of increasing tx throughput. zk-Rollups, in particular, move these checks offchain by encoding them in zk-S(N/T)ARK circuits. Rather than paying a committee of Ethereum validators to perform the above kinds of checks, L2 users instead pay a sequencer to execute these checks via the circuit(s) which encode them. The sequencer can then generate a zero-knowledge proof of having executed the circuit(s) correctly, which they can send to a rollup contract on Ethereum. The Ethereum validators then verify this zk-S(N/T)ARK. It often turns out to be much cheaper for users to pay the sequencer to do this, than to execute a smart contract on Ethereum directly. But there's a problem. Ethereum (and the EVM) doesn't have a notion of privacy. * There is no notion of a private state variable in the EVM. * There is no notion of a private function in the EVM. So users cannot keep private state variables' values private from Ethereum validators, nor from existing (non-private) L2 sequencers. Nor can users keep the details of which function they've executed private from validators or sequencers. How does Aztec add privacy? Well, we just encode *extra* checks in our zk-Rollup's zk-SNARK circuits! These extra checks introduce the notions of private state and private functions, and enforce privacy-preserving constraints on every transaction being sent to the network. In other words, since neither the EVM nor other rollups have rules for how to preserve privacy, we've written a new rollup which introduces such rules, and we've written circuits to enforce those rules! What kind of extra rules / checks does a rollup need, to enforce notions of private states and private functions? Stuff like: * "Perform state reads and writes using new tree structures which prevent tx linkability" (see [indexed merkle tree](/developers/docs/foundational-topics/advanced/storage/indexed_merkle_tree.md). * "Hide which function was just executed, by wrapping it in a zk-snark" * "Hide all functions which were executed as part of this tx's stack trace, by wrapping the whole tx in a zk-snark" ## Aztec core protocol circuits[​](#aztec-core-protocol-circuits "Direct link to Aztec core protocol circuits") So what kinds of core protocol circuits does Aztec have? ### Kernel, Rollup, and Squisher Circuits[​](#kernel-rollup-and-squisher-circuits "Direct link to Kernel, Rollup, and Squisher Circuits") The specs of these have recently been updated. Eg for squisher circuits since Honk and Goblin Plonk schemes are still being improved! But we'll need some extra circuit(s) to squish a Honk proof (as produced by the Root Rollup Circuit) into a Standard Plonk or Fflonk proof, for cheap verification on Ethereum. --- # AVM Cryptographic Compatibility Private and public functions in Aztec use different execution models. Private functions compile to ACIR circuits and have access to the full Noir standard library. Public functions compile to AVM bytecode via the transpiler, which supports only a specific set of cryptographic operations. ## Compatibility Table[​](#compatibility-table "Direct link to Compatibility Table") The table below lists the low-level blackbox operations and whether they are available in the AVM. Higher-level Noir standard library functions (like `sha256::sha256_var`, `keccak256::keccak256`, `poseidon2::hash`, and `std::hash::pedersen_hash`) are built on these primitives and work in public functions when the underlying operations are supported. | Noir Primitive | Private (ACIR) | Public (AVM) | Notes | | --------------------------- | -------------- | ----------------- | ------------------------------------------- | | Poseidon2 Permutation | Supported | Supported | `POSEIDON2PERM` opcode | | Pedersen Hash / Commitment | Supported | Supported | Lowered to `ECADD` and `MSM` operations | | SHA-256 Compression | Supported | Supported | `SHA256COMPRESSION` opcode | | Keccak f1600 | Supported | Supported | `KECCAKF1600` opcode | | Embedded Curve Add | Supported | Supported | `ECADD` opcode (Grumpkin curve) | | Multi-Scalar Multiplication | Supported | Supported | Lowered to `TORADIXBE` + `ECADD` operations | | ToRadix | Supported | Supported | `TORADIXBE` opcode | | ECDSA secp256k1 | Supported | **Not supported** | Transpiler panics | | ECDSA secp256r1 | Supported | **Not supported** | Transpiler panics | | AES-128 Encrypt | Supported | **Not supported** | Transpiler panics | | Blake2s | Supported | **Not supported** | Transpiler panics | | Blake3 | Supported | **Not supported** | Transpiler panics | ## Why the Difference[​](#why-the-difference "Direct link to Why the Difference") Private functions are compiled to ACIR (Abstract Circuit Intermediate Representation), which supports the full set of Noir standard library blackbox functions. These are evaluated as part of the zk-SNARK proof generation on the user's device. Public functions are compiled to AVM bytecode via the transpiler. The AVM has a fixed instruction set, and each supported cryptographic operation must either have a dedicated opcode or be reducible to a sequence of supported opcodes. For example, multi-scalar multiplication has no dedicated opcode but is lowered to `TORADIXBE` and `ECADD` instructions. Operations that cannot be mapped to supported opcodes cannot be transpiled. ## What Error Will I See?[​](#what-error-will-i-see "Direct link to What Error Will I See?") If you use an unsupported blackbox function in a `#[external("public")]` function, the transpiler will panic at compile time with a message like: ``` Transpiler doesn't know how to process EcdsaSecp256k1 ``` where the final token is the name of the unsupported `BlackBoxOp` variant (e.g. `AES128Encrypt`, `Blake2s`, `Blake3`). ## Signature Verification in Public: Workarounds[​](#signature-verification-in-public-workarounds "Direct link to Signature Verification in Public: Workarounds") Since ECDSA signature verification is not available in public functions, use the **Authentication Registry** pattern: 1. Verify signatures in a **private** function (where all Noir primitives are available) 2. Store approval hashes in the **Auth Registry** (a shared public contract) 3. Consume the approvals in **public** functions This is exactly how public authwits work. See [Authentication Witnesses](/developers/docs/foundational-topics/advanced/authwit.md) for the full pattern. Schnorr signatures The [`noir-lang/schnorr`](https://github.com/noir-lang/schnorr) library implements Schnorr verification in pure Noir using embedded curve operations (ECADD, MSM), which are supported in the AVM. This means Schnorr verification may work in public functions. However, the standard Aztec account contracts only use Schnorr in private functions, and the recommended pattern remains verifying signatures in private via the Auth Registry. ## ISA Reference[​](#isa-reference "Direct link to ISA Reference") For the complete list of AVM opcodes, see the [AVM ISA Quick Reference](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/yarn-project/simulator/docs/avm/avm-isa-quick-reference.md). ## Related Pages[​](#related-pages "Direct link to Related Pages") * [Public Execution (AVM)](/developers/docs/foundational-topics/advanced/circuits/public_execution.md) – How the AVM executes public functions * [Authentication Witnesses](/developers/docs/foundational-topics/advanced/authwit.md) – The Auth Registry pattern for public authorization * [Call Types](/developers/docs/foundational-topics/call_types.md) – How private and public functions interact * [Private Kernel](/developers/docs/foundational-topics/advanced/circuits/private_kernel.md) – How private functions are processed --- # Private Kernel Circuit The private kernel circuit is executed by the user on their own device. This ensures private inputs remain private. note This is the only core protocol circuit that truly requires the "zero-knowledge" property. Other circuits use SNARKs for succinct verification, but don't need to hide witness data. The private kernel must hide: the contract function executed, the user's address, and the function's inputs and outputs. ## Overview[​](#overview "Direct link to Overview") The private kernel processes all private function calls in a transaction, accumulating their side effects (note hashes, nullifiers, logs, messages) and validation requests. It runs recursively—once per private function call—building up a proof that all private execution was correct. The kernel validates: * Proof of private function execution * Correct call context (caller, address, arguments) * Proper scoping of side effects to their originating contract * Uniqueness and ordering of side effect counters ## Kernel Phases[​](#kernel-phases "Direct link to Kernel Phases") The private kernel consists of five circuit types: ### Init[​](#init "Direct link to Init") The entry point for private kernel execution. It processes the first private function call in a transaction and validates: * The call matches the transaction request (origin, function, arguments) * The function is marked as private * No msg\_sender exists (first call has no caller) ### Inner[​](#inner "Direct link to Inner") Processes subsequent private function calls after init. Can chain multiple times as the call stack grows. It: * Verifies the previous kernel proof * Pops the next call from the private call stack * Validates the call matches its request * Appends new side effects to accumulated data ### Reset[​](#reset "Direct link to Reset") Can be called one or more times at any point after init and before tail/tail-to-public. Optimizes the accumulated data by: * Squashing transient note hash/nullifier pairs (where a note is created and nullified within the same transaction) along with their associated logs * Validating note hash and nullifier read requests against the state * Validating key validation requests This circuit reduces the data size before finalization. ### Tail[​](#tail "Direct link to Tail") The final circuit for **private-only** transactions (no public function calls). It: * Sorts remaining side effects * Converts accumulated data to rollup format * Produces output ready for rollup aggregation ### Tail to Public[​](#tail-to-public "Direct link to Tail to Public") The bridge circuit for transactions with both private and public execution. It: * Splits side effects into non-revertible and revertible arrays * Prepares data for public execution * Handles the transition from private to public phases ## Data Flow[​](#data-flow "Direct link to Data Flow") As the kernel processes each private call, it accumulates: | Data Type | Description | | -------------------- | --------------------------------------------------- | | Note hashes | Commitments to new private notes | | Nullifiers | Markers that invalidate notes or provide uniqueness | | L2 to L1 messages | Cross-chain messages to Ethereum | | Private logs | Encrypted event data | | Public call requests | Queued public function calls | Each item is scoped with the contract address that emitted it, ensuring proper attribution. ## Performance Impact[​](#performance-impact "Direct link to Performance Impact") The kernel circuits add significant overhead to every transaction. Understanding this overhead is important when designing contracts and profiling performance. ### Gate counts per phase[​](#gate-counts-per-phase "Direct link to Gate counts per phase") Consider a typical transaction where a user calls a single contract function. This actually involves **two** private function calls — the account entrypoint (e.g. `SchnorrAccount:entrypoint`) and your contract function — so the kernel processes both: ``` Account entrypoint: 22,000 gates (1st private call → processed by init) Your function: 14,000 gates (2nd private call → processed by inner) private_kernel_init: ~46,000 gates private_kernel_inner: ~101,000 gates private_kernel_reset: ~200,000 gates private_kernel_tail: ~44,000 gates ───────────────────────────────────── Total transaction: ~427,000 gates ``` The init circuit handles the first private function call (the account entrypoint), and inner handles each subsequent one. The exact kernel gate counts vary depending on the transaction's complexity (number of note hashes, nullifiers, read requests, etc.), but the key takeaway is: **kernel overhead is substantial and scales with the number of private function calls**. Each additional private function call in a transaction adds at least one more kernel inner circuit (\~101k gates). This means that architectural decisions — like whether to inline logic into one function vs. splitting across multiple function calls — can have a significant impact on total proving time. Design tip When profiling shows high gate counts, consider whether you can reduce the number of distinct private function calls in your transaction. For example, inlining a verification step into the calling function saves an entire kernel fold (\~101k gates), even if it slightly increases the calling function's own gate count. Large circuits do not prevent transaction inclusion A contract with a high gate count is **not** uncallable. The only effect of a large circuit is that it takes longer to prove on the client. Your transaction will still be included in a block as long as it is valid by the time the proof is submitted. In practice, there are only two edge cases where slow proving could cause issues: 1. **Transaction expiry**: If proving takes so long (e.g. over an hour) that the transaction becomes invalid by the time you're done. This is unlikely for most use cases. 2. **Fee volatility**: If network fees increase rapidly while you're proving, your transaction may be underpriced by the time it's broadcast. This is similar to signing an Ethereum transaction and waiting before submitting it. You can mitigate this by overpaying for fees if rapid inclusion is a priority. One of the major benefits of Aztec is that private computation is proven client-side: you can do as much computation as you want in private functions — the network cost is the same regardless. For tools to measure these costs, see the [profiling guide](/developers/docs/aztec-nr/framework-description/advanced/how_to_profile_transactions.md). ## Related Pages[​](#related-pages "Direct link to Related Pages") * [Transactions](/developers/docs/foundational-topics/transactions.md) - How private kernel fits into transaction execution * [Public Execution](/developers/docs/foundational-topics/advanced/circuits/public_execution.md) - How public functions are executed by the AVM * [Profiling Transactions](/developers/docs/aztec-nr/framework-description/advanced/how_to_profile_transactions.md) - Measuring gate counts and identifying bottlenecks * [Writing Efficient Contracts](/developers/docs/aztec-nr/framework-description/advanced/writing_efficient_contracts.md) - Optimization strategies --- # Public Execution (AVM) Public function execution in Aztec is handled by the **Aztec Virtual Machine (AVM)**. Unlike private execution (which runs on user devices), public execution runs on the sequencer's infrastructure where access to current state is required. note Unlike the private kernel which runs recursively for each private call, **there is no "public kernel" circuit**. The AVM executes all public functions for a transaction in a single proof. The term "public kernel" is sometimes used colloquially to refer to the AVM's role in public execution. ## Overview[​](#overview "Direct link to Overview") The AVM processes public call requests that were queued during private execution. It operates on the current state of the public data tree, note hash tree, and nullifier tree—state that only the sequencer knows at execution time. For transactions containing public functions, the execution flow is: 1. **Private Kernel** - Processes private functions, queues public call requests 2. **Hiding Kernel** - Bridges private output to public phase 3. **AVM** - Executes all public functions, produces accumulated data 4. **Rollup Circuits** - Validates proofs and includes in block ## Supported Cryptographic Operations[​](#supported-cryptographic-operations "Direct link to Supported Cryptographic Operations") The AVM supports Poseidon2, Pedersen, SHA-256, Keccak, and Grumpkin curve operations (embedded curve add, multi-scalar multiplication). ECDSA signature verification, AES-128, Blake2s, and Blake3 are not available in public functions. warning If your contract uses unsupported Noir blackbox functions in a public function, transpilation will fail at compile time. See [AVM Cryptographic Compatibility](/developers/docs/foundational-topics/advanced/circuits/avm_compatibility.md) for the full compatibility table and workarounds. ## Execution Phases[​](#execution-phases "Direct link to Execution Phases") The AVM executes public functions in three distinct phases: | Phase | Revertible | Purpose | | ------------- | ---------- | ----------------------------------------------- | | **Setup** | No | Non-revertible initialization (fee preparation) | | **App Logic** | Yes | Main application logic | | **Teardown** | Yes | Fee payment finalization | This phased approach enables atomic fee payment even if the main transaction logic reverts. The setup phase cannot be reverted, ensuring the sequencer receives payment. ## Inputs and Outputs[​](#inputs-and-outputs "Direct link to Inputs and Outputs") ### Inputs from Private Execution[​](#inputs-from-private-execution "Direct link to Inputs from Private Execution") The AVM receives from the private phase: * **Public call requests**: Setup, app logic, and teardown function calls * **Non-revertible accumulated data**: Note hashes, nullifiers, L2-L1 messages from setup phase * **Revertible accumulated data**: Note hashes, nullifiers, L2-L1 messages that can be reverted * **Gas settings**: Limits for execution and teardown * **Fee payer**: Address responsible for transaction fees ### Outputs[​](#outputs "Direct link to Outputs") After execution, the AVM produces: | Output | Description | | ------------------ | ------------------------------------------ | | Note hashes | Combined private + public note commitments | | Nullifiers | Combined private + public nullifiers | | L2-L1 messages | Cross-chain messages to Ethereum | | Public logs | Event data from public execution | | Public data writes | State updates to the public data tree | | End tree snapshots | Final state of all trees after execution | | Transaction fee | Computed fee based on gas consumed | | Reverted flag | Whether app logic phase reverted | ## State Transitions[​](#state-transitions "Direct link to State Transitions") The AVM validates state transitions by tracking tree snapshots: * **Start snapshots**: Tree roots before public execution * **End snapshots**: Tree roots after all public functions complete These snapshots are validated in the rollup circuits to ensure continuity across transactions in a block. ## Related Pages[​](#related-pages "Direct link to Related Pages") * [AVM Cryptographic Compatibility](/developers/docs/foundational-topics/advanced/circuits/avm_compatibility.md) – Which Noir primitives work in public functions * [Private Kernel](/developers/docs/foundational-topics/advanced/circuits/private_kernel.md) – How private functions are processed * [Call Types](/developers/docs/foundational-topics/call_types.md) – How private and public functions interact * [State Management](/developers/docs/foundational-topics/state_management.md) – How public and private state works --- # Rollup Circuits The rollup circuits compress thousands of transactions into a single SNARK proof for verification on Ethereum. They aggregate proofs from private kernel and AVM execution, validate state transitions, and produce the final epoch proof submitted to L1. note The rollup circuits use a "binary tree of proofs" topology. This allows proof generation to be parallelized across prover instances—each layer of the tree can be computed in parallel, or subtrees can be distributed to different provers. ## Circuit Hierarchy[​](#circuit-hierarchy "Direct link to Circuit Hierarchy") Rollup circuits operate at four levels, each producing outputs consumed by the next: | Level | Circuits | Input | Output | | --------------- | ---------------------------------- | ------------------- | ----------------------- | | **Transaction** | TX Base (Private/Public), TX Merge | Kernel proofs | Transaction rollup data | | **Block** | Block Root, Block Merge | Transaction rollups | Block rollup data | | **Checkpoint** | Checkpoint Root, Checkpoint Merge | Block rollups | Checkpoint data | | **Epoch** | Root Rollup | Checkpoint rollups | Final epoch proof | ## Transaction Level[​](#transaction-level "Direct link to Transaction Level") ### TX Base Rollups[​](#tx-base-rollups "Direct link to TX Base Rollups") Process individual transactions from kernel proofs: * **TX Base Private** - Processes transactions with only private execution. Validates the private kernel proof, updates tree snapshots (note hash, nullifier), and accumulates fees and mana usage. * **TX Base Public** - Processes transactions that include public (AVM) execution. Validates the AVM proof, which has already performed tree updates and fee/mana accumulation during public execution. ### TX Merge Rollup[​](#tx-merge-rollup "Direct link to TX Merge Rollup") Merges pairs of transaction rollup proofs in binary fashion. Can chain recursively to aggregate many transactions into a single proof. Validates proof correctness and consecutive transaction ordering. ## Block Level[​](#block-level "Direct link to Block Level") ### Block Root Rollups[​](#block-root-rollups "Direct link to Block Root Rollups") Transition from transaction-level to block-level outputs. Several variants handle different scenarios: * **Block Root First** - First block of a checkpoint (validates parity root and L1-to-L2 tree) * **Block Root** - Subsequent blocks in a checkpoint * **Block Root Single TX** - Optimized variant for single-transaction blocks * **Block Root Empty TX First** - Handles empty blocks These circuits update the archive tree, compute block headers, and accumulate L2-to-L1 message hashes. ### Block Merge Rollup[​](#block-merge-rollup "Direct link to Block Merge Rollup") Merges pairs of block rollup proofs within a checkpoint. Validates archive continuity and state consistency between blocks. ## Checkpoint Level[​](#checkpoint-level "Direct link to Checkpoint Level") ### Checkpoint Root Rollups[​](#checkpoint-root-rollups "Direct link to Checkpoint Root Rollups") Transition from block-level to checkpoint-level outputs: * **Checkpoint Root** - Standard checkpoint containing multiple blocks * **Checkpoint Root Single Block** - Optimized for single-block checkpoints These circuits validate previous block headers, compute blob commitments, and accumulate fee recipients. ### Checkpoint Merge Rollup[​](#checkpoint-merge-rollup "Direct link to Checkpoint Merge Rollup") Merges pairs of checkpoint proofs. Validates checkpoint continuity and blob accumulator consistency. ### Checkpoint Padding[​](#checkpoint-padding "Direct link to Checkpoint Padding") A special circuit for epochs with only one checkpoint. Provides an empty right child for the binary tree structure. ## Epoch Level (Root Rollup)[​](#epoch-level-root-rollup "Direct link to Epoch Level (Root Rollup)") The final circuit that completes an epoch proof. It: * Merges two checkpoint rollup proofs * Validates epoch-level blob batching challenges * Produces the final `RootRollupPublicInputs` for L1 submission The root rollup output includes: * Previous and new archive roots * Checkpoint header hashes * Accumulated fees across all checkpoints * Final blob public inputs for data availability ## Flexible Tree Topology[​](#flexible-tree-topology "Direct link to Flexible Tree Topology") The architecture supports asymmetric "wonky trees" for efficiency: * Transactions can be grouped variably into blocks * Not all branches need the same depth * Single-element optimizations reduce proof overhead * Padding circuits handle partial epochs This flexibility allows sequencers to optimize proving costs based on actual workload. ## Related Pages[​](#related-pages "Direct link to Related Pages") * [Private Kernel](/developers/docs/foundational-topics/advanced/circuits/private_kernel.md) - How private function proofs are generated * [Public Execution](/developers/docs/foundational-topics/advanced/circuits/public_execution.md) - How the AVM produces public execution proofs --- # Indexed Merkle Tree (Nullifier Tree) ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") This page assumes familiarity with: * Merkle trees and membership proofs * The UTXO model for private state * Zero-knowledge proof concepts (circuits, constraints) ## Overview[​](#overview "Direct link to Overview") This page covers indexed merkle trees and how they improve nullifier tree performance in circuits, including: * Why nullifier trees are necessary * How indexed merkle trees work * Membership exclusion proofs * Batch insertions * Tradeoffs This content was presented to the [Privacy + Scaling Explorations team at the Ethereum Foundation](https://pse.dev/). [YouTube video player](https://www.youtube-nocookie.com/embed/x_0ZhUKtWSs?si=TmguEhgz4Gu07Dac) ## Primer on Nullifier Trees[​](#primer-on-nullifier-trees "Direct link to Primer on Nullifier Trees") Privacy in public blockchains requires a UTXO model. State is stored in encrypted UTXOs in merkle trees. Since updating state directly leaks information, we simulate updates by "destroying" old UTXOs and creating new ones, resulting in an append-only merkle tree. A classic merkle tree: ![Classic merkle tree structure showing leaf nodes hashed up to a root](/assets/ideal-img/normal-merkle-tree.568bc10.640.png) To destroy state, a "nullifier" tree stores deterministic values linked to notes in the append-only tree. This is typically implemented as a sparse Merkle Tree. A sparse merkle tree (not every leaf stores a value): ![Sparse merkle tree with empty leaves represented as zeros](/assets/ideal-img/sparse-merkle-tree.59ba475.640.png) To spend or modify a note in the private state tree, you must create a nullifier and prove it does not already exist in the nullifier tree. Since nullifier trees are modeled as sparse merkle trees, non-membership checks are conceptually trivial. Data is stored at the leaf index corresponding to its value. For example, in a sparse tree containing 2256 values, to prove non-membership of value 2128: * Prove tree\_values\[2128]=0 via a merkle membership proof (value does not exist) * Conversely, prove tree\_values\[2128]==1 to show the item exists ## Problems introduced by using Sparse Merkle Trees for Nullifier Trees[​](#problems-introduced-by-using-sparse-merkle-trees-for-nullifier-trees "Direct link to Problems introduced by using Sparse Merkle Trees for Nullifier Trees") While sparse Merkle Trees offer a simple solution, they have significant drawbacks. A sparse nullifier tree must have an index for e∈Fp​, which for the bn254 curve requires a depth of 254. A tree of depth 254 means 254 hashes per membership proof. Each nullifier insertion requires a non-membership check followed by an insertion—two trips from leaf to root. This results in 254×2 hashes per insertion. Since the tree is sparse, insertions are random and must be sequential, so hash count scales linearly with nullifier count. This causes constraint counts in rollup circuits to grow rapidly, leading to long proving times. ## Indexed Merkle Tree Constructions[​](#indexed-merkle-tree-constructions "Direct link to Indexed Merkle Tree Constructions") [This paper](https://eprint.iacr.org/2021/1263.pdf) (page 6) introduces indexed merkle trees, which enable efficient non-membership proofs. Each node stores a value v∈Fp​ and pointers to the leaf with the next higher value: leaf={v,inext​,vnext​}. Based on the tree's insertion rules, no leaves exist between the range (v,vnext​). The merkle tree forms a linked list of increasing values. Once inserted, a leaf's pointers can change but its nullifier value cannot. Since leaves are no longer positioned at (index==value), a deep tree is unnecessary—32 levels suffice. This improves insertions by approximately 8x (256/32). *The node that provides the non-membership check is called a "low nullifier".* The insertion protocol: 1. Look for a nullifier's corresponding low\_nullifier where: low\_nullifiernext\_value​>new\_nullifier > if new\_nullifier is the largest use the leaf: low\_nullifiernext\_value​==0 2. Perform membership check of the low nullifier. 3. Perform a range check on the low nullifier's value and next\_value fields: new\_nullifier​>low\_nullifiervalue​&&(new\_nullifier\ * If (low\_nullifiernext\_index​==0): * Special case, the low leaf is at the very end, so the new\_value must be higher than all values in the tree: * assert(low\_nullifiervalue​\ * assert(low\_nullifiervalue​\new\_valuevalue​) This provides significant performance improvement, but since the tree is not sparse, we can also perform batch insertions. ## Batch insertions[​](#batch-insertions "Direct link to Batch insertions") Since nullifiers are inserted deterministically (append-only), we can insert entire subtrees rather than appending nodes individually. However, for every node inserted, low nullifier pointers must be updated. This adds complexity when the low nullifier exists within the subtree being inserted. All impacted low nullifiers must be updated before the subtree insertion. Batch insertion in an append-only merkle tree: 1. Prove the target subtree consists of all empty values 2. Calculate the root of an empty subtree and perform an inclusion proof for this empty root 3. Recreate the subtree within the circuit 4. Use the same sibling path to get the new root after subtree insertion In the following example, a subtree of size 4 is inserted. The subtree is greyed out as "pending". **Legend**: * Green: New Inserted Value * Orange: Low Nullifier **Example** 1. Prepare to insert subtree \[35,50,60,15] ![Preparing to insert subtree with values 35, 50, 60, 15](/assets/ideal-img/subtree-insert-1.04d9287.640.png) 2. Update low nullifier for new nullifier 35 ![Updating low nullifier for value 35](/assets/ideal-img/subtree-insert-2.92b8e75.640.png) 3. Update low nullifier for new nullifier 50 (the low nullifier exists within the pending insertion subtree) ![Updating low nullifier for value 50 from pending subtree](/assets/ideal-img/subtree-insert-3.e86f005.640.png) 4. Update low nullifier for new nullifier 60 ![Updating low nullifier for value 60](/assets/ideal-img/subtree-insert-4.9375e53.640.png) 5. Update low nullifier for new nullifier 15 ![Updating low nullifier for value 15](/assets/ideal-img/subtree-insert-5.0bc9b9a.640.png) 6. Update pointers for new nullifier 15 ![Updating pointers for value 15](/assets/ideal-img/subtree-insert-6.169ec0e.640.png) 7. Insert subtree ![Final state after subtree insertion](/assets/ideal-img/subtree-insert-7.740d73d.640.png) ### Performance gains from subtree insertion[​](#performance-gains-from-subtree-insertion "Direct link to Performance gains from subtree insertion") Sparse nullifier tree insertions require 1 non-membership check (254 hashes) and 1 insertion (254 hashes). For 4 values: 2032 hashes. In a depth-32 indexed tree, each subtree insertion costs 1 non-membership check (32 hashes) and 1 pointer update (32 hashes) per value, plus subtree construction (\~67 hashes). Total: 327 hashes—a significant efficiency gain. *Range check constraint costs are negligible compared to hash costs.* ## Performing subtree insertions in a circuit context[​](#performing-subtree-insertions-in-a-circuit-context "Direct link to Performing subtree insertions in a circuit context") A challenge arises when the low nullifier for a value exists within the subtree being inserted. In this case, you cannot perform a non-membership check against the tree root because the leaf needed for non-membership has not yet been inserted. These are called "pending" insertions. **Circuit Inputs** * `new_nullifiers`: `fr[]` * `low_nullifier_leaf_preimages`: `tuple of {value: fr, next_index: fr, next_value: fr}` * `low_nullifier_membership_witnesses`: A sibling path and a leaf index of low nullifier * `current_nullifier_tree_root`: Current root of the nullifier tree * `next_insertion_index`: `fr`, the tip of the nullifier tree * `subtree_insertion_sibling_path`: A sibling path to check the subtree against the root If the low nullifier does not yet exist in the tree, membership checks fail and no non-membership proof can be produced. To handle this, the circuit must track all values pending insertion: * If `low_nullifier_membership_witness` is invalid (all zeros or leaf index of -1), this indicates a pending low nullifier read request * Loop through all pending insertions to find one with value lower than the nullifier being inserted * If no matching pending insertion is found, the circuit is invalid Pseudocode with pending insertion handling: ``` auto empty_subtree_hash = SOME_CONSTANT_EMPTY_SUBTREE; auto pending_insertion_subtree = []; auto insertion_index = inputs.next_insertion_index; auto root = inputs.current_nullifier_tree_root; // Check nothing exists where we would insert our subtree assert(membership_check(root, empty_subtree_hash, insertion_index >> subtree_depth, inputs.subtree_insertion_sibling_path)); for (i in len(new_nullifiers)) { auto new_nullifier = inputs.new_nullifiers[i]; auto low_nullifier_leaf_preimage = inputs.low_nullifier_leaf_preimages[i]; auto low_nullifier_membership_witness = inputs.low_nullifier_membership_witnesses[i]; if (low_nullifier_membership_witness is garbage) { bool matched = false; // Search for the low nullifier within our pending insertion subtree for (j in range(0, i)) { auto pending_nullifier = pending_insertion_subtree[j]; if (pending_nullifier.is_garbage()) continue; if (pending_nullifier[j].value < new_nullifier && (pending_nullifier[j].next_value > new_nullifier || pending_nullifier[j].next_value == 0)) { // Found matching low nullifier matched = true; // Update pointers auto new_nullifier_leaf = { .value = new_nullifier, .next_index = pending_nullifier.next_index, .next_value = pending_nullifier.next_value } // Update pending subtree pending_nullifier.next_index = insertion_index; pending_nullifier.next_value = new_nullifier; pending_insertion_subtree.push(new_nullifier_leaf); break; } } // could not find a matching low nullifier in the pending insertion subtree assert(matched); } else { // Membership check for low nullifier assert(perform_membership_check(root, hash(low_nullifier_leaf_preimage), low_nullifier_membership_witness)); // Range check low nullifier against new nullifier assert(new_nullifier < low_nullifier_leaf_preimage.next_value || low_nullifier_leaf.next_value == 0); assert(new_nullifier > low_nullifier_leaf_preimage.value); // Update new nullifier pointers auto new_nullifier_leaf = { .value = new_nullifier, .next_index = low_nullifier_preimage.next_index, .next_value = low_nullifier_preimage.next_value }; // Update low nullifier pointers low_nullifier_preimage.next_index = next_insertion_index; low_nullifier_preimage.next_value = new_nullifier; // Update state vals for next iteration root = update_low_nullifier(low_nullifier, low_nullifier_membership_witness); pending_insertion_subtree.push(new_nullifier_leaf); } next_insertion_index += 1; } // insert subtree root = insert_subtree(root, inputs.next_insertion_index >> subtree_depth, pending_insertion_subtree); ``` ## Drawbacks[​](#drawbacks "Direct link to Drawbacks") While indexed merkle trees provide significant circuit performance improvements, they increase computation and storage requirements for nodes. Finding the "low nullifier" for a non-membership proof requires searching existing nodes—a naive implementation uses brute force. Performance improves if nodes maintain a sorted data structure of existing nullifiers, though this increases storage footprint. ## Related resources[​](#related-resources "Direct link to Related resources") * [State Management](/developers/docs/foundational-topics/state_management.md) - How public and private state work, including nullifiers * [Circuits](/developers/docs/foundational-topics/advanced/circuits.md) - Core protocol circuits where indexed merkle trees are used * [Note Discovery](/developers/docs/foundational-topics/advanced/storage/note_discovery.md) - How private notes are discovered and managed * [Original paper](https://eprint.iacr.org/2021/1263.pdf) - Academic reference for indexed merkle trees --- # Note Discovery Note discovery refers to the process of a user identifying and decrypting [notes](/developers/docs/foundational-topics/state_management.md#notes) that belong to them. ## Alternative approaches[​](#alternative-approaches "Direct link to Alternative approaches") Other protocols have explored different note discovery mechanisms. **Brute force** approaches download all notes and trial-decrypt each one, but this becomes prohibitively expensive as networks grow. **Offchain communication** has the sender share note content directly with recipients, avoiding onchain costs but introducing reliance on side channels. Aztec apps can use offchain communication if they wish, but the default mechanism is note tagging. ## Note tagging[​](#note-tagging "Direct link to Note tagging") Aztec uses note tagging as its default discovery mechanism. When creating a note, the sender *tags* the log with a value that only the sender and recipient can identify. This allows recipients to efficiently query for relevant logs without downloading and attempting to decrypt everything. ### How it works[​](#how-it-works "Direct link to How it works") #### Every log has a tag[​](#every-log-has-a-tag "Direct link to Every log has a tag") In Aztec, each emitted log is an array of fields, e.g. `[tag, x, y, z]`. The first field is a *tag* used to index and identify logs. The Aztec node indexes logs by their tag and exposes an API (`getPrivateLogsByTags()`) that retrieves logs matching specific tags. #### Tag derivation[​](#tag-derivation "Direct link to Tag derivation") The sender and recipient share a predictable scheme for generating tags. Tags are derived through a layered hashing process that makes them specific to a particular sender-recipient pair, contract, and sequence number. The derivation has four stages: 1. **Shared secret**: The sender and recipient compute the same shared secret via Diffie-Hellman key exchange on the Grumpkin curve. Each party uses their [incoming viewing secret key](/developers/docs/foundational-topics/accounts/keys.md#incoming-viewing-keys) (`ivsk`) and the other party's address point: `S = (preaddress + ivsk) × AddressPoint`. 2. **App tagging secret**: The shared secret is hashed with the contract address to produce a per-contract secret: `poseidon2(S.x, S.y, contract_address)`. This ensures tags from different contracts cannot be linked. 3. **Directional secret**: The app secret is hashed with the recipient address: `poseidon2(appSecret, recipient)`. This makes the secret asymmetric — tags from Alice to Bob differ from tags from Bob to Alice. 4. **Tag**: The directional secret is hashed with an index (a counter that increments for each log the sender emits to this recipient in this contract): `poseidon2(directionalSecret, index)`. When the log is emitted, the protocol kernel **siloes** the tag with the contract address before it appears onchain. This siloed tag is what the node stores and indexes. Both the sender and recipient can independently compute the siloed tags and use them to query the node. #### The sender in note tagging[​](#the-sender-in-note-tagging "Direct link to The sender in note tagging") The "sender" in note tagging is **not necessarily the transaction sender**. It's the **sender for tags**, which account contracts set by calling `set_sender_for_tags(account_address)` before making calls to other contracts. This is typically the account contract address itself. This sender address is used along with the recipient address to compute the shared secret via Diffie-Hellman key exchange, which is then used to derive the tag. #### Registering known senders[​](#registering-known-senders "Direct link to Registering known senders") To discover notes from a particular sender, the recipient's PXE must know the sender's address in advance so it can compute the shared tagging secret. Register senders using the wallet API: ``` // Register a sender so your PXE can discover notes from them await wallet.registerSender(senderAddress); ``` Notes sent to yourself are always discoverable — the PXE automatically adds all local accounts as implicit senders. ### The sync process[​](#the-sync-process "Direct link to The sync process") The `#[aztec]` macro automatically injects an unconstrained `sync_state` utility function into every contract. This function is invoked by the PXE during note syncing to orchestrate discovery via oracles; manual execution is forbidden by the PXE to prevent inconsistencies. The process works as follows: 1. **Fetch tagged logs**: The contract calls the `fetchTaggedLogs` oracle. The PXE computes tags for every (sender, recipient) pair it knows about, queries the node for matching logs, and returns them to the contract. 2. **Decrypt**: For each log, the contract strips the tag and attempts AES-128 decryption using a symmetric key derived from the recipient's private key (via ECDH). Logs that don't decrypt are silently discarded (they were not intended for this recipient). 3. **Parse message type**: Successfully decrypted messages are dispatched by type — private notes, partial notes, or private events. 4. **Nonce discovery** (for notes): To confirm a decrypted note is valid, the system must match it against the unique note hashes emitted in the same transaction. It iterates the note hashes in the transaction, computes candidate nonces using `compute_note_hash_nonce(first_nullifier, note_index)` (a domain-separated Poseidon2 hash), and checks whether recomputing the unique note hash with each candidate nonce produces a match. A match confirms the note was emitted in this transaction and provides the nonce needed to later nullify it. (Note hash tree inclusion is validated separately.) 5. **Store**: Validated notes are added to the PXE database, making them available for use in future transactions. Developers don't need to implement any of this manually — the `#[aztec]` macro handles it. However, since the discovery logic lives in contract code (called via oracles to the PXE), users can customize or replace the discovery mechanism to suit their needs. #### The sliding window algorithm[​](#the-sliding-window-algorithm "Direct link to The sliding window algorithm") The PXE doesn't scan all possible tag indexes — it uses a window-based approach to efficiently find new logs: * It tracks the **highest aged index**: the highest tag index seen in a block at least 24 hours old (`MAX_TX_LIFETIME`). Once a block is this old, no new transactions can reference it as an anchor, so no new logs can appear at or below that index. * It tracks the **highest finalized index**: the highest tag index seen in any finalized block. * It scans from the aged index to 20 indexes beyond the finalized index, covering both recent and in-flight logs. This means there's a practical limit on how many logs a single sender can emit to the same recipient in the same contract within a short time period. For most applications this limit is not a concern. ### Limitations and solutions[​](#limitations-and-solutions "Direct link to Limitations and solutions") #### You cannot receive tagged notes from an unknown sender[​](#you-cannot-receive-tagged-notes-from-an-unknown-sender "Direct link to You cannot receive tagged notes from an unknown sender") Without knowing the sender's address, you cannot create the shared secret needed to derive the note tag. This is a fundamental limitation of the current tagging scheme. There are three broad families of solutions to this problem: **a) Brute force search** - Scan every single log and test if it decrypts. This has obvious performance issues as the network grows and becomes prohibitively expensive. **b) Tagging with known sender** (current implementation) - You know who will send you messages and search for those specifically. This is very fast and allows you to remove senders who spam you. However, we don't currently have a mechanism for constraining this (i.e., guaranteeing that the recipient will find the message). **c) Tagging with handshaking** - An intermediate solution where you can be notified of new senders. A handshake occurs onchain that lets the recipient discover a new sender, and from that point on there's regular tagging. This design either: * Is fast but leaks privacy (e.g., a public event with "new handshake for Alice!") * Is slow but doesn't leak (you brute force scan all logs from a handshake contract, testing if any handshakes are for you) The handshaking design space is large — for example, you could set up infrastructure where a server searches handshakes for you, trading off infrastructure requirements for performance. **Handshaking is not currently implemented in Aztec.nr.** For now, if you need to receive notes from unknown senders, potential workarounds include: * Having senders register themselves in a contract first, allowing recipients to search for note tags from all registered senders * Using offchain communication to share sender addresses with recipients, who then call `wallet.registerSender(address)` to enable discovery * Implementing a custom discovery mechanism in your contract See the [Note Delivery](/developers/docs/aztec-nr/framework-description/note_delivery.md) documentation for more details on how the sender is used when delivering notes. ## Advanced cryptography techniques[​](#advanced-cryptography-techniques "Direct link to Advanced cryptography techniques") Beyond the tagging system described above, there are more advanced cryptographic techniques for note discovery: * **Oblivious message retrieval (OMR)**: Allows retrieving messages without the server knowing which messages were accessed * **Private information retrieval (PIR)**: Enables querying a database without revealing which records you're interested in These techniques would solve a privacy leak that exists with the current tagging system: when your PXE queries an Aztec node for logs with specific tags, the node can observe your IP address and correlate it with which tags (and therefore which transactions) you're interested in. Even though the logs are encrypted, this network-level metadata can leak information about your activity. OMR and PIR would eliminate this issue by allowing you to retrieve your logs without the node knowing which ones you requested. However, these methods are currently impractical in production due to computational costs. They represent a long-term goal for achieving stronger privacy guarantees. --- # Storage Slots Storage slots in Aztec serve a similar purpose to Ethereum—they identify where contract state is stored. However, Aztec handles public and private state differently to maintain privacy guarantees while preventing conflicts between contracts. note In the formulas below, `H()` represents a hash function (specifically poseidon2 in the protocol). ## Public State Slots[​](#public-state-slots "Direct link to Public State Slots") As described in [State Model](/developers/docs/foundational-topics/state_management.md), Aztec public state behaves similarly to public state on Ethereum from a developer's perspective. Behind the scenes, however, the storage is managed differently. Public state uses a single large sparse tree, so we silo slots by hashing them with the contract address: ``` siloed_storage_slot = H(contract_address, storage_slot) ``` You can think of `storage_slot` as the logical position in contract storage, while `siloed_storage_slot` identifies the actual position in the global tree. This siloing is performed by the [kernel circuits](/developers/docs/foundational-topics/advanced/circuits/private_kernel.md). For structs and arrays, logical storage slots are computed similarly to Ethereum (e.g., a struct with 3 fields uses 3 consecutive logical slots). However, since siloed slots are hashes, the actual tree positions are not consecutive. ## Private State Slots[​](#private-state-slots "Direct link to Private State Slots") Private storage works differently. As described in [State Model](/developers/docs/foundational-topics/state_management.md), private state is stored as encrypted logs with corresponding commitments in the note hash tree—an append-only structure where each leaf is a note hash. Notes are never updated or deleted; instead, a nullifier is emitted to invalidate a note. This append-only design prevents information leakage that would occur if we updated specific storage slots, even with encrypted values. Because of this, storage slots don't exist in the traditional sense for private state. The note hash tree leaves are simply commitments to note content. Nevertheless, the concept of a storage slot remains useful for application logic. It allows us to reason about distinct pieces of data—for example, ensuring that one account's balance cannot be confused with another's, or with the total supply. ### How Storage Slots Work in Private State[​](#how-storage-slots-work-in-private-state "Direct link to How Storage Slots Work in Private State") Storage slots are included as part of the note hash computation, logically linking all notes that belong to the same slot. For a token balance, this means the balance equals the sum of all non-nullified notes sharing the same storage slot—similar to how a physical wallet's balance is the sum of the bills inside it. The note hash computation includes the storage slot along with other note data (owner, randomness, and note-specific values). This happens in the application circuit. The private state variable wrappers in Aztec.nr (`PrivateSet`, `PrivateMutable`, etc.) handle this automatically. When reading notes, the application circuit constrains which storage slot the notes must belong to, ensuring notes from different slots cannot be mixed. ### Contract Address Siloing[​](#contract-address-siloing "Direct link to Contract Address Siloing") To ensure contracts can only modify their own storage, the kernel circuit performs a second siloing step: ``` siloed_note_hash = H(contract_address, note_hash) ``` This forces all note hashes to be scoped to the contract that created them. The kernel then makes each note hash unique by incorporating a nonce derived from the transaction: ``` unique_note_hash = H(note_nonce, siloed_note_hash) ``` This `unique_note_hash` is what gets inserted into the note hash tree. info Nullifiers are also siloed by contract address at the kernel level to prevent collisions across contracts. ### Privacy Implications[​](#privacy-implications "Direct link to Privacy Implications") With this design, knowing a storage slot is not sufficient to determine what data it contains—unlike public state where the slot directly maps to a value. The note hash tree only contains commitments, and the storage slot is just one component mixed into those commitments. This is a key property that enables private state in Aztec. ## Further Reading[​](#further-reading "Direct link to Further Reading") * [State Model](/developers/docs/foundational-topics/state_management.md) - Overview of public and private state in Aztec * [Private Kernel Circuits](/developers/docs/foundational-topics/advanced/circuits/private_kernel.md) - How siloing is enforced at the protocol level --- # Call Types ## What is a Call[​](#what-is-a-call "Direct link to What is a Call") We say that a smart contract is called when one of its functions is invoked and its code is run. This means there'll be: * a caller * arguments * return values * a call status (successful or failed) There are multiple types of calls, and some of the naming can make things **very** confusing. This page lists the different call types and execution modes, pointing out key differences between them. A key property of Aztec calls is that contracts can call each other privately, keeping even the call stack itself private. This two-minute explainer covers the idea before we get into the details (find more on the [video lessons](/developers/docs/resources/video_lessons.md) page): [What is Private Composability? An Aztec Explainer](https://www.youtube-nocookie.com/embed/idxRuGQnQKs) ## Ethereum Call Types[​](#ethereum-call-types "Direct link to Ethereum Call Types") Aztec's design is heavily influenced by Ethereum, and many APIs and concepts are similar. This section provides background on Ethereum call types for context. If you're already familiar with Ethereum, you can skip to [Aztec Call Types](#aztec-call-types). Ethereum background (click to expand) Broadly speaking, Ethereum contracts can be thought of as executing as a result of three different things: running certain EVM opcodes, running Solidity code (which compiles to EVM opcodes), or via the node JSON-RPC interface (e.g. when executing transactions). ### EVM[​](#evm "Direct link to EVM") Certain opcodes allow contracts to make calls to other contracts, each with different semantics. We're particularly interested in `CALL` and `STATICCALL`, and how those relate to contract programming languages and client APIs. #### `CALL`[​](#call "Direct link to call") This is the most common and basic type of call. It grants execution control to the caller until it eventually returns. No special semantics are in play here. Most Ethereum transactions spend the majority of their time in `CALL` contexts. #### `STATICCALL`[​](#staticcall "Direct link to staticcall") This behaves almost exactly the same as `CALL`, with one key difference: any state-changing operations are forbidden and will immediately cause the call to fail. This includes writing to storage, emitting logs, or deploying new contracts. This call is used to query state on an external contract, e.g. to get data from a price oracle, check for access control permissions, etc. #### Others[​](#others "Direct link to Others") The `CREATE` and `CREATE2` opcodes (for contract deployment) also result in something similar to a `CALL` context, but all that's special about them has to do with how deployments work. `DELEGATECALL` (and `CALLCODE`) are somewhat complicated to understand but don't have any Aztec equivalents, so they are not worth covering. ### Solidity[​](#solidity "Direct link to Solidity") Solidity (and other contract programming languages such as Vyper) compile down to EVM opcodes, but it is useful to understand how they map language concepts to the different call types. #### Mutating External Functions[​](#mutating-external-functions "Direct link to Mutating External Functions") These are functions marked `payable` (which can receive ETH, which is a state change) or with no mutability declaration (sometimes called `nonpayable`). When one of these functions is called on a contract, the `CALL` opcode is emitted, meaning the callee can perform state changes, make further `CALL`s, etc. It is also possible to call such a function with `STATICCALL` manually (e.g. using assembly), but the execution will fail as soon as a state-changing opcode is executed. #### `view`[​](#view "Direct link to view") An external function marked `view` will not be able to mutate state (write to storage, etc.), it can only *view* the state. Solidity will emit the `STATICCALL` opcode when calling these functions, since its restrictions provide added safety to the caller (e.g. no risk of reentrancy). Note that it is entirely possible to use `CALL` to call a `view` function, and the result will be the exact same as if `STATICCALL` had been used. The reason why `STATICCALL` exists is so that *untrusted or unknown* contracts can be called while still being able to reason about correctness. From the [EIP](https://eips.ethereum.org/EIPS/eip-214): > '`STATICCALL` adds a way to call other contracts and restrict what they can do in the simplest way. It can be safely assumed that the state of all accounts is the same before and after a static call.' ### JSON-RPC[​](#json-rpc "Direct link to JSON-RPC") From outside the EVM, calls to contracts are made via [JSON-RPC](https://ethereum.org/en/developers/docs/apis/json-rpc/) methods, typically from some client library that is aware of contract ABIs, such as [ethers.js](https://docs.ethers.org/v5) or [viem](https://viem.sh/). #### `eth_sendTransaction`[​](#eth_sendtransaction "Direct link to eth_sendtransaction") This method is how transactions are sent to a node to get them to be broadcast and eventually included in a block. The specified `to` address will be called in a `CALL` context, with some notable properties: * there are no return values, even if the contract function invoked does return some data * there is no explicit caller: it is instead derived from a provided signature Some client libraries choose to automatically issue `eth_sendTransaction` when calling functions from a contract ABI that are not marked as `view` - [ethers is a good example](https://docs.ethers.org/v5/getting-started/#getting-started--writing). Notably, this means that any return value is lost and not available to the calling client - the library typically returns a transaction receipt instead. If the return value is required, the only option is to simulate the call using `eth_call`. Note that it is possible to call non state-changing functions (i.e. `view`) with `eth_sendTransaction` - this is always meaningless. What transactions do is change the blockchain state, so all calling such a function achieves is for the caller to lose funds by paying for gas fees. The sole purpose of a `view` function is to return data, and `eth_sendTransaction` does not make the return value available. #### `eth_call`[​](#eth_call "Direct link to eth_call") This method is the largest culprit of confusion around calls, but unfortunately requires understanding of all previous concepts in order to be explained. Its name is also quite unhelpful. What `eth_call` does is simulate a transaction (a call to a contract) given the current blockchain state. The behavior will be the exact same as `eth_sendTransaction`, except: * no actual transaction will be created * while gas *will* be measured, there'll be no transaction fees of any kind * no signature is required: the `from` address is passed directly, and can be set to any value (even if the private key is unknown, or if they are contract addresses!) * the return value of the called contract is available `eth_call` is typically used for one of the following: * query blockchain data, e.g. read token balances * preview the state changes produced by a transaction, e.g. the transaction cost, token balance changes, etc Because some libraries ([such as ethers](https://docs.ethers.org/v5/getting-started/#getting-started--reading)) automatically use `eth_call` for `view` functions (which when called via Solidity result in the `STATICCALL` opcode), these concepts can be hard to tell apart. The following bears repeating: **an `eth_call`'s call context is the same as `eth_sendTransaction`, and it is a `CALL` context, not `STATICCALL`.** ## Aztec Call Types[​](#aztec-call-types "Direct link to Aztec Call Types") While Ethereum contracts are defined by bytecode that runs on the EVM, Aztec contracts have multiple modes of execution depending on the function that is invoked. This section covers the main ways contracts can be interacted with, drawing analogies to Ethereum call types where applicable. ### Quick Reference[​](#quick-reference "Direct link to Quick Reference") | Execution Mode | Annotation | Runs On | State Access | Use Case | | -------------- | ------------------------ | --------------- | --------------------- | -------------------------------------------- | | **Private** | `#[external("private")]` | User's device | Private state (notes) | Confidential transactions, private transfers | | **Public** | `#[external("public")]` | Sequencer | Public state | Token balances, access control checks | | **Utility** | `#[external("utility")]` | Offchain client | Both (unconstrained) | Read-only queries, frontend data fetching | ### Private Execution[​](#private-execution "Direct link to Private Execution") Contract functions marked with `#[external("private")]` can only be called privately, and as such 'run' in the user's device. Since they're circuits, their 'execution' is actually the generation of a zk-SNARK proof that'll later be sent to the sequencer for verification. #### Private Calls[​](#private-calls "Direct link to Private Calls") Private functions from other contracts can be called either regularly or statically by using `self.call()` and `self.view()`. They will also be 'executed' (i.e. proved) in the user's device, and `self.view()` will fail if any state changes are attempted (like the EVM's `STATICCALL`). private\_call ``` let _ = self.call(Token::at(stable_coin).burn_private(from, amount, authwit_nonce)); ``` > [Source code: noir-projects/noir-contracts/contracts/app/lending\_contract/src/main.nr#L218-L220](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/lending_contract/src/main.nr#L218-L220) Unlike the EVM however, private execution doesn't revert in the traditional way: in case of error (e.g. a failed assertion, a state changing operation in a static context, etc.) the proof generation simply fails and no transaction request is generated, spending no network gas or user funds. #### Public Calls[​](#public-calls "Direct link to Public Calls") Since public execution can only be performed by the sequencer, public functions cannot be executed in a private context. It is possible however to *enqueue* a public function call during private execution, requesting the sequencer to run it during inclusion of the transaction. It will be [executed in public](#public-execution) normally, including the possibility to enqueue static public calls. Since the public call is made asynchronously, any return values or side effects are not available during private execution. If the public function fails once executed, the entire transaction is reverted including state changes caused by the private part, such as new notes or nullifiers. Note that this does result in gas being spent, like in the case of the EVM. enqueue\_public ``` self.enqueue_self._deposit(AztecAddress::from_field(on_behalf_of), amount, collateral_asset); ``` > [Source code: noir-projects/noir-contracts/contracts/app/lending\_contract/src/main.nr#L104-L106](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/lending_contract/src/main.nr#L104-L106) It is also possible to create public functions that can *only* be invoked by privately enqueueing a call from the same contract, which can be very useful to update public state after private execution (e.g. update a token's supply after privately minting). This is achieved by annotating functions with `#[only_self]`. A common pattern is to enqueue public calls to check some validity condition on public state, e.g. that a deadline has not expired or that some public value is set. enqueueing ``` PublicChecks::at(PUBLIC_CHECKS_ADDRESS).check_block_number(operation, value).enqueue_view_incognito(context); ``` > [Source code: noir-projects/noir-contracts/contracts/protocol/public\_checks\_contract/src/utils.nr#L21-L23](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/protocol/public_checks_contract/src/utils.nr#L21-L23) Note that this reveals what public function is being called on what contract, and perhaps more importantly which contract enqueued the call during private execution. To prevent this you can enqueue a call to a public function using `self.enqueue_incognito` that behaves the same as `self.enqueue` but conceals the message sender. To address this, we have introduced a `PublicChecks` contract that can be used to perform common checks, such as verifying the timestamp or block number. By having these checks on a contract shared between apps the privacy set increases. An example of how a deadline can be checked using the `PublicChecks` contract follows: call-check-deadline ``` privately_check_timestamp(Comparator.LT, config.deadline, self.context); ``` > [Source code: noir-projects/noir-contracts/contracts/app/crowdfunding\_contract/src/main.nr#L47-L49](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/crowdfunding_contract/src/main.nr#L47-L49) `privately_check_timestamp` and `privately_check_block_number` are helper functions around the call to the `PublicChecks` contract: helper\_public\_checks\_functions ``` /// Asserts that the current timestamp in the enqueued public call enqueued by `check_timestamp` satisfies /// the `operation` with respect to the `value. Preserves privacy by performing the check via the public checks /// contract. /// This conceals an address of the calling contract by setting `context.msg_sender` to the public checks contract /// address. pub fn privately_check_timestamp(operation: u8, value: u64, context: &mut PrivateContext) { PublicChecks::at(PUBLIC_CHECKS_ADDRESS).check_timestamp(operation, value).enqueue_view_incognito(context); } /// Asserts that the current block number in the enqueued public call enqueued by `check_block_number` satisfies /// the `operation` with respect to the `value. Preserves privacy by performing the check via the public checks /// contract. /// This conceals an address of the calling contract by setting `context.msg_sender` to the public checks contract /// address. pub fn privately_check_block_number(operation: u8, value: u32, context: &mut PrivateContext) { PublicChecks::at(PUBLIC_CHECKS_ADDRESS).check_block_number(operation, value).enqueue_view_incognito(context); } ``` > [Source code: noir-projects/noir-contracts/contracts/protocol/public\_checks\_contract/src/utils.nr#L5-L25](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/protocol/public_checks_contract/src/utils.nr#L5-L25) This is what the implementation of the check timestamp functionality looks like: check\_timestamp ``` /// Asserts that the current timestamp satisfies the `operation` with respect /// to the `value. #[external("public")] #[view] fn check_timestamp(operation: u8, value: u64) { let lhs_field = self.context.timestamp() as Field; let rhs_field = value as Field; assert(compare(lhs_field, operation, rhs_field), "Timestamp mismatch."); } ``` > [Source code: noir-projects/noir-contracts/contracts/protocol/public\_checks\_contract/src/main.nr#L15-L25](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/protocol/public_checks_contract/src/main.nr#L15-L25) note The `PublicChecks` contract is not part of the [aztec-nr repository](https://github.com/AztecProtocol/aztec-nr). To add it as a dependency, point to the aztec-packages repository: ``` [dependencies] public_checks = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v4.3.1", directory = "noir-projects/noir-contracts/contracts/protocol/public_checks_contract" } ``` Even with the public checks contract, achieving good privacy is hard. For example, if the value being checked against is unique and stored in the contract's public storage, it's then simple to find private transactions that are using that value in the enqueued public reads, and therefore link them to this contract. For this reason it is encouraged to try to avoid public function calls and instead privately read [Delayed Public Mutable](/developers/docs/aztec-nr/framework-description/state_variables.md#delayedpublicmutable) state when possible. ### Public Execution[​](#public-execution "Direct link to Public Execution") Contract functions marked with `#[external("public")]` can only be called publicly, and are executed by the sequencer. The computation model is very similar to the EVM: all state, parameters, etc. are known to the entire network, and no data is private. Static execution like the EVM's `STATICCALL` is possible too, with similar semantics (state can be accessed but not modified, etc.). note The AVM supports a subset of Noir's cryptographic operations. Signature verification (ECDSA) is not available in public functions. See [AVM Cryptographic Compatibility](/developers/docs/foundational-topics/advanced/circuits/avm_compatibility.md) for details. Since private calls are always run in a user's device, it is not possible to perform any private execution from a public context. A reasonably good mental model for public execution is that of an EVM in which some work has already been done privately, and all that is known about it is its correctness and side-effects (new notes and nullifiers, enqueued public calls, etc.). A reverted public execution will also revert the private side-effects. Public functions in other contracts can be called both regularly and statically, just like on the EVM. public\_call ``` self.enqueue(Token::at(config.accepted_asset).transfer_in_public( self.msg_sender(), self.address, max_fee, authwit_nonce, )); ``` > [Source code: noir-projects/noir-contracts/contracts/fees/fpc\_contract/src/main.nr#L153-L160](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/fees/fpc_contract/src/main.nr#L153-L160) note Public functions can be called either directly in a public context (as shown above), or asynchronously by enqueuing from a private context (as shown in the [Public Calls](#public-calls) section). ### Utility[​](#utility "Direct link to Utility") Contract functions marked with `#[external("utility")]` cannot be called as part of a transaction. They are only invoked by applications that interact with contracts for: * **State queries**: Reading from both private and public state via an offchain client * **Local state management**: Modifying contract-related PXE state (e.g., processing logs in Aztec.nr) Since utility execution is unconstrained and relies heavily on oracle calls, no guarantees are made on the correctness of results. However, you can verify that the bytecode being executed is correct, since a contract's address includes a commitment to all of its utility functions. ### aztec.js[​](#aztecjs "Direct link to aztec.js") There are two main ways to execute an Aztec contract function using the `aztec.js` library, with close similarities to their [JSON-RPC counterparts](#json-rpc). #### `simulate`[​](#simulate "Direct link to simulate") This is used to get a result out of an execution, either private or public. It creates no transaction and spends no gas. The mental model is fairly close to that of [`eth_call`](#eth_call), in that it can be used to call any type of function, simulate its execution and get a result out of it. `simulate` is also the only way to run [utility functions](#utility). simulate\_function ``` const { result: balance } = await token.methods .balance_of_public(aliceAddress) .simulate({ from: aliceAddress }); console.log(`Alice's token balance: ${balance}`); ``` > [Source code: docs/examples/ts/aztecjs\_connection/index.ts#L148-L154](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_connection/index.ts#L148-L154) warning No correctness is guaranteed on the result of `simulate`! Correct execution is entirely optional and left up to the client that handles this request. #### `send`[​](#send "Direct link to send") This creates a transaction, generates proofs for private execution, broadcasts the transaction to the network, and returns a receipt. This is how transactions are sent, getting them to be included in blocks and spending gas. It is similar to [`eth_sendTransaction`](#eth_sendtransaction), except it also performs work on the user's device, namely the production of the proof for the private part of the transaction. send\_tx ``` await contract.methods.buy_pack(seed).send({ from: firstPlayer }); ``` > [Source code: yarn-project/end-to-end/src/e2e\_card\_game.test.ts#L113-L115](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/yarn-project/end-to-end/src/e2e_card_game.test.ts#L113-L115) You can also use `send` to check for execution failures in testing contexts by expecting the transaction to throw: local-tx-fails ``` await expect( claimContract.methods.claim(anotherDonationNote, donorAddress).send({ from: unrelatedAddress }), ).rejects.toThrow('confirmed_note.owner == self.msg_sender()'); ``` > [Source code: yarn-project/end-to-end/src/e2e\_crowdfunding\_and\_claim.test.ts#L208-L212](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/yarn-project/end-to-end/src/e2e_crowdfunding_and_claim.test.ts#L208-L212) ## Next Steps[​](#next-steps "Direct link to Next Steps") * [State Management](/developers/docs/foundational-topics/state_management.md) - Learn how private and public state works in Aztec * [Transactions](/developers/docs/foundational-topics/transactions.md) - Understand the transaction lifecycle * [Contract Creation](/developers/docs/foundational-topics/contract_creation.md) - Deploy and interact with contracts * [Declaring Storage](/developers/docs/aztec-nr/framework-description/state_variables.md) - Define storage in your contracts --- # Contract Deployment In the Aztec protocol, contracts are created as *instances* of contract *classes*. Unlike Ethereum where deployment is binary (deployed or not), Aztec contracts progress through multiple states before they are fully operational. ## Contract Lifecycle Overview[​](#contract-lifecycle-overview "Direct link to Contract Lifecycle Overview") Aztec contracts go through these states: 1. **Contract Class Registration** - Publishing the contract bytecode to the network 2. **Contract Instance Creation** - Computing a deterministic address from class, salt, and initialization parameters 3. **Initialization** - Running the constructor to set up initial state 4. **Public Deployment** - Broadcasting the instance to the network for public function calls 5. **Private Function Broadcasting** - Sharing private function artifacts offchain (optional) Not every contract needs every state. A private-only contract can skip class registration and public deployment entirely. See the [Contract Deployment Quick Reference](/developers/docs/aztec-nr/contract_readiness_states.md) for a practical guide on which steps your contract needs. ## Contract Classes[​](#contract-classes "Direct link to Contract Classes") A contract class is a collection of state variable declarations, and related private, public and utility functions. Contract classes don't have state, they just define code (storage structure and function logic). A contract class cannot be called; only a contract instance can be called. ### Key Benefits of Contract Classes[​](#key-benefits-of-contract-classes "Direct link to Key Benefits of Contract Classes") Contract classes simplify code reuse by making implementations a first-class citizen in the protocol. With a single class registration, multiple contract instances can be deployed that reference it, reducing deployment costs. Classes also facilitate upgradability by decoupling state from code, making it easier for an instance to switch to different code while retaining its state. ### Structure of a Contract Class[​](#structure-of-a-contract-class "Direct link to Structure of a Contract Class") A contract class includes: * `artifact_hash`: Hash of the contract artifact * `private_functions_root`: Merkle root of the private functions tree * `packed_public_bytecode`: Packed bytecode representation of the AVM bytecode for all public functions The specification of the artifact hash is not enforced by the protocol. It should include commitments to utility functions code and compilation metadata. It is intended to be used by clients to verify that an offchain fetched artifact matches a registered class. ### Contract Class Registration[​](#contract-class-registration "Direct link to Contract Class Registration") A contract class is published by calling a private `publish` function in a canonical `ContractClassRegistry` contract, which emits a registration nullifier. This process guarantees that the public bytecode for a contract class is publicly available, which is required for deploying contract instances. Contract class registration can be skipped if there are no public functions, and the contract will still be usable privately. However, if you have public functions, you must either register the class before deployment or skip public deployment entirely (only private functions will be callable). ## Contract Instances[​](#contract-instances "Direct link to Contract Instances") A deployed contract is effectively an instance of a contract class. It always references a contract class, which determines what code it executes when called. A contract instance has both private and public state, as well as an address that serves as its identifier. ### Structure of a Contract Instance[​](#structure-of-a-contract-instance "Direct link to Structure of a Contract Instance") A contract instance includes: * `salt`: User-generated pseudorandom value for uniqueness * `deployer`: Optional address of the contract deployer. Zero for universal deployment * `contract_class_id`: Identifier of the contract class for this instance * `initialization_hash`: Hash of the selector and arguments to the constructor * `public_keys`: Public keys used for encryption and nullifying (nullifier, incoming viewing, outgoing viewing, and tagging keys) ### Instance Address[​](#instance-address "Direct link to Instance Address") The address of a contract instance is computed as the hash of the elements in its structure. This computation is deterministic, allowing users to precompute the expected deployment address of their contract, including account contracts. ### Contract Initialization vs. Public Deployment[​](#contract-initialization-vs-public-deployment "Direct link to Contract Initialization vs. Public Deployment") Aztec makes an important distinction between initialization and public deployment: 1. **Initialization**: A contract instance is considered initialized once it emits an initialization nullifier, meaning it can only be initialized once. The default state for any address is uninitialized. A user who knows the preimage of the address can still issue a private call into a function in the contract, as long as that function doesn't assert that the contract has been initialized. 2. **Public Deployment**: A contract instance is considered publicly deployed when it has been broadcast to the network via the `publish_for_public_execution` function in the canonical `ContractInstanceRegistry` contract, which emits a deployment nullifier. All public function calls to an undeployed address fail, since the contract class is not known to the network. ### Initialization[​](#initialization "Direct link to Initialization") Contract constructors are not enshrined in the protocol, but handled at the application circuit level. Constructors are methods used for initializing a contract, either private or public, and contract classes may declare more than a single constructor. They can be declared by the `#[initializer]` macro. You can read more about how to use them on the [defining initializer functions](/developers/docs/aztec-nr/framework-description/functions/how_to_define_functions.md#define-initializer-functions) page. A contract must ensure: * It is initialized at most once * It is initialized using the method and arguments defined in its address preimage * It is initialized by its deployer (if non-zero) * Functions dependent on initialization cannot be invoked until the contract is initialized Functions in a contract may skip the initialization check. ## Verification of Executed Code[​](#verification-of-executed-code "Direct link to Verification of Executed Code") When a function is called on a contract instance, the protocol circuits verify that the executed code matches what was registered. For private functions, the circuit checks that the function's verification key hash exists in the `private_functions_root` of the contract class. For public functions, the AVM verifies that the bytecode matches the registered `packed_public_bytecode`. This verification ensures that contracts execute the exact code that was published during class registration. ## Genesis Contracts[​](#genesis-contracts "Direct link to Genesis Contracts") The `ContractInstanceRegistry` and `ContractClassRegistry` contracts are protocol contracts that exist from the genesis of the Aztec Network at predefined addresses. They are necessary for deploying other contracts to the network. ## Private Function Broadcasting[​](#private-function-broadcasting "Direct link to Private Function Broadcasting") Private function artifacts can be shared offchain so others can call your private functions. This is optional—callers who already have the artifacts don't need them broadcast. This step is only necessary when you want external parties to interact with your contract's private functions without having obtained the artifacts through other means. ## Proving Contract States[​](#proving-contract-states "Direct link to Proving Contract States") Your contract can verify the deployment or initialization state of other contracts. This is useful for: * Ensuring a dependency contract is deployed before interacting * Access control based on contract state * Conditional logic based on initialization ``` use aztec::history::deployment::{ assert_contract_bytecode_was_not_published_by, assert_contract_bytecode_was_published_by, assert_contract_was_initialized_by, assert_contract_was_not_initialized_by, }; // Prove a contract's bytecode was published by a given block assert_contract_bytecode_was_published_by(block_header, contract_address); // Prove a contract's bytecode was NOT published by a given block assert_contract_bytecode_was_not_published_by(block_header, contract_address); // Prove a contract was initialized by a given block // (init_hash is the contract's initialization hash, obtainable via get_contract_instance) assert_contract_was_initialized_by(block_header, contract_address, init_hash); // Prove a contract was NOT initialized by a given block assert_contract_was_not_initialized_by(block_header, contract_address, init_hash); ``` These functions prove inclusion or non-inclusion of the corresponding nullifiers in the nullifier tree at a given block. ## Further reading[​](#further-reading "Direct link to Further reading") * [Contract Deployment Quick Reference](/developers/docs/aztec-nr/contract_readiness_states.md) - Practical guide for which deployment steps your contract needs * [Deploying Contracts](/developers/docs/aztec-js/how_to_deploy_contract.md) - Deploy contracts using TypeScript * [DApp Development Tutorial](/developers/docs/tutorials/js_tutorials/aztecjs-getting-started.md) - Build a complete application * [Communicating Cross-Chain](/developers/docs/aztec-nr/framework-description/ethereum_aztec_messaging.md) - Portal contracts and L1/L2 messaging --- # L1-L2 Communication (Portals) In Aztec, *portals* facilitate communication between L1 and L2. Unlike typical L2 solutions that rely on synchronous communication, Aztec's privacy-first design and the way transactions are processed (kernel proofs built on historical data) make direct calls between L1 and L2 impossible while maintaining privacy. Portals solve this by acting as bridges for asynchronous message passing, transmitting messages from public functions in L1 to private functions in L2 and vice versa. ## Objective[​](#objective "Direct link to Objective") The goal is to set up a minimal-complexity mechanism, that will allow a base-layer (L1) and the Aztec Network (L2) to communicate arbitrary messages such that: * L2 functions can `call` L1 functions. * L1 functions can `call` L2 functions. * Messages have minimal impact on rollup block size. ## High Level Overview[​](#high-level-overview "Direct link to High Level Overview") This document will contain communication abstractions that we use to support interaction between *private* functions, *public* functions and Layer 1 portal contracts. Fundamental restrictions for Aztec: * L1 and L2 have very different execution environments. Operations that are cheap on L1 are often expensive on L2 and vice versa. For example, `keccak256` is cheap on L1 but very expensive on L2. * *Private* function calls are fully "prepared" and proven by the user, which provides the kernel proof along with commitments and nullifiers to the sequencer. * *Public* functions altering public state (updatable storage) must be executed at the current "head" of the chain, which only the sequencer can ensure, so these must be executed separately to the *private* functions. * *Private* and *public* functions within Aztec are therefore ordered such that *private* functions are executed first, then *public* functions. * Messages are consumables, and can only be consumed by the recipient. See [Message Boxes](#message-boxes) for more information. With the aforementioned restrictions taken into account, cross-chain messages can be operated in a similar manner to when *public* functions must transmit information to *private* functions. In such a scenario, a "message" is created and conveyed to the recipient for future use. It is worth noting that any call made between different domains (*private, public, cross-chain*) is unilateral in nature. In other words, the caller is unaware of the outcome of the initiated call until told when some later rollup is executed (if at all). This can be regarded as message passing, providing us with a consistent mental model across all domains, which is convenient. As an illustration, suppose a private function adds a cross-chain call. In such a case, the private function would not have knowledge of the result of the cross-chain call within the same rollup (since it has yet to be executed). Similarly to the ordering of private and public functions, we can also reap the benefits of intentionally ordering messages between L1 and L2. When a message is sent from L1 to L2, it has been "emitted" by an action in the past (an L1 interaction), allowing us to add it to the list of consumables at the "beginning" of the block execution. This practical approach means that a message could be consumed in the same block it is included. In a sophisticated setup, rollup n could send an L2 to L1 message that is then consumed on L1, and the response is added already in n+1. However, messages going from L2 to L1 will be added as they are emitted. info Because everything is unilateral and async, application developers must explicitly handle failure cases so users can gracefully recover. Token bridges are a prime example: it would be very inconvenient if funds are locked on one domain but never minted or unlocked on the other. ## Components[​](#components "Direct link to Components") ### Portal[​](#portal "Direct link to Portal") A "portal" refers to the part of an application residing on L1, which is associated with a particular L2 address (the confidential part of the application). It could be a contract or even an EOA on L1. ### Message Boxes[​](#message-boxes "Direct link to Message Boxes") In a logical sense, a Message Box functions as a one-way message passing mechanism with two ends, one residing on each side of the divide, i.e., one component on L1 and another on L2. Essentially, these boxes are utilized to transmit messages between L1 and L2 via the rollup contract. The boxes can be envisaged as multi-sets that enable the same message to be inserted numerous times, a feature that is necessary to accommodate scenarios where, for instance, "deposit 10 eth to A" is required multiple times. The diagram below provides a detailed illustration of how one can perceive a message box in a logical context. ![](/assets/ideal-img/com-abs-5.017c953.640.png) * Here, a `sender` will insert a message into the `pending` set, the specific constraints of the actions depend on the implementation domain, but for now, say that anyone can insert into the pending set. * At some point, a rollup will be executed, in this step messages are "moved" from pending on Domain A, to ready on Domain B. Note that consuming the message is "pulling & deleting" (or nullifying). The action is atomic, so a message that is consumed from the pending set MUST be added to the ready set, or the state transition should fail. A further constraint is that the `sender` and `recipient` version fields must match the version of their respective inbox/outbox contracts. * When the message has been added to the ready set, the `recipient` can consume the message as part of a function call. A difference when compared to other cross-chain setups, is that Aztec is "pulling" messages, and that the message doesn't need to be calldata for a function call. For other rollups, execution is happening FROM the "message bridge", which then calls the L1 contract. For Aztec, you call the L1 contract, and it should then consume messages from the message box. Why pull instead of push? Privacy. Pushing would require full calldata, which would publicly expose inputs to private functions since L1 → L2 transaction calldata is committed on L1. By instead pulling, we can have the "message" be something that is derived from the arguments instead. This way, a private function to perform second half of a deposit, leaks the "value" deposited and "who" made the deposit (as this is done on L1), but the new owner can be hidden on L2. To support messages in both directions we require two of these message boxes (one in each direction). However, due to the limitations of each domain, the message box for sending messages into the rollup and sending messages out are not fully symmetrical. In reality, the setup looks closer to the following: ![](/assets/ideal-img/com-abs-6.0a38c8b.640.png) info The L2 -> L1 pending messages set only exist logically, as it is practically unnecessary. For anything to happen to the L2 state (e.g., update the pending messages), the state will be updated on L1, meaning that we could just as well insert the messages directly into the ready set. ### Rollup Contract[​](#rollup-contract "Direct link to Rollup Contract") The rollup contract has a few very important responsibilities. The contract must keep track of the *L2 rollup state root*, perform *state transitions* and ensure that the data is available for anyone else to synchronize to the current state. To ensure that *state transitions* are performed correctly, the contract will derive public inputs for the **rollup circuit** based on the input data, and then use a *verifier* contract to validate that inputs correctly transition the current state to the next. All data needed for the public inputs to the circuit must be from the rollup block, ensuring that the block is available. For a valid proof, the *rollup state root* is updated and it will emit an *event* to make it easy for anyone to find the data. As part of *state transitions* where cross-chain messages are included, the contract must "move" messages along the way, e.g., from "pending" to "ready". ### Kernel Circuit[​](#kernel-circuit "Direct link to Kernel Circuit") For L2 to L1 messages, the kernel circuit's public inputs contain a dynamic array of messages, limited to `MAX_L2_TO_L1_MSGS_PER_TX` to ensure transactions can always be included. The circuit scopes each message to the contract address that emitted it, ensuring the sender cannot be spoofed. When consuming L1 to L2 messages, user contracts call `process_l1_to_l2_message()` which verifies the message exists in the L1 to L2 message tree and creates a nullifier to prevent double-consumption. The kernel circuit accumulates these nullifiers in its public inputs. ### Rollup Circuit[​](#rollup-circuit "Direct link to Rollup Circuit") The rollup circuit must ensure that, provided two states S and S′ and the rollup block B, applying B to S using the transition function must give us S′, e.g., T(S,B)↦S′. If this is not the case, the constraints are not satisfied. For cross-chain messages, this means inserting and nullifying L1 → L2 messages in the trees and publishing L2 → L1 messages on chain. ### Messages[​](#messages "Direct link to Messages") While a message could theoretically be arbitrarily long, we want to limit the cost of the insertion on L1 as much as possible. Therefore, we allow the users to send 32 bytes of "content" between L1 and L2. If 32 suffices, no packing required. If the 32 is too "small" for the message directly, the sender should simply pass along a `sha256(content)` instead of the content directly (note that this hash should fit in a field element which is \~254 bits. More info on this below). The content can then either be emitted as an event on L2 or kept by the sender, who should then be the only entity that can "unpack" the message. In this manner, there is some way to "unpack" the content on the receiving domain. The message that is passed along requires the `sender/recipient` pair to be communicated as well (we need to know who should receive the message and be able to check). By having the pending messages be a contract on L1, we can ensure that the `sender = msg.sender` and let only `content` and `recipient` be provided by the caller. We only store the commitment (`sha256(LxToLyMsg)`) on chain or in the trees, so we only need to update a single storage slot per message. See the [Data Structures](/developers/docs/foundational-topics/ethereum-aztec-messaging/data_structures.md) page for the full message structure definitions (`L1Actor`, `L2Actor`, `L1ToL2Msg`, `L2ToL1Msg`). info The `bytes32` elements for `content` and `secretHash` hold values that must fit in a field element (\~ 254 bits). info The nullifier computation should include the index of the message in the message tree to ensure that it is possible to send duplicate messages (e.g., 2 x deposit of 500 dai to the same account). To make it possible to hide when a specific message is consumed, the `L1ToL2Msg` is extended with a `secretHash` field, where the `secretPreimage` is used as part of the nullifier computation. This way, it is not possible for someone just seeing the `L1ToL2Msg` on L1 to know when it is consumed on L2. ## Combined Architecture[​](#combined-architecture "Direct link to Combined Architecture") The following diagram shows the overall architecture, combining the earlier sections. ![](/assets/ideal-img/com-abs-7.6cb1c07.640.png) ## See also[​](#see-also "Direct link to See also") * [Communicating Cross-Chain](/developers/docs/aztec-nr/framework-description/ethereum_aztec_messaging.md) - Practical guide with code examples for L1-L2 messaging * [Data Structures](/developers/docs/foundational-topics/ethereum-aztec-messaging/data_structures.md) - Message and actor type definitions * [Inbox](/developers/docs/foundational-topics/ethereum-aztec-messaging/inbox.md) - L1 contract for sending messages to L2 * [Outbox](/developers/docs/foundational-topics/ethereum-aztec-messaging/outbox.md) - L1 contract for consuming messages from L2 --- # Data Structures This page documents the Solidity structs used for L1-L2 message passing in the Aztec protocol. **Source**: [DataStructures.sol](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/l1-contracts/src/core/libraries/DataStructures.sol) ## `L1Actor`[​](#l1actor "Direct link to l1actor") An entity on L1, specifying the address and the chainId. Used when specifying a sender or recipient on L1. l1\_actor ``` /** * @notice Actor on L1. * @param actor - The address of the actor * @param chainId - The chainId of the actor */ struct L1Actor { address actor; uint256 chainId; } ``` > [Source code: l1-contracts/src/core/libraries/DataStructures.sol#L11-L22](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/l1-contracts/src/core/libraries/DataStructures.sol#L11-L22) ## `L2Actor`[​](#l2actor "Direct link to l2actor") An entity on L2, specifying the Aztec address and the protocol version. Used when specifying a sender or recipient on L2. l2\_actor ``` /** * @notice Actor on L2. * @param actor - The aztec address of the actor * @param version - Ahe Aztec instance the actor is on */ struct L2Actor { bytes32 actor; uint256 version; } ``` > [Source code: l1-contracts/src/core/libraries/DataStructures.sol#L24-L35](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/l1-contracts/src/core/libraries/DataStructures.sol#L24-L35) ## `L1ToL2Msg`[​](#l1tol2msg "Direct link to l1tol2msg") A message sent from L1 to L2. The `secretHash` field contains the hash of a secret pre-image that must be known to consume the message on L2. Use [`computeSecretHash`](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/yarn-project/stdlib/src/hash/hash.ts) to compute it from a secret. l1\_to\_l2\_msg ``` /** * @notice Struct containing a message from L1 to L2 * @param sender - The sender of the message * @param recipient - The recipient of the message * @param content - The content of the message (application specific) padded to bytes32 or hashed if larger. * @param secretHash - The secret hash of the message (make it possible to hide when a specific message is consumed on * L2). * @param index - Global leaf index on the L1 to L2 messages tree. */ struct L1ToL2Msg { L1Actor sender; L2Actor recipient; bytes32 content; bytes32 secretHash; uint256 index; } ``` > [Source code: l1-contracts/src/core/libraries/DataStructures.sol#L37-L55](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/l1-contracts/src/core/libraries/DataStructures.sol#L37-L55) ## `L2ToL1Msg`[​](#l2tol1msg "Direct link to l2tol1msg") A message sent from L2 to L1. l2\_to\_l1\_msg ``` /** * @notice Struct containing a message from L2 to L1 * @param sender - The sender of the message * @param recipient - The recipient of the message * @param content - The content of the message (application specific) padded to bytes32 or hashed if larger. * @dev Not to be confused with L2ToL1Message in Noir circuits */ struct L2ToL1Msg { DataStructures.L2Actor sender; DataStructures.L1Actor recipient; bytes32 content; } ``` > [Source code: l1-contracts/src/core/libraries/DataStructures.sol#L57-L70](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/l1-contracts/src/core/libraries/DataStructures.sol#L57-L70) ## See also[​](#see-also "Direct link to See also") * [Inbox](/developers/docs/foundational-topics/ethereum-aztec-messaging/inbox.md) - L1 contract for sending messages to L2 * [Outbox](/developers/docs/foundational-topics/ethereum-aztec-messaging/outbox.md) - L1 contract for consuming messages from L2 * [Portal messaging overview](/developers/docs/foundational-topics/ethereum-aztec-messaging.md) - How L1-L2 messaging works --- # Inbox The `Inbox` is a contract deployed on L1 that handles message passing from L1 to L2. **Links**: [Interface](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/l1-contracts/src/core/interfaces/messagebridge/IInbox.sol), [Implementation](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/l1-contracts/src/core/messagebridge/Inbox.sol). ## `sendL2Message()`[​](#sendl2message "Direct link to sendl2message") Sends a message from L1 to L2. send\_l1\_to\_l2\_message ``` /** * @notice Inserts a new message into the Inbox * @dev Emits `MessageSent` with data for easy access by the sequencer * @param _recipient - The recipient of the message * @param _content - The content of the message (application specific) * @param _secretHash - The secret hash of the message (make it possible to hide when a specific message is consumed * on L2) * @return The key of the message in the set and its leaf index in the tree */ function sendL2Message(DataStructures.L2Actor memory _recipient, bytes32 _content, bytes32 _secretHash) external returns (bytes32, uint256); ``` > [Source code: l1-contracts/src/core/interfaces/messagebridge/IInbox.sol#L35-L48](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/l1-contracts/src/core/interfaces/messagebridge/IInbox.sol#L35-L48) | Name | Type | Description | | ----------- | ----------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Recipient | [`L2Actor`](/developers/docs/foundational-topics/ethereum-aztec-messaging/data_structures.md#l2actor) | The recipient of the message. The recipient's version **MUST** match the inbox version and the actor must be an Aztec contract that is **attached** to the contract making this call. If the recipient is not attached to the caller, the message cannot be consumed by it. | | Content | `field` (\~254 bits) | The content of the message. This is the data that will be passed to the recipient. The content is limited to a single field for rollup purposes. If the content is small enough it can be passed directly, otherwise it should be hashed and the hash passed along (you can use our [`Hash`](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/l1-contracts/src/core/libraries/crypto/Hash.sol) utilities with `sha256ToField` functions). | | Secret Hash | `field` (\~254 bits) | A hash of a secret used when consuming the message on L2. Keep this preimage secret to make the consumption private. To consume the message the caller must know the pre-image (the value that was hashed). Use [`computeSecretHash`](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/yarn-project/stdlib/src/hash/hash.ts) to compute it from a secret. | | ReturnValue | `(bytes32, uint256)` | The message hash (used as an identifier) and the leaf index in the tree. | #### Edge cases[​](#edge-cases "Direct link to Edge cases") * Will revert with `Inbox__ActorTooLarge(bytes32 actor)` if the recipient actor is larger than the field size (\~254 bits). * Will revert with `Inbox__VersionMismatch(uint256 expected, uint256 actual)` if the recipient version doesn't match the inbox version. * Will revert with `Inbox__ContentTooLarge(bytes32 content)` if the content is larger than the field size (\~254 bits). * Will revert with `Inbox__SecretHashTooLarge(bytes32 secretHash)` if the secret hash is larger than the field size (\~254 bits). * Will revert with `Inbox__Ignition()` during the ignition phase (when the rollup's mana target is 0). ## View functions[​](#view-functions "Direct link to View functions") These functions allow you to query the current state of the Inbox. | Function | Returns | Description | | ---------------------------- | ------------ | ------------------------------------------------------------------------------------------------ | | `getRoot(uint256)` | `bytes32` | Returns the root of a message tree for a given checkpoint number. | | `getState()` | `InboxState` | Returns the current inbox state (rolling hash, total messages inserted, in-progress checkpoint). | | `getTotalMessagesInserted()` | `uint64` | Returns the total number of messages inserted into the inbox. | | `getInProgress()` | `uint64` | Returns the checkpoint number currently being filled. | | `getFeeAssetPortal()` | `address` | Returns the address of the Fee Juice portal. | ## Internal functions[​](#internal-functions "Direct link to Internal functions") note The following functions are only callable by the Rollup contract and are documented here for completeness. ### `consume()`[​](#consume "Direct link to consume") Consumes a message tree for a given checkpoint number. consume ``` /** * @notice Consumes the current tree, and starts a new one if needed * @dev Only callable by the rollup contract * @dev In the first iteration we return empty tree root because first checkpoint's messages tree is always * empty because there has to be a 1 checkpoint lag to prevent sequencer DOS attacks * * @param _toConsume - The checkpoint number to consume * * @return The root of the consumed tree */ function consume(uint256 _toConsume) external returns (bytes32); ``` > [Source code: l1-contracts/src/core/interfaces/messagebridge/IInbox.sol#L50-L62](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/l1-contracts/src/core/interfaces/messagebridge/IInbox.sol#L50-L62) | Name | Type | Description | | ----------- | --------- | -------------------------------------- | | \_toConsume | `uint256` | The checkpoint number to consume. | | ReturnValue | `bytes32` | The root of the consumed message tree. | #### Edge cases[​](#edge-cases-1 "Direct link to Edge cases") * Will revert with `Inbox__Unauthorized()` if `msg.sender != ROLLUP`. * Will revert with `Inbox__MustBuildBeforeConsume()` if trying to consume a checkpoint that hasn't been built yet. ## Related pages[​](#related-pages "Direct link to Related pages") * [Outbox](/developers/docs/foundational-topics/ethereum-aztec-messaging/outbox.md) - L2 to L1 message passing * [Data Structures](/developers/docs/foundational-topics/ethereum-aztec-messaging/data_structures.md) - Message and actor type definitions --- # Outbox The `Outbox` is a contract deployed on L1 that handles message passing from L2 to L1. Portal contracts call `consume()` to receive and process messages that were sent from L2 contracts. The Rollup contract inserts message roots via `insert()` when epochs are proven. **Links**: [Interface](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol), [Implementation](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/l1-contracts/src/core/messagebridge/Outbox.sol). ## `insert()`[​](#insert "Direct link to insert") Inserts the root of a merkle tree containing all of the L2 to L1 messages in an epoch. This function is only callable by the Rollup contract. outbox\_insert ``` /** * @notice Inserts the root of a merkle tree containing all of the L2 to L1 messages in an epoch specified by _epoch. * @dev Only callable by the rollup contract * @dev Emits `RootAdded` upon inserting the root successfully * @param _epoch - The epoch in which the L2 to L1 messages reside * @param _root - The merkle root of the tree where all the L2 to L1 messages are leaves */ function insert(Epoch _epoch, bytes32 _root) external; ``` > [Source code: l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol#L18-L27](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol#L18-L27) | Name | Type | Description | | -------------- | --------- | ---------------------------------------------------------------------- | | `_epochNumber` | `uint256` | The epoch number in which the L2 to L1 messages reside | | `_root` | `bytes32` | The merkle root of the tree where all the L2 to L1 messages are leaves | ### Edge cases[​](#edge-cases "Direct link to Edge cases") * Will revert with `Outbox__Unauthorized()` if `msg.sender != ROLLUP_CONTRACT`. ## `consume()`[​](#consume "Direct link to consume") Allows a recipient to consume a message from the `Outbox`. outbox\_consume ``` /** * @notice Consumes an entry from the Outbox * @dev Only useable by portals / recipients of messages * @dev Emits `MessageConsumed` when consuming messages * @param _message - The L2 to L1 message * @param _epoch - The epoch that contains the message we want to consume * @param _leafIndex - The index at the level in the epoch message tree where the message is located * @param _path - The sibling path used to prove inclusion of the message, the _path length depends * on the location of the L2 to L1 message in the epoch message tree. */ function consume( DataStructures.L2ToL1Msg calldata _message, Epoch _epoch, uint256 _leafIndex, bytes32[] calldata _path ) external; ``` > [Source code: l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol#L29-L46](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol#L29-L46) | Name | Type | Description | | -------------- | ----------- | -------------------------------------------------------------------------- | | `_message` | `L2ToL1Msg` | The L2 to L1 message to consume | | `_epochNumber` | `uint256` | The epoch number specifying the epoch that contains the message to consume | | `_leafIndex` | `uint256` | The index inside the merkle tree where the message is located | | `_path` | `bytes32[]` | The sibling path used to prove inclusion of the message | ### Edge cases[​](#edge-cases-1 "Direct link to Edge cases") * Will revert with `Outbox__PathTooLong()` if the path length is >= 256. * Will revert with `Outbox__LeafIndexOutOfBounds(uint256 leafIndex, uint256 pathLength)` if the leaf index exceeds the tree capacity for the given path length. * Will revert with `Outbox__VersionMismatch(uint256 expected, uint256 actual)` if the message version does not match the Outbox version. * Will revert with `Outbox__InvalidRecipient(address expected, address actual)` if `msg.sender != _message.recipient.actor`. * Will revert with `Outbox__InvalidChainId()` if `block.chainid != _message.recipient.chainId`. * Will revert with `Outbox__NothingToConsumeAtEpoch(uint256 epochNumber)` if the root for the epoch has not been set. * Will revert with `Outbox__AlreadyNullified(uint256 epochNumber, uint256 leafIndex)` if the message has already been consumed. * Will revert with `MerkleLib__InvalidIndexForPathLength()` if the leaf index has bits set beyond the tree height. * Will revert with `MerkleLib__InvalidRoot(bytes32 expected, bytes32 actual, bytes32 leaf, uint256 leafIndex)` if the merkle proof verification fails. ## `hasMessageBeenConsumedAtEpoch()`[​](#hasmessagebeenconsumedatepoch "Direct link to hasmessagebeenconsumedatepoch") Checks if an L2 to L1 message in a specific epoch has been consumed. outbox\_has\_message\_been\_consumed\_at\_epoch\_and\_index ``` /** * @notice Checks to see if an L2 to L1 message in a specific epoch has been consumed * @dev - This function does not throw. Out-of-bounds access is considered valid, but will always return false * @param _epoch - The epoch that contains the message we want to check * @param _leafId - The unique id of the message leaf */ function hasMessageBeenConsumedAtEpoch(Epoch _epoch, uint256 _leafId) external view returns (bool); ``` > [Source code: l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol#L48-L56](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol#L48-L56) | Name | Type | Description | | -------------- | --------- | ------------------------------------------------------------------------ | | `_epochNumber` | `uint256` | The epoch number specifying the epoch that contains the message to check | | `_leafId` | `uint256` | The unique id of the message leaf | ### Edge cases[​](#edge-cases-2 "Direct link to Edge cases") * This function does not throw. Out-of-bounds access is considered valid, but will always return false. ## `getRootData()`[​](#getrootdata "Direct link to getrootdata") Returns the merkle root for a given epoch number. Returns `bytes32(0)` if the epoch has not been proven. ``` function getRootData(uint256 _epochNumber) external view returns (bytes32); ``` | Name | Type | Description | | -------------- | --------- | ------------------------------------------- | | `_epochNumber` | `uint256` | The epoch number to fetch the root data for | **Returns**: The merkle root of the L2 to L1 message tree for the epoch, or `bytes32(0)` if not proven. ## Related pages[​](#related-pages "Direct link to Related pages") * [Inbox](/developers/docs/foundational-topics/ethereum-aztec-messaging/inbox.md) - L1 to L2 message passing * [Data Structures](/developers/docs/foundational-topics/ethereum-aztec-messaging/data_structures.md) - Message struct definitions * [L1-L2 Communication (Portals)](/developers/docs/foundational-topics/ethereum-aztec-messaging.md) - Overview of cross-chain messaging --- # Registry The Registry is a contract deployed on L1 that tracks canonical and historical rollup instances. It allows you to query the current rollup contract and look up prior deployments by version. **Links**: [Interface](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/l1-contracts/src/governance/interfaces/IRegistry.sol), [Implementation](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/l1-contracts/src/governance/Registry.sol). ## `numberOfVersions()`[​](#numberofversions "Direct link to numberofversions") Retrieves the number of versions that have been deployed. registry\_number\_of\_versions ``` function numberOfVersions() external view returns (uint256); ``` > [Source code: l1-contracts/src/governance/interfaces/IRegistry.sol#L25-L27](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/l1-contracts/src/governance/interfaces/IRegistry.sol#L25-L27) | Name | Description | | ----------- | ---------------------------------------------- | | ReturnValue | The number of versions that have been deployed | ## `getCanonicalRollup()`[​](#getcanonicalrollup "Direct link to getcanonicalrollup") Retrieves the current rollup contract. registry\_get\_canonical\_rollup ``` function getCanonicalRollup() external view returns (IHaveVersion); ``` > [Source code: l1-contracts/src/governance/interfaces/IRegistry.sol#L17-L19](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/l1-contracts/src/governance/interfaces/IRegistry.sol#L17-L19) | Name | Description | | ----------- | ------------------ | | ReturnValue | The current rollup | ## `getRollup(uint256 _version)`[​](#getrollupuint256-_version "Direct link to getrollupuint256-_version") Retrieves the rollup contract for a specific version. registry\_get\_rollup ``` function getRollup(uint256 _chainId) external view returns (IHaveVersion); ``` > [Source code: l1-contracts/src/governance/interfaces/IRegistry.sol#L21-L23](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/l1-contracts/src/governance/interfaces/IRegistry.sol#L21-L23) | Name | Description | | ----------- | ------------------------------------ | | `_version` | The version identifier of the rollup | | ReturnValue | The rollup for the specified version | ## Other view functions[​](#other-view-functions "Direct link to Other view functions") | Function | Returns | Description | | ------------------------ | -------------------- | ------------------------------------------------------------------------------------ | | `getVersion(uint256)` | `uint256` | Returns the version number stored at the given index in the historical versions list | | `getGovernance()` | `address` | Returns the governance contract address (owner) | | `getRewardDistributor()` | `IRewardDistributor` | Returns the reward distributor contract | ## Related pages[​](#related-pages "Direct link to Related pages") * [Inbox](/developers/docs/foundational-topics/ethereum-aztec-messaging/inbox.md) - L1 to L2 message passing * [Outbox](/developers/docs/foundational-topics/ethereum-aztec-messaging/outbox.md) - L2 to L1 message passing * [L1-L2 Communication (Portals)](/developers/docs/foundational-topics/ethereum-aztec-messaging.md) - Overview of cross-chain messaging --- # Fees Fees are an integral part of any protocol's design. Proper fee pricing contributes to the longevity and security of a network, and the fee payment mechanisms available inform the types of applications that can be built. In a nutshell, the pricing of transactions transparently accounts for: * L1 costs, including L1 execution of a block, and data availability via blobs, * L2 node operating costs, including proving This is achieved through multiple variables and calculations. ## Terminology[​](#terminology "Direct link to Terminology") Familiar terms from Ethereum mainnet as referred to on the Aztec network: | Ethereum Mainnet | Aztec | Description | | ---------------- | ------------------ | -------------------------------------------------------------- | | gas | mana | Unit measuring computational effort for transaction operations | | fee per gas | Fee Juice per mana | Price per unit of mana | | fee (wei) | Fee Juice | Total fee paid for a transaction | ## What is mana?[​](#what-is-mana "Direct link to What is mana?") Mana is Aztec's unit of computational effort, equivalent to gas on Ethereum. Every transaction consumes mana based on the operations it performs. Mana has two dimensions: * **Data Availability (DA) mana**: Cost of publishing transaction data to the data availability layer * **L2 mana**: Cost of executing the transaction on Aztec The total transaction fee is calculated as: ``` fee = (daMana × feePerDaMana) + (l2Mana × feePerL2Mana) ``` note The SDK and protocol code use "gas" in variable names (e.g., `daGas`, `l2Gas`, `feePerDaGas`, `feePerL2Gas`) rather than "mana". When reading code, `Gas` and mana refer to the same concept. ## What is Fee Juice?[​](#what-is-fee-juice "Direct link to What is Fee Juice?") Fee Juice is the native fee token on Aztec, used to pay for transaction fees. It is bridged Aztec tokens from Ethereum and is **non-transferable** on Aztec - it can only be used to pay fees, not sent between accounts. Aztec borrows ideas from EIP-1559, including congestion multipliers and the ability to specify base and priority fees per mana. ## Factors affecting fees[​](#factors-affecting-fees "Direct link to Factors affecting fees") Other fields used in mana and fee calculations are determined in various ways: * hard-coded constants (eg congestion update fraction) * values assumed constant (eg L1 gas cost of publishing a block, blobs per block) * informed from previous block header and/or L1 rollup contract (eg base fee per mana) * informed via an oracle (eg wei per mana) Most constants are defined by the protocol, while others are part of the rollup contract on L1. ### User-defined settings[​](#user-defined-settings "Direct link to User-defined settings") Users can define the following settings as part of a transaction: gas\_settings\_vars ``` /** Gas usage and fees limits set by the transaction sender for different dimensions and phases. */ export class GasSettings { constructor( public readonly gasLimits: Gas, public readonly teardownGasLimits: Gas, public readonly maxFeesPerGas: GasFees, public readonly maxPriorityFeesPerGas: GasFees, ) {} ``` > [Source code: yarn-project/stdlib/src/gas/gas\_settings.ts#L26-L35](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/yarn-project/stdlib/src/gas/gas_settings.ts#L26-L35) The `Gas` and `GasFees` types each specify Data availability and L2 cost components, so the settings are: * gasLimits: DA and L2 gas limits * teardownGasLimits: DA and L2 gas limits for a txs optional teardown operation * maxFeesPerGas: maximum DA and L2 fees-per-gas * maxPriorityFeesPerGas: maximum priority DA and L2 fees-per-gas ## Fee payment[​](#fee-payment "Direct link to Fee payment") A fee payer obtains Fee Juice by bridging Aztec tokens from Ethereum. The fee payer can be the account itself or a fee-paying contract (FPC), which functions similarly to a paymaster on Ethereum. On Aztec, Fee Juice is held as a public balance, is non-transferable, and is only deducted by the protocol to pay for fees. ### Bridging Fee Juice from Ethereum[​](#bridging-fee-juice-from-ethereum "Direct link to Bridging Fee Juice from Ethereum") Fee Juice originates on Ethereum as an ERC-20 token. Bridging means depositing that token into the enshrined `FeeJuicePortal` contract on L1, then claiming the resulting balance on L2. Unlike user-deployed token portals, the `FeeJuicePortal` is part of the protocol's L1 deployment, but it uses the same [cross-chain messaging](/developers/docs/aztec-nr/framework-description/ethereum_aztec_messaging.md) mechanism available to any token; the [Token Bridge Tutorial](/developers/docs/tutorials/js_tutorials/token_bridge.md) describes portal contracts in general. Bridging happens in two steps: 1. **Deposit on L1.** The depositor generates a random claim secret, approves the `FeeJuicePortal` to spend their tokens, and calls its deposit function with the recipient's Aztec address, the amount, and the hash of the claim secret. The portal locks the tokens and sends an L1-to-L2 message addressed to the `FeeJuice` protocol contract on Aztec. 2. **Claim on L2.** After the message becomes available on Aztec (about two L2 blocks after the deposit), the claimant presents the claim secret to the `FeeJuice` contract. It consumes the message, emitting a nullifier so the same deposit cannot be claimed twice, and credits the recipient's public Fee Juice balance. Only the hash of the claim secret appears on L1, and the recipient address is fixed at deposit time, so revealing the secret on L2 releases the funds only to the intended recipient. The bridge is one-way. There is no withdrawal path back to L1 for users: bridged Fee Juice can only be spent on fees, and tokens leave the L1 portal only when the rollup contract distributes collected fees to sequencers and provers. Claiming is itself an L2 transaction whose fee must be paid, which would leave a brand-new account stuck. The protocol resolves this by letting a transaction claim Fee Juice and spend it on that same transaction's fee: the claim runs in the transaction's non-revertible [setup phase](/developers/docs/foundational-topics/transactions.md#setup-phase-non-revertible), so the credited balance is available to cover the fee even if the rest of the transaction reverts. This is how a new account can pay for its own deployment with bridged Fee Juice. The `FeeJuice` contract also offers a standalone claim for pre-funding an account that will pay fees later. In `aztec.js`, the `L1FeeJuicePortalManager` class handles the L1 side (token approval and deposit) and returns the claim details, and `FeeJuicePaymentMethodWithClaim` performs the claim and fee payment in a single transaction. See [Bridge Fee Juice from L1](/developers/docs/aztec-js/how_to_pay_fees.md#bridge-fee-juice-from-l1) for a code walkthrough. The `aztec-wallet` CLI's `bridge-fee-juice` command handles the L1 deposit step and prints the claim details for use in a later transaction. ### Payment methods[​](#payment-methods "Direct link to Payment methods") An account with Fee Juice can pay for its transactions directly. A new account can even pay for its own deployment transaction, provided Fee Juice was bridged to its address before deployment. Alternatively, accounts can use [fee-paying contracts (FPCs)](/developers/docs/aztec-js/how_to_pay_fees.md#use-fee-payment-contracts) to pay for transactions. An FPC holds its own Fee Juice balance to pay the protocol, and can accept other tokens from users in exchange. The **Sponsored FPC** pays fees unconditionally, enabling free transactions. It is available on testnet, devnet, and local network. On mainnet, ecosystem-deployed FPCs are the practical option: the built-in reference FPC contract does not work on mainnet alpha because custom token class IDs are not included in the default public setup allowlist. As an example, Nethermind's [Private Multi Asset FPC](https://github.com/NethermindEth/aztec-fpc) demonstrates one such design; it accepts multiple tokens and routes fee payments as private notes. ### How FPCs work[​](#how-fpcs-work "Direct link to How FPCs work") An FPC acts as a fee payer on the user's behalf. Simpler FPCs (like the Sponsored FPC) just call `set_as_fee_payer()` with no user payment at all. More sophisticated FPCs accept user tokens in exchange for paying Fee Juice. A common pattern for quote-based FPCs works as follows: 1. **Quote.** The user requests a fee quote from the FPC operator, specifying the token they want to pay with and the estimated gas cost. The operator signs the quote, binding it to the user, asset, amounts, and an expiry. 2. **Authorization.** The user creates an [authentication witness](/developers/docs/foundational-topics/advanced/authwit.md) authorizing the FPC to transfer tokens from their balance. This is the same authwit mechanism used for any delegated token transfer. 3. **Setup phase (non-revertible).** The FPC's entrypoint runs during the transaction's [setup phase](/developers/docs/foundational-topics/transactions.md#setup-phase-non-revertible). It verifies the quote, collects the user's payment, declares itself as the fee payer via `set_as_fee_payer()`, and calls `end_setup()` to mark the boundary between non-revertible and revertible execution. Because this runs in the non-revertible phase, the payment is **irrevocably committed** before application logic executes: the user pays regardless of whether the app-logic phase reverts. 4. **App phase (revertible).** The user's actual transaction logic runs here. In the common fee-entrypoint flow the FPC is not involved in this phase, though some FPC designs (such as cold-start flows) perform additional app-phase work like claiming bridged tokens. Setup-phase allowlist Setup-phase execution is restricted by a protocol-level allowlist of permitted public function calls. Public token functions such as `transfer_in_public` and `_increase_public_balance` have been removed from the default allowlist; custom FPCs may only call protocol-contract setup functions (for example those on `AuthRegistry` and `FeeJuice`). See the [migration note](/developers/docs/resources/migration_notes.md#custom-token-fpcs-removed-from-default-public-setup-allowlist) for details. Key properties for developers integrating with an FPC: * **Authwit scope.** The authwit authorizes a specific action hash (typically covering the transfer amount). A nonce can be included when otherwise-identical actions need to be distinguishable. The authwit is single-use, consumed by a nullifier onchain. * **Token interface.** On networks that use the default setup allowlist, an FPC cannot call arbitrary public token functions during setup (see the callout above). Fee collection for quote-based FPCs therefore typically happens in the private domain: the user privately transfers an agreed amount to the FPC (or its operator) under the authwit from step 2, and the FPC separately declares itself the fee payer during setup using only protocol-contract calls. Any token that implements the standard Aztec token interface with authwit verification is compatible with this private-note pattern. * **Gas estimation first.** The Fee Juice amount in the quote is typically derived from the transaction's gas estimate. Simulate and estimate gas *before* requesting a quote, then pass the estimated cost to the FPC operator. * **Quote expiry.** Quotes are time-bound and single-use. Fetch a fresh quote per transaction. * **Cold-start variant.** Some FPCs offer a cold-start entrypoint where a brand-new account can bridge tokens from L1, claim them on L2, and pay the fee in one transaction, with no prior L2 balance or authwit needed, because the FPC itself claims and distributes the bridged tokens. The user still needs L1 tokens and ETH for the initial bridge transaction. Fee payments themselves can also be made private via a fully private FPC that holds Fee Juice internally and nominates itself as the fee payer during the setup phase, without revealing who initiated the transaction. See [Pay Fees Privately](/developers/docs/aztec-js/how_to_use_private_fee_juice.md) for how this pattern works and an example implementation. ### Teardown phase[​](#teardown-phase "Direct link to Teardown phase") Transactions can optionally have a "teardown" phase as part of their public execution, during which the "transaction fee" is available to public functions. This is useful to transactions/contracts that need to compute a "refund", e.g. contracts that facilitate fee abstraction. This enables FPCs to calculate the actual transaction cost and refund any overpayment to the user. Not all FPC designs use the teardown phase; some charge a fixed quoted amount with no refund, keeping unused Fee Juice in the FPC's balance for future transactions. ### Operator rewards[​](#operator-rewards "Direct link to Operator rewards") The calculated fee of a transaction is deducted from the fee payer (nominated account or fee-paying contract), then pooled together across transactions, blocks, and epochs. Once an epoch is proven, the total collected fees (minus any burnt congestion amount) are distributed to the provers and block proposers that contributed to the epoch. ## Next steps[​](#next-steps "Direct link to Next steps") For a guide on paying fees programmatically, including how to bridge Fee Juice from L1, see [How to Pay Fees](/developers/docs/aztec-js/how_to_pay_fees.md). --- # Private Execution Environment (PXE) This page describes the Private Execution Environment (PXE, pronounced "pixie"), a client-side library for the execution of private operations. It is a TypeScript library that can be run within Node.js, inside wallet software or a browser. The PXE generates proofs of private function execution, and sends these proofs along with public function execution requests to the sequencer. Private inputs never leave the client-side PXE. The PXE is responsible for: * storing secrets (e.g. encryption keys, notes, tagging secrets for note discovery) and exposing an interface for safely accessing them * orchestrating private function (circuit) execution and proof generation, including implementing [oracles](/developers/docs/aztec-nr/framework-description/advanced/protocol_oracles.md) needed for transaction execution * syncing users' relevant network state, obtained from an Aztec node * safely handling multiple accounts with siloed data and permissions One PXE can handle data and secrets for multiple accounts, while also providing isolation between them as required. ## System architecture[​](#system-architecture "Direct link to System architecture") Privacy consideration When the PXE queries the node for world state (e.g., to check if a nullifier exists), the node learns which data the user is interested in. This is a known tradeoff—users can mitigate this by running their own node. ## Components[​](#components "Direct link to Components") ### Contract Function Simulator[​](#contract-function-simulator "Direct link to Contract Function Simulator") An application prompts the user's PXE to execute a transaction (e.g. execute function X with arguments Y from account Z). The application or wallet may handle gas estimation. The contract function simulator handles execution of smart contract functions by simulating transactions. It generates the required data and inputs for these functions, including partial witnesses and public inputs. Until simulated simulations are implemented ([#9133](https://github.com/AztecProtocol/aztec-packages/issues/9133)), authentication witnesses are required for simulation before proving. ### Proof Generation[​](#proof-generation "Direct link to Proof Generation") After simulation, the wallet calls `proveTx` on the PXE with all of the data generated during simulation and any [authentication witnesses](/developers/docs/foundational-topics/advanced/authwit.md) (for allowing contracts to act on behalf of the user's account contract). Once proven, the wallet sends the transaction to the network and sends the transaction hash back to the application. ### Database[​](#database "Direct link to Database") The PXE database stores various types of data locally: * **Notes**: Data representing users' private state. Notes are stored onchain as encrypted logs. Once discovered via [note tagging](/developers/docs/foundational-topics/advanced/storage/note_discovery.md), notes are decrypted and stored locally in the PXE. * **Authentication Witnesses**: Data used to approve others for executing transactions on your behalf. The PXE provides this data to transactions on-demand during transaction simulation via oracles. * **Capsules**: Per-contract non-volatile local storage for caching computation results and persisting data across transactions. See [Using Capsules](/developers/docs/aztec-nr/framework-description/advanced/how_to_use_capsules.md) for more details. * **Address Book**: Complete addresses (address + public keys) for registered accounts and known senders. This enables the PXE to sync private logs tagged with registered sender addresses. Note discovery is handled by Aztec contracts, not the PXE. This allows users to customize or update their note discovery mechanism as needed. ### Contract management[​](#contract-management "Direct link to Contract management") Applications can add contract code required for a user to interact with the application to the user's PXE. The PXE will check whether the required contracts have already been registered. There are no getters to check whether a contract has been registered, as this could leak privacy (e.g. a dapp could check whether specific contracts have been registered in a user's PXE and infer information about their interaction history). ### Keystore[​](#keystore "Direct link to Keystore") The keystore securely stores cryptographic keys for registered accounts, including: * **Nullifier keys**: Used to create nullifiers that invalidate notes when spent * **Incoming viewing keys**: Used to decrypt notes sent to the account * **Outgoing viewing keys**: Used to decrypt notes sent by the account * **Tagging keys**: Used for note discovery via the tagging protocol ### Oracles[​](#oracles "Direct link to Oracles") Oracles are pieces of data that are injected into a smart contract function from the client side. Learn more about [how oracles work](/developers/docs/aztec-nr/framework-description/advanced/protocol_oracles.md). ## Oracle versioning[​](#oracle-versioning "Direct link to Oracle versioning") The set of oracles that the PXE exposes to private and utility functions is versioned, so that contracts can declare which oracles they expect to be available. Every contract compiled with `Aztec.nr` records the oracle version it was built against, and the PXE checks this version before executing any oracle call. The version uses two components, `major.minor`, with the following compatibility rules: * **`major`** must match exactly. A major bump is a breaking change — oracles were removed or their signatures changed — and a PXE on a different major cannot safely run the contract. * **`minor`** indicates additive changes (new oracles). The PXE uses a best-effort approach here: a contract compiled against a higher `minor` than the PXE supports is still allowed to run, and an error is only thrown if the contract actually invokes an oracle the PXE does not know about. In practice, a contract built with a newer Aztec.nr may not use any of the newly added oracles at all, in which case it runs fine on an older PXE. The canonical version constants live in the PXE (`ORACLE_VERSION_MAJOR` / `ORACLE_VERSION_MINOR` in `yarn-project/pxe/src/oracle_version.ts`) and in Aztec.nr (`noir-projects/aztec-nr/aztec/src/oracle/version.nr`). The two are kept in lockstep as part of each release. ### Resolving a version mismatch[​](#resolving-a-version-mismatch "Direct link to Resolving a version mismatch") If you see an error like *"Oracle '…' not found. … The contract was compiled with Aztec.nr oracle version X.Y, but this private execution environment only supports up to A.B"*, the contract uses one or more oracles from a newer Aztec.nr than your PXE supports. To fix it, upgrade the software that ships the PXE (sandbox, wallet, or whatever embeds `@aztec/pxe`) to a release whose Aztec.nr version is at least as new as the one the contract was compiled with. If the PXE reports a version that *should* include every oracle the contract needs but an oracle is still missing, that is a contract bug rather than a version problem and you should likely report it to the app developer. ## For developers[​](#for-developers "Direct link to For developers") To learn how to develop on top of the PXE, refer to these guides: * [Using capsules for local storage](/developers/docs/aztec-nr/framework-description/advanced/how_to_use_capsules.md) * [Using oracles in smart contracts](/developers/docs/aztec-nr/framework-description/advanced/protocol_oracles.md) * [Authentication witnesses](/developers/docs/foundational-topics/advanced/authwit.md) ## Next steps[​](#next-steps "Direct link to Next steps") * [Wallets](/developers/docs/foundational-topics/wallets.md) - Learn how wallets interact with the PXE * [State management](/developers/docs/foundational-topics/state_management.md) - Understand how private state is managed * [Note discovery](/developers/docs/foundational-topics/advanced/storage/note_discovery.md) - Learn how notes are discovered and synced --- # State Management Aztec has a hybrid public/private state model. Contract developers can specify which data is public and which is private, as well as the functions that operate on that data. Private and public data are stored in two separate trees: a **public data tree** and a **note hashes tree**. Both trees store state for all accounts on the network directly as leaves, unlike Ethereum where a state trie contains smaller tries for individual accounts. This means storage must be carefully allocated to prevent collisions. Storage is *siloed* to each contract, though the exact siloing mechanism differs slightly between public and private storage. ## Public State[​](#public-state "Direct link to Public State") Public state in Aztec works similarly to other blockchains. It is transparent and managed by smart contract logic. The sequencer stores and updates public state. It executes state transitions, generates proofs of correct execution (or delegates to the prover network), and publishes data to Ethereum. ## Private State[​](#private-state "Direct link to Private State") Private state is encrypted and owned by users who hold the decryption keys. It uses an append-only data structure since updating records directly would leak information about the transaction graph. To "delete" private state, you add an associated nullifier to a nullifier set. The nullifier is computed such that observers cannot link a state record to its nullifier without the owner's keys. Modifying state is accomplished by nullifying the existing record and creating a new one. This gives private state an intrinsic UTXO (unspent transaction output) structure. ## Notes[​](#notes "Direct link to Notes") Private state uses UTXOs, commonly called **notes**. Notes are encrypted pieces of data that only their owner can decrypt. ### How Notes Work[​](#how-notes-work "Direct link to How Notes Work") In Ethereum's account-based model, each account maps to a specific storage location. In Aztec's UTXO model, notes specify their owner and have no fixed relationship between accounts and data locations. Rather than storing entire notes, the protocol stores **note commitments** (hashes) in a Merkle tree called the note hash tree. Users prove they know the note preimage when updating private state. When a note is consumed, Aztec creates a nullifier from the note data and may create new notes with updated information. This decouples the actions of creating, updating, and deleting private state. ![](/assets/ideal-img/public-and-private-state-diagram.8ac73af.640.png) Notes work like cash. To spend a 5 dollar note on a $3.50 purchase, you nullify the $5 note and create two new notes: $1.50 for yourself and $3.50 for the recipient. Only you and the recipient know about the $3.50 transfer. ### Sending Notes[​](#sending-notes "Direct link to Sending Notes") When creating notes for a recipient, you need a way to deliver them: **Onchain (encrypted logs):** The standard method. Emit an encrypted log as part of your transaction. The encrypted note data is posted onchain, allowing recipients to find notes through [note discovery](/developers/docs/foundational-topics/advanced/storage/note_discovery.md). **Offchain:** If you know the recipient directly, share the note data with them. They store it in their PXE and can spend it later. **Self-created notes:** Notes you create for yourself don't need broadcasting. Store them in your PXE to prove ownership and spend them later. ### Abstracting Notes[​](#abstracting-notes "Direct link to Abstracting Notes") Users don't need to think about individual notes. The Aztec.nr library abstracts notes by letting developers define custom note types that specify how notes are created, nullified, transferred, and displayed. Aztec.nr also handles [note discovery](/developers/docs/foundational-topics/advanced/storage/note_discovery.md) for notes encrypted to a user's account. ## Technical Details[​](#technical-details "Direct link to Technical Details") ### Storage Slots[​](#storage-slots "Direct link to Storage Slots") Public storage uses literal storage slots. Private storage uses logical storage slots that associate multiple notes together. See [storage slots](/developers/docs/foundational-topics/advanced/storage/storage_slots.md) for details. ### Contract Address Siloing[​](#contract-address-siloing "Direct link to Contract Address Siloing") The contract address is included when computing note hashes to ensure different contracts don't produce identical hashes. The protocol handles this automatically. ### Note Types[​](#note-types "Direct link to Note Types") Aztec.nr provides several note types: * **`PrivateSet`** - A collection of notes, useful for balances represented as multiple value notes * **`PrivateMutable`** - A single note representing one value that can be replaced * **`PrivateImmutable`** - A single note that cannot be changed after initialization These state variables must be wrapped in an `Owned<>` type that specifies the note owner. The `Owned<>` wrapper binds a note collection to a specific owner address, ensuring notes are correctly associated with their owner for nullifier computation and access control. ``` #[storage] struct Storage { balance: Owned, Context>, } ``` Notes can also be custom types storing any values your application needs. Use the `#[note]` macro for standard notes or `#[custom_note]` for notes requiring custom hash or nullifier computation. ### Built-in Note Types[​](#built-in-note-types "Direct link to Built-in Note Types") **`UintNote`** - Stores a numeric value (`u128`). Supports partial notes for scenarios where the value is determined in public execution. uint\_note\_def ``` #[derive(Deserialize, Eq, Serialize, Packable)] #[custom_note] pub struct UintNote { /// The number stored in the note. pub value: u128, } ``` > [Source code: noir-projects/aztec-nr/uint-note/src/uint\_note.nr#L26-L33](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/aztec-nr/uint-note/src/uint_note.nr#L26-L33) **`FieldNote`** - Stores a single `Field` value. ### Creating and Destroying Notes[​](#creating-and-destroying-notes "Direct link to Creating and Destroying Notes") The [lifecycle module](https://github.com/AztecProtocol/aztec-packages/tree/v4.3.1/noir-projects/aztec-nr/aztec/src/note/lifecycle.nr) contains functions for note management: * `create_note` - Creates a new note, computing its hash and pushing it to the context * `destroy_note` - Nullifies a note by computing and emitting its nullifier Notes created and nullified within the same transaction are called **transient notes**. The kernel circuits automatically squash these, avoiding unnecessary tree insertions and improving efficiency. ### Note Interface[​](#note-interface "Direct link to Note Interface") Notes must implement the `NoteHash` trait from [note\_interface.nr](https://github.com/AztecProtocol/aztec-packages/tree/v4.3.1/noir-projects/aztec-nr/aztec/src/note/note_interface.nr): * `compute_note_hash(self, owner, storage_slot, randomness)` - Computes the note's commitment * `compute_nullifier(self, context, owner, note_hash_for_nullification)` - Computes the nullifier for consumption * `compute_nullifier_unconstrained(self, owner, note_hash_for_nullification)` - Unconstrained nullifier computation The `#[note]` macro generates default implementations using `poseidon2_hash_with_separator`. ### Reading Notes[​](#reading-notes "Direct link to Reading Notes") Only users with appropriate keys can read private values they have permission to access. Notes can be read offchain without modifying onchain state. When reading a note in a transaction, subsequent reads of the same note would reveal a link between transactions. To preserve privacy, notes read in transactions are typically "consumed" (nullified) and new notes created. With `PrivateSet`, a private variable's value can be interpreted as the sum of all notes at that storage slot. Nullifying is done by inserting a nullifier into the nullifier tree, not by deleting the note hash. ### Updating Notes[​](#updating-notes "Direct link to Updating Notes") To update a value, nullify the existing note hash(es) and insert a new note hash for the updated value. The PXE tracks note state locally while the note hash tree records the cryptographic commitments. ## Further Reading[​](#further-reading "Direct link to Further Reading") * [High level network architecture](/developers/docs/foundational-topics.md) * [Transaction lifecycle](/developers/docs/foundational-topics/transactions.md#simple-example-of-the-private-transaction-lifecycle) * [Storage slots](/developers/docs/foundational-topics/advanced/storage/storage_slots.md) * [Note discovery](/developers/docs/foundational-topics/advanced/storage/note_discovery.md) --- # Transactions On this page you'll learn: * The step-by-step process of sending a transaction on Aztec * The role of components like PXE, Aztec Node, and the sequencer * The private and public kernel circuits and how they execute function calls * The call stacks for private and public functions and how they determine a transaction's completion For a two-minute visual overview of how a single transaction spans private and public execution, watch this explainer (find more on the [video lessons](/developers/docs/resources/video_lessons.md) page): [One Transaction, Two Worlds: Private and Public State on Aztec](https://www.youtube-nocookie.com/embed/MayopgQ1FjI) ## Simple Example of the (Private) Transaction Lifecycle[​](#simple-example-of-the-private-transaction-lifecycle "Direct link to Simple Example of the (Private) Transaction Lifecycle") The transaction lifecycle for an Aztec transaction is fundamentally different from the lifecycle of an Ethereum transaction. The introduction of the Private eXecution Environment (PXE) provides a safe environment for the execution of sensitive operations, ensuring that decrypted data are not accessible to unauthorized applications. However, the PXE exists client-side on user devices, which creates a different model for imagining what the lifecycle of a typical transaction might look like. The existence of a sequencing network also introduces some key differences between the Aztec transaction model and the transaction model used for other networks. The accompanying diagram illustrates the flow of interactions between a user, their wallet, the PXE, the node operators (sequencers / provers), and the L1 chain. ![](/assets/ideal-img/transaction-lifecycle.266635e.640.png) 1. **The user initiates a transaction** – In this example, the user decides to privately send 10 DAI to gudcause.eth. After inputting the amount and the receiving address, the user clicks the confirmation button on their wallet. 2. **The PXE executes transfer locally** – The PXE, running locally on the user's device, executes the transfer method on the DAI token contract on Aztec and computes the state difference based on the user's intention. At this point, the transaction exists solely within the context of the PXE. 3. **The PXE proves correct execution** – The PXE proves correct execution (via zero-knowledge proofs) of the authorization and of the private transfer method. Once the proofs have been generated, the PXE sends the proofs and required inputs (new note commitments and nullifiers) to the sequencer. 4. **The sequencer processes the transaction** – The pseudorandomly-selected sequencer validates the transaction proofs along with required inputs for this private transfer. The sequencer also executes public functions and updates state: public state is updated by directly modifying entries in the sparse Merkle tree, while private state is updated by adding the newly created note commitments and nullifiers to the indexed Merkle trees. The sequencer then computes the new state root and posts the block to L1. 5. **The transaction settles to L1** – The block is posted to L1, and later, provers submit epoch proofs to the verifier contract on Ethereum. Once the epoch proof is verified, the state transitions are considered final and the private transfer has settled. ### Detailed Diagram[​](#detailed-diagram "Direct link to Detailed Diagram") The following diagram provides a more detailed overview of the transaction execution process, highlighting three different types of transaction execution: contract deployments, private transactions, and public transactions. ![](/assets/ideal-img/local_network_sending_a_tx.48faac8.640.png) See the page on [call types](/developers/docs/foundational-topics/call_types.md) for more context on transaction execution. ### Transaction Requests[​](#transaction-requests "Direct link to Transaction Requests") Transaction requests are how transactions are constructed and sent to the network. In Aztec.js: constructor ``` constructor( /** Sender. */ public origin: AztecAddress, /** Pedersen hash of function arguments. */ public argsHash: Fr, /** Transaction context. */ public txContext: TxContext, /** Function data representing the function to call. */ public functionData: FunctionData, /** A salt to make the hash difficult to predict. The hash is used as the first nullifier if there is no nullifier emitted throughout the tx. */ public salt: Fr, ) {} ``` > [Source code: yarn-project/stdlib/src/tx/tx\_request.ts#L15-L28](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/yarn-project/stdlib/src/tx/tx_request.ts#L15-L28) Where: * `origin` is the account contract where the transaction is initiated from. * `argsHash` is the hash of the arguments of the entrypoint call. The complete set of arguments is passed to the PXE as part of the `TxExecutionRequest` and checked against this hash. * `txContext` contains the chain id, version, and gas settings. * `functionData` contains the function selector and indicates whether the function is private or public. * `salt` is used to make the transaction request hash difficult to predict. The hash is used as the first nullifier if no nullifier is emitted throughout the transaction. The `TxExecutionRequest` class: tx\_execution\_request\_class ``` export class TxExecutionRequest { constructor( /** * Sender. */ public origin: AztecAddress, /** * Selector of the function to call. */ public functionSelector: FunctionSelector, /** * The hash of arguments of first call to be executed (usually account entrypoint). * @dev This hash is a pointer to `argsOfCalls` unordered array. */ public firstCallArgsHash: Fr, /** * Transaction context. */ public txContext: TxContext, /** * An unordered array of packed arguments for each call in the transaction. * @dev These arguments are accessed in Noir via oracle and constrained against the args hash. The length of * the array is equal to the number of function calls in the transaction (1 args per 1 call). */ public argsOfCalls: HashedValues[], /** * Transient authorization witnesses for authorizing the execution of one or more actions during this tx. * These witnesses are not expected to be stored in the local witnesses database of the PXE. */ public authWitnesses: AuthWitness[], /** * Read-only data passed through the oracle calls during this tx execution. */ public capsules: Capsule[], /** * A salt to make the tx request hash difficult to predict. * The hash is used as the first nullifier if there is no nullifier emitted throughout the tx. */ public salt = Fr.random(), ) {} ``` > [Source code: yarn-project/stdlib/src/tx/tx\_execution\_request.ts#L23-L64](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/yarn-project/stdlib/src/tx/tx_execution_request.ts#L23-L64) An account contract validates that the transaction request has been authorized via its specified authorization mechanism, via the `is_valid_impl` function. Here is an example using an ECDSA signature: is\_valid\_impl ``` #[contract_library_method] fn is_valid_impl(context: &mut PrivateContext, outer_hash: Field) -> bool { // Load public key from storage let storage = Storage::init(context); let public_key = storage.signing_public_key.get_note(); // Safety: The witness is only used as a "magical value" that makes the signature verification below pass. // Hence it's safe. let signature: [u8; 64] = unsafe { get_auth_witness_as_bytes(outer_hash) }; // Verify payload signature using Ethereum's signing scheme // Note that noir expects the hash of the message/challenge as input to the ECDSA verification. let outer_hash_bytes: [u8; 32] = outer_hash.to_be_bytes(); let hashed_message: [u8; 32] = sha256::digest(outer_hash_bytes); std::ecdsa_secp256k1::verify_signature(public_key.x, public_key.y, signature, hashed_message) } ``` > [Source code: noir-projects/noir-contracts/contracts/account/ecdsa\_k\_account\_contract/src/main.nr#L59-L76](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/account/ecdsa_k_account_contract/src/main.nr#L59-L76) Transaction requests are simulated in the PXE in order to generate the necessary inputs for generating proofs. Once transactions are proven, a `Tx` object is created and can be sent to the network to be included in a block: tx\_class ``` export class Tx extends Gossipable { static override p2pTopic = TopicType.tx; private calldataMap: Map | undefined; constructor( /** * Identifier of the tx. * It's a hash of the public inputs of the tx's proof. * This claimed hash is reconciled against the tx's public inputs (`this.data`) in data_validator.ts. */ public readonly txHash: TxHash, /** * Output of the private kernel circuit for this tx. */ public readonly data: PrivateKernelTailCircuitPublicInputs, /** * Proof from the private kernel circuit. */ public readonly chonkProof: ChonkProof, /** * Contract class log fields emitted from the tx. * Their order should match the order of the log hashes returned from `this.data.getNonEmptyContractClassLogsHashes`. * This claimed data is reconciled against a hash of this data (that is contained within * the tx's public inputs (`this.data`)), in data_validator.ts. */ public readonly contractClassLogFields: ContractClassLogFields[], /** * An array of calldata for the enqueued public function calls and the teardown function call. * This claimed data is reconciled against hashes of this data (that are contained within * the tx's public inputs (`this.data`)), in data_validator.ts. */ public readonly publicFunctionCalldata: HashedValues[], ) { super(); } ``` > [Source code: yarn-project/stdlib/src/tx/tx.ts#L27-L64](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/yarn-project/stdlib/src/tx/tx.ts#L27-L64) #### Contract Interaction Methods[​](#contract-interaction-methods "Direct link to Contract Interaction Methods") Most transaction requests are created as interactions with specific contracts. The exception is transactions that deploy contracts. Here are the main methods for interacting with contracts related to transactions. 1. [`simulate`](#simulate) 2. [`send`](#send) ##### `simulate`[​](#simulate "Direct link to simulate") simulate ``` /** * Simulate a transaction and get information from its execution. * Differs from prove in a few important ways: * 1. It returns the values of the function execution, plus additional metadata if requested * 2. It supports `utility`, `private` and `public` functions * * @param options - An optional object containing additional configuration for the simulation. * @returns Depending on the simulation options, this method directly returns the result value of the executed * function or a rich object containing extra metadata, such as estimated gas costs (if requested via options), * execution statistics and emitted offchain effects */ public async simulate( options: SimulateInteractionOptions = {} as SimulateInteractionOptions, ): Promise { ``` > [Source code: yarn-project/aztec.js/src/contract/contract\_function\_interaction.ts#L115-L130](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/yarn-project/aztec.js/src/contract/contract_function_interaction.ts#L115-L130) ##### `send`[​](#send "Direct link to send") send ``` /** * Sends a transaction to the contract function with the specified options. * By default, waits for the transaction to be mined and returns the receipt (or custom type). * @param options - An object containing 'from' property representing * the AztecAddress of the sender, optional fee configuration, and optional wait settings * @returns TReturn (if wait is undefined/WaitOpts) or TxHash (if wait is NO_WAIT) */ // Overload for when wait is not specified at all - returns { receipt: TReturn, offchainEffects } public send(options: SendInteractionOptionsWithoutWait): Promise>; // Generic overload for explicit wait values // eslint-disable-next-line jsdoc/require-jsdoc public send( options: SendInteractionOptions, ): Promise>; // eslint-disable-next-line jsdoc/require-jsdoc public async send( options: SendInteractionOptions, ): Promise> { ``` > [Source code: yarn-project/aztec.js/src/contract/base\_contract\_interaction.ts#L37-L56](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/yarn-project/aztec.js/src/contract/base_contract_interaction.ts#L37-L56) ### Batch Transactions[​](#batch-transactions "Direct link to Batch Transactions") Batched transactions are a way to send multiple transactions in a single call. They are created by the `BatchCall` class in Aztec.js. This allows a batch of function calls from a single wallet to be sent as a single transaction through a wallet. batch\_call\_class ``` export class BatchCall extends BaseContractInteraction { constructor( wallet: Wallet, protected interactions: (BaseContractInteraction | ExecutionPayload)[], ) { super(wallet); } ``` > [Source code: yarn-project/aztec.js/src/contract/batch\_call.ts#L16-L24](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/yarn-project/aztec.js/src/contract/batch_call.ts#L16-L24) ### Enabling Transaction Semantics[​](#enabling-transaction-semantics "Direct link to Enabling Transaction Semantics") There are two kernel circuits in Aztec, the private kernel and the public kernel. Each circuit validates the correct execution of a particular function call. A transaction is built up by generating proofs for multiple recursive iterations of kernel circuits. Each call in the call stack is modeled as a new iteration of the kernel circuit and is managed by a [FIFO](https://en.wikipedia.org/wiki/FIFO_\(computing_and_electronics\)) queue containing pending function calls. There are two call stacks, one for private calls and one for public calls. One iteration of a kernel circuit will pop a call off of the stack and execute the call. If the call triggers subsequent contract calls, these are pushed onto the stack. Private kernel proofs are generated first. The transaction is ready to move to the next phase when the private call stack is empty. The public kernel circuit takes in proof of a public/private kernel circuit with an empty private call stack, and operates recursively until the public call stack is also empty. A transaction is considered complete when both call stacks are empty. The only information leaked about the transaction is: 1. The number of private state updates triggered 2. The set of public calls generated The addresses of all private calls are hidden from observers. ## Transaction phases[​](#transaction-phases "Direct link to Transaction phases") An Aztec transaction is split into up to three phases at execution time. The boundaries matter mostly when integrating with fee-paying contracts (FPCs): which phase a call runs in determines whether it can revert, which public functions it is allowed to call, and when its side effects become final. ### Setup phase (non-revertible)[​](#setup-phase-non-revertible "Direct link to Setup phase (non-revertible)") The setup phase runs before the user's application logic. Fee-related bookkeeping happens here: * The fee payer is nominated via a call to the protocol's `set_as_fee_payer()` function. An FPC typically calls this in its entrypoint; a user paying directly with Fee Juice does it implicitly via the default entrypoint. * `end_setup()` is called to mark the boundary between the non-revertible and revertible phases. Everything committed before `end_setup()` stands regardless of whether later phases revert. Because the setup phase is non-revertible, the protocol restricts which public function calls are allowed during it. The default allowlist permits only protocol-contract setup functions (for example those on `AuthRegistry` and `FeeJuice`); in v4.2.0, public token functions such as `transfer_in_public` and `_increase_public_balance` were removed from it. See the [migration note](/developers/docs/resources/migration_notes.md#custom-token-fpcs-removed-from-default-public-setup-allowlist) for details. Practical consequences: * A fee payment committed during setup is charged to the payer even if the app phase later reverts. * An FPC cannot collect payment by directly calling an arbitrary user token's public transfer during setup. It either works purely in the private domain, or relies on a token function the network operator has added to the allowlist. ### App phase (revertible)[​](#app-phase-revertible "Direct link to App phase (revertible)") The app phase runs the user's actual transaction logic. Private execution has already happened locally in the PXE before the transaction was submitted (producing the proof, nullifiers, and note commitments included with the transaction); what runs in this phase is the public call stack. It starts with the public calls that private execution enqueued, and grows as those public calls themselves enqueue further public calls. If any public call in this phase reverts, all state changes from the phase are discarded, but fees committed during setup are still paid. ### Teardown phase (optional)[​](#teardown-phase-optional "Direct link to Teardown phase (optional)") Transactions can optionally include a teardown phase after app execution. During teardown, the final transaction fee is available to public functions, which is useful for FPCs that want to refund unused gas to the user. Not every FPC uses teardown; some charge a fixed quoted amount with no refund, retaining any surplus in the FPC's Fee Juice balance. ## Next Steps[​](#next-steps "Direct link to Next Steps") * Learn about [accounts](/developers/docs/foundational-topics/accounts.md) and how they authorize transactions * Understand [state management](/developers/docs/foundational-topics/state_management.md) and how transaction effects are stored * Explore the [PXE](/developers/docs/foundational-topics/pxe.md) in more detail * Understand the [performance impact of kernel circuits](/developers/docs/foundational-topics/advanced/circuits/private_kernel.md#performance-impact) on proving time --- # Wallets This page covers the main responsibilities of a wallet in the Aztec network. Wallets are the applications through which users manage their accounts. Users rely on wallets to browse through their accounts, monitor their balances, and create new accounts. Wallets also store seed phrases and private keys, or interact with external keystores such as hardware wallets. Wallets also provide an interface for dapps. Dapps may request access to see the user accounts, in order to show the state of those accounts in the context of the application, and request to send transactions from those accounts as the user interacts with the dapp. In addition to these usual responsibilities, wallets in Aztec also need to track private state. This implies keeping a local database of all private notes encrypted for any of the user's accounts, so dapps and contracts can query the user's private state. Aztec wallets are also responsible for producing local proofs of execution for private functions. ## Account setup[​](#account-setup "Direct link to Account setup") The first step for any wallet is to let the user set up their [accounts](/developers/docs/foundational-topics/accounts.md). An account in Aztec is represented onchain by its corresponding account contract that the user must deploy to begin interacting with the network. This account contract dictates how transactions are authenticated and executed. A wallet must support at least one specific account contract implementation, which means being able to deploy such a contract, as well as interacting with it when sending transactions. Code-wise, this requires [implementing the `AccountContract` interface](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/yarn-project/aztec.js/src/account/account_contract.ts). Note that users must be able to receive funds in Aztec before deploying their account. A wallet should let a user generate a [deterministic complete address](/developers/docs/foundational-topics/accounts/keys.md#address-derivation) without having to interact with the network, so they can share it with others to receive funds. This requires that the wallet pins a specific contract implementation, its initialization arguments, a deployment salt, and the user's keys. These values yield a deterministic address, so when the account contract is actually deployed, it is available at the precalculated address. Once the account contract is deployed, the user can start sending transactions using it as the transaction origin. ## Transaction lifecycle[​](#transaction-lifecycle "Direct link to Transaction lifecycle") Every transaction in Aztec is broadcast to the network as a zero-knowledge proof of correct execution, in order to preserve privacy. This means that transaction proofs are generated on the wallet and not on a remote node. This is one of the biggest differences with regard to EVM chain wallets. A wallet is responsible for **creating** an *execution request* out of one or more *function calls* requested by a dapp. For example, a dapp may request a wallet to "invoke the `transfer` function on the contract at `0x1234` with the following arguments", in response to a user action. The wallet turns that into an execution request with the signed instructions to execute that function call from the user's account contract. In an [ECDSA-based account](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/account/ecdsa_k_account_contract/src/main.nr), for instance, this is an execution request that encodes the function call in the *entrypoint payload*, and includes its ECDSA signature with the account's signing private key. Once the *execution request* is created, the wallet is responsible for **simulating** and **proving** the execution of its private functions. The simulation yields an execution trace, which can be used to provide the user with a list of side effects of the private execution of the transaction. During this simulation, the wallet is responsible for providing data to the virtual machine, such as private notes, encryption keys, or nullifier secrets. This execution trace is fed into the prover, which returns a zero-knowledge proof that guarantees correct execution and hides all private information. The output of this process is a *transaction object*. info Private functions use a UTXO model, so their execution trace is determined entirely by the input notes. Since notes are immutable, simulation results match mined results exactly. However, a transaction may be dropped if it tries to consume a note that was nullified by another transaction first. Public functions use an account model (like Ethereum), so their execution trace depends on chain state at inclusion time, which may differ from simulation. Before sending, the wallet may run a **simulation** — a lightweight execution using a stub account contract that avoids expensive kernel circuit execution. This simulation estimates gas limits for the transaction and captures any required private authorization data (see [Authorizing actions](#authorizing-actions) below). The `EmbeddedWallet` runs this step automatically on every send. Finally, the wallet **sends** the resulting *transaction* object, which includes the proof of execution, to an Aztec Node. The transaction is then broadcasted through the peer-to-peer network, to be eventually picked up by a sequencer and included in a block. ## Authorizing actions[​](#authorizing-actions "Direct link to Authorizing actions") Account contracts in Aztec expose an interface for other contracts to validate [whether an action is authorized by the account or not](/developers/docs/foundational-topics/accounts.md#authentication-witnesses-authwit). For example, an application contract may want to transfer tokens on behalf of a user, in which case the token contract will check with the account contract whether the application is authorized to do so. These actions may be carried out in private or in public functions, and in transactions originated by the user or by someone else. Wallets should manage these authorizations, prompting the user when they are requested by an application. Authorizations in private executions come in the form of *auth witnesses*, which are usually signatures over an identifier for an action. Applications can request the wallet to produce an auth witness via the `createAuthWit` call. In public functions, authorizations are pre-stored in the account contract storage, which is handled by a call to an internal function in the account contract implementation. Wallets can automate private authorization by capturing authorization requests during simulation. The `EmbeddedWallet`, for example, detects which private authwits a transaction needs and generates them automatically, so dapps don't need to explicitly create or manage private authorizations. Public authorizations still require explicit setup, as they involve onchain state changes that must occur before the authorized action. ## Key management[​](#key-management "Direct link to Key management") As in EVM-based chains, wallets are expected to manage user keys, or provide an interface to hardware wallets or alternative key stores. Keep in mind that in Aztec each account requires [multiple key pairs](/developers/docs/foundational-topics/accounts/keys.md): protocol keys (nullifier and incoming viewing keys) are mandated by the protocol and used for spending notes and decryption, whereas signing keys are dependent on the account contract implementation rolled out by the wallet. Should the account contract support it, wallets must provide the user with the means to rotate or recover their signing keys. info Due to limitations in the current architecture, protocol keys need to be available in the wallet software itself and cannot be delegated to an external keystore. This restriction may be lifted in a future release. ## Recipient address management[​](#recipient-address-management "Direct link to Recipient address management") Wallets are also expected to manage the public encryption keys of any recipients of local transactions. When creating an encrypted note for a recipient given their address, the wallet needs to provide their [complete address](/developers/docs/foundational-topics/accounts/keys.md#address-derivation). Recipients broadcast their complete addresses when deploying their account contracts, and wallets collect this information and save it in a local registry for easy access when needed. Note that, in order to interact with a recipient who has not yet deployed their account contract (and thus not broadcasted their complete address), it must also be possible to manually add an entry to a wallet's local registry of complete addresses. ## Private state[​](#private-state "Direct link to Private state") Wallets also store the user's private state. Aztec uses a [note tagging system](/developers/docs/foundational-topics/advanced/storage/note_discovery.md) that allows users to efficiently discover notes that belong to them. When a note is created, the sender tags it with a value derived from a shared secret, allowing the recipient's wallet to query for relevant notes without attempting to decrypt every note on the network. Once discovered, notes are decrypted and added to the corresponding account's private state. To discover notes from a sender, the wallet must first register that sender's address with the PXE. This allows the wallet to compute the shared secrets needed for note tagging. Wallets typically register senders when users add contacts or interact with new counterparties. Wallets must also scan for private state in blocks prior to the deployment of a user's account contract, since users may have received notes before deployment. Private state can be encrypted and broadcast through the network, then committed to L1. While tags allow wallets to query the network for relevant notes, the tags themselves don't reveal the recipient - only the sender and recipient can compute and recognize them. This means wallets need to maintain a local database of their accounts' private state to answer queries efficiently. Dapps may require access to the user's private state, in order to show information relevant to the current application. For instance, a dapp for a token may require access to the user's private notes in the token contract in order to display the user's balance. It is the responsibility of the wallet to require authorization from the user before disclosing private state to a dapp. ## Account interface[​](#account-interface "Direct link to Account interface") The account interface is used for creating an *execution request* out of one or more *function calls* requested by a dapp, as well as creating an *auth witness* for a given message hash. Account contracts are expected to handle multiple function calls per transaction, since dapps may choose to batch multiple actions into a single request to the wallet. account-interface ``` /** * Minimal interface for transaction execution and authorization. */ export type Account = EntrypointInterface & AuthorizationProvider & { /** Returns the complete address for this account. */ getCompleteAddress(): CompleteAddress; /** Returns the address for this account. */ getAddress(): AztecAddress; }; ``` > [Source code: yarn-project/aztec.js/src/account/account.ts#L23-L34](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/yarn-project/aztec.js/src/account/account.ts#L23-L34) --- # Community Calls **Build with us, live.** Every week you can join office hours and ecosystem calls to get unblocked, learn from maintainers, and connect with other builders. Pick the call that fits your needs, add it to your calendar, and show up with questions. *** ## Ecosystem Call[​](#ecosystem-call "Direct link to Ecosystem Call") * **When:** Biweekly · Wednesdays · 16:00 - 16:45 UTC * **Where:** [Google Meet](https://meet.google.com/tnk-phse-bmz) * **For:** The Ecosystem Call is the place to be if you're building on Aztec. Get updates on libraries and devnet, watch demo apps, peek at what's coming next, and connect with the community. Building an app? Don't miss it. *** ## Aztec & Noir Developer Office Hours[​](#aztec--noir-developer-office-hours "Direct link to Aztec & Noir Developer Office Hours") * **When:** Thursdays · 14:00 - 15:00 UTC * **Where:** [Google Meet](https://meet.google.com/vev-waao-mab) * **For:** Developers building with Aztec.nr smart contracts or writing and debugging Noir. Bring your questions about syntax, tooling, patterns, or protocol-level topics. Share a project you're working on, or just hang out with the Aztec Labs Dev Rel team and other devs. *** ## Community[​](#community "Direct link to Community") * Follow discussions in the [Forum](https://forum.aztec.network) * Ask daily questions in [Discord](https://discord.gg/aztec) *** ## One calendar, all calls[​](#one-calendar-all-calls "Direct link to One calendar, all calls") Save time, add everything with one click: [Subscribe to the Aztec Builder Calendar](https://calendar.google.com/calendar/u/0?cid=Y19kMTdhMTEzNmU3NDEwNDNiNDJkMTZlYWU2ZDUzODg4YjlhYTVhNzA5NzNkNjVkNDU3YTA1ZTc3NWNhMGEwNWY5QGdyb3VwLmNhbGVuZGFyLmdvb2dsZS5jb20) --- # Limitations The Aztec stack is a work in progress. Packages have been released early to gather feedback on the capabilities of the protocol and user experiences. ## What to expect[​](#what-to-expect "Direct link to What to expect") * Regular breaking changes * Missing features * Bugs * An "unpolished" UX * Missing information ## Why participate[​](#why-participate "Direct link to Why participate") Front-run the future! Help shape and define: * Previously-impossible smart contracts and applications * Network tooling * Network standards * Smart contract syntax * Educational content * Core protocol improvements ## Limitations developers need to know about[​](#limitations-developers-need-to-know-about "Direct link to Limitations developers need to know about") * The Aztec stack is unaudited and under active development. See the [Alpha Network](/participate/alpha.md) page for details on what this means. * `msg_sender` is leaked by default when making private -> public calls. * `self.enqueue(...)` sets `msg_sender` to the private caller's address, which is publicly visible. * Use `self.enqueue_incognito(...)` to hide the sender. The called public function must use `maybe_msg_sender()` instead of `msg_sender()` to handle the null sender. * The initial `msg_sender` is `-1`, which can be problematic for some contracts. * Some side-effect counts are still visible in a transaction. Note hashes, nullifiers, and private logs are padded to hide their true counts, but the number of public function calls and L2->L1 messages remains visible. Privacy sets to further reduce leakage are still under development. * A transaction can only emit a limited number of side-effects (notes, nullifiers, logs, L2->L1 messages). See [circuit limitations](#circuit-limitations). * We have not settled on the final constants, since we are still in a testing phase. You could find that certain compositions of nested private function calls (for example, call stacks that are dynamic in size, based on runtime data) could accumulate so many side-effects as to exceed transaction limits. Such transactions would then be unprovable. Please open an issue if you encounter this, as it will help us decide on adequate sizes for our constants. * Not all Noir cryptographic primitives work in public (AVM) functions. Signature verification (ECDSA secp256k1/r1), AES-128, Blake2s, and Blake3 are not supported. See [AVM Cryptographic Compatibility](/developers/docs/foundational-topics/advanced/circuits/avm_compatibility.md) for details and workarounds. * There are many features that we still want to implement. Check out GitHub and the forum for details. If you would like a feature, please open an issue on GitHub. ## WARNING[​](#warning "Direct link to WARNING") Do not use real, meaningful secrets on Aztec networks. Some privacy features are still in development, including ensuring a secure "zk" property. Since the Aztec stack is still being developed, there are no guarantees that real secrets will remain secret. ## Limitations[​](#limitations "Direct link to Limitations") There are plans to resolve all of the below. ### It is not audited[​](#it-is-not-audited "Direct link to It is not audited") None of the Aztec stack is audited. It is being iterated on every day. It will not be audited for quite some time. ### Under-constrained[​](#under-constrained "Direct link to Under-constrained") Some of our more complex circuits are still in development, so they are still under-constrained. #### What are the consequences?[​](#what-are-the-consequences "Direct link to What are the consequences?") Sound proofs are really only needed as a protection against malicious behavior, which we are not testing for at this stage. ### Keys and addresses may change in future rollup versions[​](#keys-and-addresses-may-change-in-future-rollup-versions "Direct link to Keys and addresses may change in future rollup versions") The key derivation scheme is documented and stable within the current rollup version, but it may change in future rollup upgrades. Applications should not hardcode assumptions about the specific derivation algorithm. Please open new discussions on [Discourse](https://discourse.aztec.network) or open issues on [GitHub](https://github.com/AztecProtocol/aztec-packages) if you have requirements that are not being met by the current key derivation scheme. ### No privacy-preserving queries to nodes[​](#no-privacy-preserving-queries-to-nodes "Direct link to No privacy-preserving queries to nodes") Ethereum has a notion of a "full node" which keeps up with the blockchain and stores the full chain state. Many users do not wish to run full nodes, so they rely on third-party "full-node-as-a-service" infrastructure providers who service blockchain queries from their users. This pattern is likely to develop in Aztec as well, except there is a problem: privacy. If a privacy-seeking user makes a query to a third-party full node, that user might leak data about who they are, about their historical network activity, or about their future intentions. One solution to this problem is "always run a full node", but pragmatically, not everyone will. To protect less-advanced users' privacy, research is underway to explore how a privacy-seeking user may request and receive data from a third-party node without revealing what that data is, nor who is making the request. ### Limited private data authentication[​](#limited-private-data-authentication "Direct link to Limited private data authentication") The PXE supports a `scopes` parameter that restricts which accounts' notes a function call can access. However, this is caller-specified: the app chooses its own scopes. There is no mandatory, protocol-enforced authorization layer where the PXE denies an app access to another app's private data. A wallet can restrict scope on behalf of the user, but this is not yet standardized or enforced by default. ### No client-side bytecode validation[​](#no-client-side-bytecode-validation "Direct link to No client-side bytecode validation") Public bytecode is validated at the protocol level when contract classes are registered (the Contract Class Registry verifies encoding and commitments). However, the PXE and wallets do not yet validate that the bytecode a user is about to execute matches their stated intentions (function signature and contract address). #### What are the consequences?[​](#what-are-the-consequences-1 "Direct link to What are the consequences?") If incorrect or malicious bytecode is executed, it could read private data from another contract and emit it publicly. Client-side bytecode validation is planned to close this gap. ### Insecure hashes[​](#insecure-hashes "Direct link to Insecure hashes") We are planning a full assessment of the protocol's hashes, including rigorous domain separation. #### What are the consequences?[​](#what-are-the-consequences-2 "Direct link to What are the consequences?") Collisions and other hash-related attacks might be possible. This is unlikely to cause problems at this early stage, but is a known area of ongoing work. ### New privacy standards are required[​](#new-privacy-standards-are-required "Direct link to New privacy standards are required") There are many [patterns](/developers/docs/resources/considerations/privacy_considerations.md) which can leak privacy, even on Aztec. Standards have not been developed yet to encourage best practices when designing private smart contracts. #### What are the consequences?[​](#what-are-the-consequences-3 "Direct link to What are the consequences?") For example, until community standards are developed to reduce the uniqueness of ["Tx Fingerprints"](/developers/docs/resources/considerations/privacy_considerations.md#function-fingerprints-and-tx-fingerprints), app developers might accidentally forfeit some function privacy. ## Smart contract limitations[​](#smart-contract-limitations "Direct link to Smart contract limitations") We will never be done with all the features we want to add to Aztec.nr. We have many features that we still want to implement. Please check out GitHub and open new issues with any feature requests you might have. ## Circuit limitations[​](#circuit-limitations "Direct link to Circuit limitations") ### Upper limits on function outputs and transaction outputs[​](#upper-limits-on-function-outputs-and-transaction-outputs "Direct link to Upper limits on function outputs and transaction outputs") Due to the rigidity of zk-SNARK circuits, there are upper bounds on the amount of computation a circuit can perform, and on the amount of data that can be passed into and out of a function. > Blockchain developers are no stranger to restrictive computational environments. Ethereum has gas limits, local variable stack limits, call stack limits, contract deployment size limits, log size limits, etc. Here are the current constants: constants ``` // TREES RELATED CONSTANTS pub global ARCHIVE_HEIGHT: u32 = 30; // 4-second blocks for 100 years. pub global VK_TREE_HEIGHT: u32 = 7; pub global FUNCTION_TREE_HEIGHT: u32 = 7; // The number of private functions in a contract is therefore 128. pub global NOTE_HASH_TREE_HEIGHT: u32 = 42; // 64 notes/tx (static because of base rollup insertion), 15tps, for 100 years. pub global PUBLIC_DATA_TREE_HEIGHT: u32 = 40; // Average of 16 updates/tx (guess), 15tps, 100 years. pub global NULLIFIER_TREE_HEIGHT: u32 = NOTE_HASH_TREE_HEIGHT; pub global L1_TO_L2_MSG_TREE_HEIGHT: u32 = 36; // 1024 messages per checkpoint, with 72 seconds per checkpoint, for 100 years. pub global OUT_HASH_TREE_HEIGHT: u32 = 5; // 32 (MAX_CHECKPOINTS_PER_EPOCH) checkpoints per epoch, each has 1 out hash. pub global ARTIFACT_FUNCTION_TREE_MAX_HEIGHT: u32 = FUNCTION_TREE_HEIGHT; // The number of unconstrained functions in a contract. Set to equal the number of private functions in a contract. pub global NULLIFIER_TREE_ID: Field = 0; pub global NOTE_HASH_TREE_ID: Field = 1; pub global PUBLIC_DATA_TREE_ID: Field = 2; pub global L1_TO_L2_MESSAGE_TREE_ID: Field = 3; pub global ARCHIVE_TREE_ID: Field = 4; pub global NOTE_HASH_TREE_LEAF_COUNT: u64 = 1 << (NOTE_HASH_TREE_HEIGHT as u64); pub global L1_TO_L2_MSG_TREE_LEAF_COUNT: u64 = 1 << (L1_TO_L2_MSG_TREE_HEIGHT as u64); pub global OUT_HASH_TREE_LEAF_COUNT: u32 = 1 << OUT_HASH_TREE_HEIGHT; // SUB-TREES RELATED CONSTANTS pub global NOTE_HASH_SUBTREE_HEIGHT: u32 = 6; pub global NULLIFIER_SUBTREE_HEIGHT: u32 = 6; pub global PUBLIC_DATA_SUBTREE_HEIGHT: u32 = 6; pub global L1_TO_L2_MSG_SUBTREE_HEIGHT: u32 = 10; pub global NOTE_HASH_SUBTREE_ROOT_SIBLING_PATH_LENGTH: u32 = NOTE_HASH_TREE_HEIGHT - NOTE_HASH_SUBTREE_HEIGHT; pub global NULLIFIER_SUBTREE_ROOT_SIBLING_PATH_LENGTH: u32 = NULLIFIER_TREE_HEIGHT - NULLIFIER_SUBTREE_HEIGHT; pub global L1_TO_L2_MSG_SUBTREE_ROOT_SIBLING_PATH_LENGTH: u32 = L1_TO_L2_MSG_TREE_HEIGHT - L1_TO_L2_MSG_SUBTREE_HEIGHT; // Maximum number of subtrees a L2ToL1Msg unbalanced tree can have. Used when calculating the out hash of a tx. pub global MAX_L2_TO_L1_MSG_SUBTREES_PER_TX: u32 = 3; // ceil(log2(MAX_L2_TO_L1_MSGS_PER_TX)) // "PER TRANSACTION" CONSTANTS pub global MAX_NOTE_HASHES_PER_TX: u32 = 1 << NOTE_HASH_SUBTREE_HEIGHT; pub global MAX_NULLIFIERS_PER_TX: u32 = 1 << NULLIFIER_SUBTREE_HEIGHT; pub global MAX_PRIVATE_CALL_STACK_LENGTH_PER_TX: u32 = 16; pub global MAX_ENQUEUED_CALLS_PER_TX: u32 = 32; pub global PROTOCOL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX: u32 = 1; // This is the fee_payer's fee juice balance. pub global MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX: u32 = (1 as u8 << PUBLIC_DATA_SUBTREE_HEIGHT as u8) as u32; pub global MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX: u32 = MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX - PROTOCOL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX; pub global MAX_PUBLIC_DATA_READS_PER_TX: u32 = 64; pub global MAX_L2_TO_L1_MSGS_PER_TX: u32 = 8; // Leave at 8, because it results in sha256 hashing in the Tx Base Rollup pub global MAX_PRIVATE_LOGS_PER_TX: u32 = MAX_NOTE_HASHES_PER_TX; pub global MAX_CONTRACT_CLASS_LOGS_PER_TX: u32 = 1; pub global MAX_NOTE_HASH_READ_REQUESTS_PER_TX: u32 = 64; pub global MAX_NULLIFIER_READ_REQUESTS_PER_TX: u32 = 64; // Key validation requests are not only for app-siloed _nullifier_ secret keys: app-siloed tagging shared secrets might require this mechanism, // hence why it's higher than you might expect (roughly (but not quite) enough for a tagging shared secret per private log + 1 nsk). pub global MAX_KEY_VALIDATION_REQUESTS_PER_TX: u32 = MAX_PRIVATE_LOGS_PER_TX; // "PER CALL" CONSTANTS pub global MAX_NOTE_HASHES_PER_CALL: u32 = 16; pub global MAX_NULLIFIERS_PER_CALL: u32 = 16; pub global MAX_PRIVATE_CALL_STACK_LENGTH_PER_CALL: u32 = 8; pub global MAX_ENQUEUED_CALLS_PER_CALL: u32 = MAX_ENQUEUED_CALLS_PER_TX; pub global MAX_L2_TO_L1_MSGS_PER_CALL: u32 = MAX_L2_TO_L1_MSGS_PER_TX; pub global MAX_PRIVATE_LOGS_PER_CALL: u32 = MAX_NOTE_HASHES_PER_CALL; pub global MAX_CONTRACT_CLASS_LOGS_PER_CALL: u32 = 1; pub global MAX_NOTE_HASH_READ_REQUESTS_PER_CALL: u32 = 16; pub global MAX_NULLIFIER_READ_REQUESTS_PER_CALL: u32 = 16; pub global MAX_KEY_VALIDATION_REQUESTS_PER_CALL: u32 = MAX_PRIVATE_LOGS_PER_CALL; ``` > [Source code: noir-projects/noir-protocol-circuits/crates/types/src/constants.nr#L33-L100](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr#L33-L100) #### What are the consequences?[​](#what-are-the-consequences-4 "Direct link to What are the consequences?") When you write an Aztec.nr function, there will be upper bounds on the following: * The number of public state reads and writes; * The number of note reads and nullifications; * The number of new notes that may be created; * The number of encrypted logs that may be emitted; * The number of unencrypted logs that may be emitted; * The number of L1->L2 messages that may be consumed; * The number of L2->L1 messages that may be submitted to L1; * The number of private function calls; * The number of public function calls that may be enqueued; Not only are there limits on a *per function* basis, there are also limits on a *per transaction* basis. **In particular, these *per-transaction* limits will limit transaction call stack depths**. This means if a function call results in a cascade of nested function calls, and each of those function calls outputs many state reads and writes, or logs, then all of that accumulated output data might exceed the per-transaction limits that we currently have. This would cause such transactions to fail. There are plans to relax some of this rigidity by providing many "sizes" of circuit. > **In the meantime**, if you encounter a per-transaction limit when testing, please open an issue to explain what you were trying to do - we would love to hear about it. And if you are feeling adventurous, you could modify the PXE to increase the limits. **However**, the limits cannot be increased indefinitely. Although we do anticipate that we will be able to increase them slightly, do not provide yourself with 1 million state transitions per transaction. That would be as unrealistic as artificially increasing Ethereum gas limits to 1 trillion. ## There is more[​](#there-is-more "Direct link to There is more") See the [GitHub issues](https://github.com/AztecProtocol/aztec-packages/issues) for all known bug fixes and features currently being worked on. --- # Privacy Considerations Privacy is a core value of Aztec Protocol. Keeping information private is difficult, and once information is leaked, it cannot be unleaked. This page outlines key privacy considerations that developers should understand when building applications on Aztec. ## What can Aztec keep private?[​](#what-can-aztec-keep-private "Direct link to What can Aztec keep private?") Aztec provides a set of tools to enable developers to build private smart contracts. The following can be kept private: **Private persistent state** Store state variables in an encrypted form, so that no one can see what those variables are, except those with the decryption key. **Private events and messages** Emit encrypted events, or encrypted messages from a private smart contract function. Only those with the decryption key will learn the message. **Private function execution** Execute a private function without the world knowing which function you've executed. **Private bytecode** The bytecode of private functions does not need to be distributed to the world; much like real-world contracts. danger Privacy is not guaranteed without care. Although Aztec provides the tools for private smart contracts, information can still be leaked unless you are careful. Aztec is still under development, so real-world, meaningful, valuable secrets *should not* be entrusted to the system. This page outlines some best practices to help you build privacy-preserving applications. *** ## Leaky practices[​](#leaky-practices "Direct link to Leaky practices") There are many caveats to the above. Since Aztec also enables interaction with the *public* world (public L2 functions and L1 functions), private information can be accidentally leaked if developers aren't careful. ### Crossing the private to public boundary[​](#crossing-the-private-to-public-boundary "Direct link to Crossing the private to public boundary") Any time a private function makes a call to a public function, information is leaked. Now, that might be perfectly fine in some use cases (it's up to the smart contract developer). Indeed, most interesting apps will require some public state. But let's have a look at some leaky patterns: * Calling a public function from a private function. The public function execution will be publicly visible. * Calling a public function from a private function and revealing the `msg_sender` of that call (the `msg_sender` will be publicly visible). You can hide the sender by using `self.enqueue_incognito(...)` instead of `self.enqueue(...)`, which sets `msg_sender` to a null address. The called function must use `maybe_msg_sender()` to handle this. * Passing arguments to a public function from a private function. All of those arguments will be publicly visible. * Calling an internal public function from a private function. The fact that the call originated from a private function of that same contract will be trivially known. * Emitting unencrypted events from a private function. The unencrypted event name and arguments will be publicly visible. * Sending L2->L1 messages from a private function. The entire message, and the resulting L1 function execution will all be publicly visible. ### Crossing the public to private boundary[​](#crossing-the-public-to-private-boundary "Direct link to Crossing the public to private boundary") If a public function sends a message to be consumed by a private function, the act of consuming that message might be leaked if not following recommended patterns. ### Timing of transactions[​](#timing-of-transactions "Direct link to Timing of transactions") Information about the nature of a transaction can be leaked based on the timing of that transaction. If a transaction is executed at 8am GMT, it's much less likely to have been made by someone in the USA. If there's a spike in transactions on the last day of every month, those might be salaries. These minor details are information that can disclose much more information about a user than the user might otherwise expect. Suppose that every time Alice sends Bob a private token, 1 minute later a transaction is always submitted to the tx pool with the same kind of 'fingerprint'. Alice might deduce that these transactions are automated reactions by Bob. (Here, 'fingerprint' is an intentionally vague term. It could be a public function call, or a private tx proof with a particular number of nonzero public inputs, or some other discernible pattern that Alice sees). In short, you should think about the *timing* of user transactions and how this might leak information. ### Function Fingerprints and Tx Fingerprints[​](#function-fingerprints-and-tx-fingerprints "Direct link to Function Fingerprints and Tx Fingerprints") A 'Function Fingerprint' is any data which is exposed by a function to the outside world. A 'Tx Fingerprint' is any data which is exposed by a tx to the outside world. We're interested in minimizing leakages of information from private txs. The leakiness of a Tx Fingerprint depends on the leakiness of its constituent functions' Function Fingerprints *and* on the appearance of the tx's Tx Fingerprint as a whole. For a private function (and by extension, for a private tx), the following information *could* be leaked (depending on the function, of course): * All calls to public functions. * The contract address of the private function (if it calls an internal public function). * This could be the address of the transactor themselves, if the calling contract is an account contract. * All arguments which are passed to public functions. * All calls to L1 functions (in the form of L2 -> L1 messages). * The contents of L2 -> L1 messages. * All public logs (topics and arguments). * The roots of all trees which have been read from. * The *number* of some ['side effects'](https://en.wikipedia.org/wiki/Side_effect_\(computer_science\)). Note hashes, nullifiers, and private logs are padded to hide their true counts, but the following remain visible: * \# public function calls * \# L2->L1 messages > Note: many of these were mentioned in the ["Crossing the private to public boundary"](#crossing-the-private-to-public-boundary) section. > Note: a transaction's Tx Fingerprint is the combined set of publicly observable data listed above (for example: the number of public function calls, the number of L2->L1 messages, the contents of public logs, and which tree roots were read). Anyone watching the L2 transaction pool can see this fingerprint for every transaction that is submitted, and transactions with distinctive fingerprints can be linked to specific contracts or to patterns of user behavior. #### Standardizing Fingerprints[​](#standardizing-fingerprints "Direct link to Standardizing Fingerprints") If each private function were to have a unique Fingerprint, then all private functions would be distinguishable from each other, and all of the efforts of the Aztec Protocol to enable private function execution would have been pointless. Standards need to be developed to encourage smart contract developers to adhere to a restricted set of Tx Fingerprints. For example, a standard might propose that the number of new note hashes, nullifiers, logs, etc. must always be equal, and must always equal a power of two. Such a standard would effectively group private functions and transactions into "privacy sets," where all functions and transactions in a particular privacy set would look indistinguishable from each other when executed. ### Data queries[​](#data-queries "Direct link to Data queries") It's not just the broadcasting of transactions to the network that can leak data. Ethereum has a notion of a "full node" which keeps up with the blockchain and stores the full chain state. Many users don't wish to run full nodes, so they rely on third-party "full-node-as-a-service" infrastructure providers, who service blockchain queries from their users. This pattern is likely to develop in Aztec as well, except there's a problem: privacy. If a privacy-seeking user makes a query to a third-party full node, that user might leak data about who they are, their historical network activity, or their future intentions. One solution to this problem is to always run a full node, but pragmatically, not everyone will. To protect less-advanced users' privacy, research is underway to explore how a privacy-seeking user may request and receive data from a third-party node without revealing what that data is, nor who is making the request. You should be aware of this avenue for private data leakage. **Whenever an app requests information from a node, the entity running that node is unlikely to be your user.** #### What kind of queries can be leaky?[​](#what-kind-of-queries-can-be-leaky "Direct link to What kind of queries can be leaky?") ##### Querying for up-to-date note sibling paths[​](#querying-for-up-to-date-note-sibling-paths "Direct link to Querying for up-to-date note sibling paths") To read a private state is to read a note from the note hash tree. To read a note is to prove existence of that note in the note hash tree. And to prove existence is to re-compute the root of the note hash tree using the leaf value, the leaf index, and the sibling path of that leaf. This computed root is then exposed to the world, as a way of saying "This note exists", or more precisely "This note has existed at least since this historical snapshot time". If an old historical snapshot is used, then that old historical root will be exposed, and this leaks some information about the nature of your transaction: it leaks that your note was created before the snapshot date. It shrinks the 'privacy set' of the transaction to a smaller window of time than the entire history of the network. So for maximal privacy, it's in a user's best interest to read from the very-latest snapshot of the data tree. Naturally, the note hash tree is continuously changing as new transactions take place and their new notes are appended. Most notably, the sibling path for every leaf in the tree changes every time a new leaf is appended. If a user runs their own node, there's no problem: they can query the latest sibling path for their note(s) from their own machine without leaking any information to the outside world. But if a user is not running their own node, they would need to query the very-latest sibling path of their note(s) from some third-party node. In order to query the sibling path of a leaf, the leaf's index needs to be provided as an argument. Revealing the leaf's index to a third party trivially reveals exactly the note(s) you're about to read. And since those notes were created in some prior transaction, the third party will be able to link you with that prior transaction. Suppose then that the third party also serviced the creator of said prior transaction: they will slowly be able to link more and more transactions, and gain more and more insight into a network which is meant to be private. We're researching cryptographic ways to enable users to retrieve sibling paths from third parties without revealing leaf indices. > \* Note: due to the non-uniformity of Aztec transactions, the 'privacy set' of a transaction might not be the entire set of transactions that came before. ##### Any query[​](#any-query "Direct link to Any query") Any query to a node leaks information to that node. We're researching cryptographic ways to enable users to query any data privately. --- # Glossary ### ACIR (Abstract Circuit Intermediate Representation)[​](#acir-abstract-circuit-intermediate-representation "Direct link to ACIR (Abstract Circuit Intermediate Representation)") ACIR bytecode is the compilation target of private functions. ACIR expresses arithmetic circuits and has no control flow: any control flow in functions is either unrolled (for loops) or flattened (by inlining and adding predicates). ACIR contains different types of opcodes including arithmetic operations, BlackBoxFuncCall (for efficient operations like hashing), Brillig opcodes (for unconstrained hints), and MemoryOp (for dynamic array access). Private functions compiled to ACIR are executed by the ACVM (Abstract Circuit Virtual Machine) and proved using Barretenberg. ### AVM (Aztec Virtual Machine)[​](#avm-aztec-virtual-machine "Direct link to AVM (Aztec Virtual Machine)") The Aztec Virtual Machine (AVM) executes the public section of a transaction. It is conceptually similar to the Ethereum Virtual Machine (EVM) but designed specifically for Aztec's needs. Public functions are compiled to AVM bytecode and executed by sequencers in the AVM. The AVM uses a flat memory model with tagged memory indexes to track maximum potential values and bit sizes. It supports control flow (if/else) and includes specific opcodes for blockchain operations like timestamp and address access, but doesn't allow arbitrary oracles for security reasons. ### Aztec[​](#aztec "Direct link to Aztec") Aztec is a privacy-first Layer 2 rollup on Ethereum. It supports smart contracts with both private & public state and private & public execution. `aztec` is a CLI tool (with an extensive set of parameters) that enables users to perform a wide range of tasks. It can: compile and test contracts, run a node, run a local network, execute tests, generate contract interfaces for javascript and more. Full reference [here](/developers/docs/cli/aztec_cli_reference.md). ### Aztec Wallet[​](#aztec-wallet "Direct link to Aztec Wallet") The Aztec Wallet is a CLI wallet, `aztec-wallet`, that allows a user to manage accounts and interact with an Aztec network. It includes a PXE. Full reference [here](/developers/docs/cli/aztec_wallet_cli_reference.md). ### `aztec-up`[​](#aztec-up "Direct link to aztec-up") `aztec-up` updates the local aztec executables to the latest version (default behavior) or to a specified version. ### Aztec.js[​](#aztecjs "Direct link to Aztec.js") A [Node package](https://www.npmjs.com/package/@aztec/aztec.js) to help make Aztec dApps. Read more and review the source code [here](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/yarn-project/aztec.js). ### Aztec.nr[​](#aztecnr "Direct link to Aztec.nr") [Aztec.nr](https://github.com/AztecProtocol/aztec-packages/tree/v4.3.1/noir-projects/aztec-nr) is a Noir framework for writing Aztec smart contracts that abstracts away state management, handling note generation, state trees, and more. Read more and review the source code [here](https://aztec.nr). ### Barretenberg[​](#barretenberg "Direct link to Barretenberg") Aztec's cryptography back-end. Refer to the graphic at the top of [this page](https://medium.com/aztec-protocol/explaining-the-network-in-aztec-network-166862b3ef7d) to see how it fits in the Aztec architecture. Barretenberg's source code can be found [here](https://github.com/AztecProtocol/barretenberg). ### bb / bb.js[​](#bb--bbjs "Direct link to bb / bb.js") `bb` (CLI) and its corresponding `bb.js` (node module) are tools that prove and verify circuits. It also has helpful functions such as: writing solidity verifier contracts, checking a witness, and viewing a circuit's gate count. ### Commitment[​](#commitment "Direct link to Commitment") A cryptographic commitment is a hash of some data (plus randomness) that hides the original value but allows you to later prove you committed to that specific value, by proving knowledge of a valid preimage, without being able to change it. In Aztec, a commitment refers to a cryptographic hash of a note. Rather than storing entire notes in a data tree, note commitments (hashes of the notes) are stored in a merkle tree called the note hash tree. Users prove that they have the note pre-image information when they update private state in a contract. This allows the network to verify the existence of private data without revealing its contents. ### Merkle Tree[​](#merkle-tree "Direct link to Merkle Tree") A Merkle tree is a binary tree data structure where adjacent nodes are hashed together recursively to produce a single node called the root hash. Merkle trees in Aztec are used to store cryptographic commitments. They are used across five Aztec Merkle trees: the note hash tree (stores commitments to private notes), the nullifier tree (stores nullifiers for spent notes), the public data tree (stores public state), the contract tree, and the archive tree. All trees use domain-separated Poseidon2 hashing with specific tree identifiers and layer separation to ensure security and prevent cross-tree attacks. ### `nargo`[​](#nargo "Direct link to nargo") With `nargo`, you can start new projects, compile, execute, and test your Noir programs. The Aztec installer ships its own pinned `nargo` and exposes it as the `aztec-nargo` wrapper on `PATH` (bare `nargo` is intentionally not provided so it does not shadow your own install). For Aztec contract work, prefer `aztec compile` and `aztec test`; for plain Noir commands, use `aztec-nargo` (or your own `nargo` install). You can find more information in the nargo installation docs [here](https://noir-lang.org/docs/getting_started/quick_start#installation) and the nargo command reference [here](https://noir-lang.org/docs/reference/nargo_commands). ### Noir[​](#noir "Direct link to Noir") Noir is a Domain Specific Language (DSL) for SNARK proving systems. It is used for writing smart contracts in Aztec because private functions on Aztec are implemented as SNARKs to support privacy-preserving operations. ### Noir Language Server[​](#noir-language-server "Direct link to Noir Language Server") The Noir Language Server can be used in vscode to facilitate writing programs in Noir by providing syntax highlighting, circuit introspection and an execution interface. The Noir LSP addon allows the dev to choose their tool, nargo or `aztec`, when writing a pure Noir program or an Aztec smart contract. You can find more info about the LSP [in the Noir docs](https://noir-lang.org/docs/tooling/language_server). ### Node[​](#node "Direct link to Node") A node is a computer running Aztec software that participates in the Aztec network. A specific type of node is a sequencer. Nodes run the public execution environment (AVM), validate proofs, and maintain the five state Merkle trees (note hash, nullifier, public state, L1-L2 message, and archive trees). The Aztec testnet rolls up to Ethereum Sepolia. To run your own node see [here](/operate/operators.md). ### Note[​](#note "Direct link to Note") In Aztec, a note is like an envelope containing private data. A commitment (hash) of this note is stored in an append-only Merkle tree maintained by all nodes in the network. Notes can be encrypted to be shared with other users. The data in a note represents a variable's state at a specific point in time. ### Note Discovery[​](#note-discovery "Direct link to Note Discovery") Note discovery refers to the process of a user identifying and decrypting the encrypted notes that belong to them. Aztec uses a note tagging system where senders tag encrypted onchain logs containing notes in a way that only the sender and recipient can identify. The tag is derived from a shared secret and an index (a shared counter that increments each time the sender creates a note for the recipient). This allows users to efficiently find their notes without brute force decryption or relying on offchain communication. ### Nullifier[​](#nullifier "Direct link to Nullifier") A nullifier is a unique value that, once posted publicly, proves something has been used or consumed without revealing what that thing was. In the context of Aztec, a nullifier is derived from a note and signifies the note has been "spent" or consumed without revealing which specific note was spent. When a note is updated or spent in Aztec, the protocol creates a nullifier from the note data using the note owner's nullifier key. This nullifier is inserted into the nullifier Merkle tree. The nullifier mechanism prevents double-spending while maintaining privacy by not requiring deletion of the original note commitment, which would leak information. ### Partial Notes[​](#partial-notes "Direct link to Partial Notes") Partial notes are a concept that allows users to commit to an encrypted value, and allows a counterparty to update that value without knowing the specific details of the encrypted value. They are notes that are created in a private function with values that are not yet considered finalized (e.g., `amount` in a `UintNote`). The partial note commitment is computed using multi scalar multiplication on an elliptic curve, then passed to a public function where another party can add value to the note without knowing its private contents. This enables use cases like private fee payments, DEX swaps, and lending protocols. ### Programmable Privacy[​](#programmable-privacy "Direct link to Programmable Privacy") Aztec achieves programmable privacy through its hybrid architecture that supports both private and public smart contract execution. Private functions run client-side with zero-knowledge proofs, while public functions run onchain. This allows developers to program custom privacy logic, choosing what data remains private and what becomes public, with composability between private and public state and execution contexts. ### Provers[​](#provers "Direct link to Provers") The Prover in a ZK system is the entity proving they have knowledge of a valid witness that satisfies a statement. In the context of Aztec, this is the entity that creates the proof that some computation was executed correctly. Here, the statement would be "I know the inputs and outputs that satisfy the requirements for the computation, and I did the computation correctly." Aztec launched with a fully permissionless proving network that anyone can participate in. The proving network produces proofs for valid rollup state transitions. How this works will be discussed via a future RFP process on Discourse, similarly to the Sequencer RFP. ### Proving Key[​](#proving-key "Direct link to Proving Key") A key that is used to generate a proof. In the case of Aztec, these are compiled from Noir smart contracts. ### Private Execution Environment (PXE)[​](#private-execution-environment-pxe "Direct link to Private Execution Environment (PXE)") The private execution environment is where private computation occurs. This is local to your device or browser. Read more [here](/developers/docs/foundational-topics/pxe.md). ### Local Network[​](#local-network "Direct link to Local Network") The local network is a development Aztec network that runs on your machine and interacts with a development Ethereum node. It allows you to develop and deploy Noir smart contracts without interacting with testnet or mainnet. Included in the local network: * Local Ethereum network (Anvil) * Deployed Aztec protocol contracts (for L1 and L2) * A set of test accounts with some test tokens to pay fees * Development tools to compile contracts and interact with the network (`aztec` and `aztec-wallet`) ### Sequencer[​](#sequencer "Direct link to Sequencer") A sequencer is a specialized node that is generally responsible for: * Selecting pending transactions from the mempool * Ordering transactions into a block * Verifying all private transaction proofs and executing all public transactions to check their validity * Computing the ROLLUP\_BLOCK\_REQUEST\_DATA * Computing state updates for messages between L2 & L1 * Broadcasting the ROLLUP\_BLOCK\_REQUEST\_DATA to the prover network via the proof pool for parallelizable computation. * Building a rollup proof from completed proofs in the proof pool * Tagging the pending block with an upgrade signal to facilitate forks * Publishing completed block with proofs to Ethereum as an ETH transaction Aztec will be launched with a fully permissionless sequencer network that anyone can participate in. How this works is being discussed actively in the [Discourse forum](https://discourse.aztec.network/t/request-for-proposals-decentralized-sequencer-selection/350/). Once this discussion process is completed, we will update the glossary and documentation with specifications and instructions for how to run. Previously in [Aztec Connect](https://medium.com/aztec-protocol/sunsetting-aztec-connect-a786edce5cae) there was a single sequencer, and you can find the Typescript reference implementation called Falafel [here](https://github.com/AztecProtocol/aztec-connect/tree/master/yarn-project/falafel). ### Smart Contracts[​](#smart-contracts "Direct link to Smart Contracts") Programs that run on the Aztec network are called smart contracts, similar to [programs](https://ethereum.org/en/developers/docs/smart-contracts/) that run on Ethereum. However, these will be written in the [Noir](https://noir-lang.org/) programming language, and may optionally include private state and private functions. ### Statement[​](#statement "Direct link to Statement") A statement in Aztec's zero-knowledge context refers to the public assertion being proved about a private computation. For example, a statement might be "I know the inputs and outputs that satisfy the requirements for this computation, and I executed the computation correctly." The statement defines what is being proven without revealing the private details (the witness) that prove it. In Aztec, statements typically involve proving correct execution of private functions, valid note ownership, or proper state transitions. ### Verifier[​](#verifier "Direct link to Verifier") The entity responsible for verifying the validity of a ZK proof. In the context of Aztec, this is: * **The sequencers**: verify that private functions were executed correctly. * **The Ethereum L1 smart contract**: verifies batches of transactions were executed correctly. ### Verification Key[​](#verification-key "Direct link to Verification Key") A key that is used to verify the validity of a proof generated from a proving key from the same smart contract. ### Witness[​](#witness "Direct link to Witness") In the context of Aztec's zero-knowledge proofs, a witness refers to the private inputs and intermediate values that satisfy the constraints of a circuit. When executing a private function, the ACVM generates the witness of the execution - the complete set of values that prove the computation was performed correctly. The witness includes both the secret inputs provided by the user and all intermediate computational steps, but is never revealed publicly. Only a cryptographic proof of the witness's validity is shared. ### Zero-knowledge (ZK) proof[​](#zero-knowledge-zk-proof "Direct link to Zero-knowledge (ZK) proof") Zero-knowledge proofs in Aztec are cryptographic proofs that allow someone to prove they know certain information or have performed a computation correctly without revealing the underlying data. Aztec uses various ZK-SNARK protocols including UltraPlonk and Honk. These proofs enable private execution where users can prove they executed a private function correctly and that they own certain notes, without revealing the function inputs, note contents, or internal computation details. The proofs are verified onchain to ensure the integrity of private state transitions. --- # 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. ## 4.3.0[​](#430 "Direct link to 4.3.0") ### \[Aztec.nr] `attempt_note_discovery` is no longer exposed; use `process_private_note_msg`[​](#aztecnr-attempt_note_discovery-is-no-longer-exposed-use-process_private_note_msg "Direct link to aztecnr-attempt_note_discovery-is-no-longer-exposed-use-process_private_note_msg") `attempt_note_discovery` is now crate-private. Custom message handlers (implementations of `CustomMessageHandler`) that previously called it directly should call `process_private_note_msg` instead, which runs the standard private note message decoding and discovery pipeline. `process_private_note_msg` takes the raw `msg_metadata` and `msg_content` rather than already-decoded note fields, so it handles decoding (and silently discards undecodable messages) on your behalf: ``` - attempt_note_discovery( - contract_address, - tx_hash, - unique_note_hashes_in_tx, - first_nullifier_in_tx, - compute_note_hash, - compute_note_nullifier, - owner, - storage_slot, - randomness, - note_type_id, - packed_note, - ); + process_private_note_msg( + contract_address, + tx_hash, + unique_note_hashes_in_tx, + first_nullifier_in_tx, + compute_note_hash, + compute_note_nullifier, + msg_metadata, + msg_content, + ); ``` **Impact**: Custom message handlers that reused the standard note message processing pipeline must switch to `process_private_note_msg`. Contracts using only built-in private note handling are unaffected. ### \[aztec-up] Bundled binaries are no longer exposed under bare names on `PATH`[​](#aztec-up-bundled-binaries-are-no-longer-exposed-under-bare-names-on-path "Direct link to aztec-up-bundled-binaries-are-no-longer-exposed-under-bare-names-on-path") The Aztec installer previously placed bundled binaries directly into `$HOME/.aztec/current/bin` under bare names (`forge`, `nargo`, `bb`, `pxe`, ...). Anything with the same name in your own `PATH` was silently shadowed in unrelated projects. Every bundled binary is now exposed only under an `aztec-` prefixed name in `$HOME/.aztec/current/bin`. Bare names are not on `PATH` at all and resolve to your own install (if any). | Was on `PATH` | Now | | ------------------ | ------------------------ | | `forge` | `aztec-forge` | | `cast` | `aztec-cast` | | `anvil` | `aztec-anvil` | | `chisel` | `aztec-chisel` | | `nargo` | `aztec-nargo` | | `noir-profiler` | `aztec-noir-profiler` | | `bb` | `aztec-bb` | | `bb-cli` | `aztec-bb-cli` | | `pxe` | `aztec-pxe` | | `txe` | `aztec-txe` | | `validator-client` | `aztec-validator-client` | | `blob-client` | `aztec-blob-client` | `aztec`, `aztec-wallet`, and `aztec-up` keep their existing names. If you relied on a bundled bare-name binary for general use: * For Aztec contract work, prefer `aztec compile` and `aztec test`. * For other Noir / Foundry commands, invoke the `aztec-*` symlink directly (e.g. `aztec-nargo fmt`, `aztec-forge build`). * Or install Foundry / nargo separately via `foundryup` / `noirup`. If you set `Noir: Nargo Path` in the VS Code Noir extension to `$HOME/.aztec/current/bin/nargo`, change it to `$HOME/.aztec/current/bin/aztec-nargo` (the symlink is a drop-in for `nargo`). See the [Noir VSCode Extension guide](/developers/docs/aztec-nr/installation.md) for details. ### \[Aztec.js] `DeployMethod` address-affecting parameters move to construction time[​](#aztecjs-deploymethod-address-affecting-parameters-move-to-construction-time "Direct link to aztecjs-deploymethod-address-affecting-parameters-move-to-construction-time") Salt, deployer, and public keys are now passed when the `DeployMethod` is constructed, not on every call to `send` / `simulate` / `request` / `getInstance`. This locks the contract address once it is determined and prevents the silent salt-cache poisoning bug where the address could change between calls. `contractAddressSalt`, `deployer`, and `universalDeploy` have been removed from `DeployOptions`, `RequestDeployOptions`, and `SimulateDeployOptions`. They now live on a new `DeployInstantiationOptions` argument passed at construction. `deployer` and `universalDeploy` are mutually exclusive; passing both throws. `Contract.deployWithPublicKeys` and the generated `MyContract.deployWithPublicKeys(...)` factories have been removed; pass `publicKeys` via the `instantiation` argument of `deploy(...)` instead. The buggy synchronous `address` and `partialAddress` getters have been removed and replaced with `getAddress()` and `getPartialAddress()` (both `async`). The compact form keeps working: `MyContract.deploy(wallet, ...args).send({ from: alice })` deploys with `deployer = alice` and `salt = random()`, exactly as before. The deployer is locked the first time `send` / `simulate` / `profile` is called (from `options.from`, with `NO_FROM` or undefined → universal) and cannot change after that: * Subsequent `send` / `simulate` / `profile` calls with a `from` that would imply a different deployer throw, instead of silently producing a different address. * A lock to universal (`AztecAddress.ZERO`) is the only one compatible with any sender, since the universal address does not depend on `from`. * A lock to a concrete address only accepts that exact `from` on subsequent calls. **Migration:** Universal deployment with a fixed salt: ``` - const deploy = MyContract.deploy(wallet, ...args); - await deploy.send({ - from: alice, - contractAddressSalt: salt, - universalDeploy: true, - }); + const deploy = MyContract.deploy(wallet, ...args, { salt, universalDeploy: true }); + await deploy.send({ from: alice }); ``` Non-universal deploy where `from` doubles as the deployer: ``` - const deploy = MyContract.deploy(wallet, ...args); - await deploy.send({ from: alice, contractAddressSalt: salt }); + const deploy = MyContract.deploy(wallet, ...args, { salt }); + await deploy.send({ from: alice }); ``` If you need to read the address before sending, lock the deployer at construction: ``` const deploy = MyContract.deploy(wallet, ...args, { salt, deployer: alice }); const address = await deploy.getAddress(); // resolves; deployer was locked at construction await deploy.send({ from: alice }); // deploys at the address `getAddress` returned ``` Universal deploys can be sent by any account, since the universal address does not depend on `from`: ``` const deploy = MyContract.deploy(wallet, ...args, { universalDeploy: true }); await deploy.send({ from: bob }); // OK, universal accepts any sender ``` A lock to a concrete deployer rejects sending from a different account, instead of silently deploying at a different address: ``` const deploy = MyContract.deploy(wallet, ...args, { deployer: alice }); await deploy.send({ from: bob }); // throws: deployer is locked to alice ``` `deployWithPublicKeys` is gone; pass `publicKeys` in the instantiation options instead: ``` - const deploy = MyContract.deployWithPublicKeys(publicKeys, wallet, ...args); + const deploy = MyContract.deploy(wallet, ...args, { publicKeys }); ``` `ContractDeployer.deploy(...)` now takes the instantiation argument as its first parameter (pass `{}` to use defaults and rely on lazy locking from `from`): ``` - const cd = new ContractDeployer(artifact, wallet); - await cd.deploy(...ctorArgs).send({ from: alice, contractAddressSalt: salt }); + const cd = new ContractDeployer(artifact, wallet); + await cd.deploy(ctorArgs, { salt }).send({ from: alice }); ``` The synchronous `address` / `partialAddress` getters are gone: ``` - const address = deploy.address; // sync, possibly undefined - const partial = await deploy.partialAddress; // sync getter wrapping async value + const address = await deploy.getAddress(); // requires the deployer to be locked + const partial = await deploy.getPartialAddress(); // requires the deployer to be locked ``` `getInstance()` no longer takes options; use the construction-time instantiation instead: ``` - const instance = await deploy.getInstance({ contractAddressSalt: salt }); + const deploy = MyContract.deploy(wallet, ...args, { salt, deployer: alice }); + const instance = await deploy.getInstance(); ``` ### \[Aztec.nr] TXE `call_public_incognito` no longer takes a `from` parameter[​](#aztecnr-txe-call_public_incognito-no-longer-takes-a-from-parameter "Direct link to aztecnr-txe-call_public_incognito-no-longer-takes-a-from-parameter") `TestEnvironment::call_public_incognito` previously accepted a `from` address that was silently ignored (the function always uses a null `msg_sender`). The `from` parameter has been removed. ``` - env.call_public_incognito(sender, SampleContract::at(addr).some_function()); + env.call_public_incognito(SampleContract::at(addr).some_function()); ``` If you need to call a public function *with* a sender, use `call_public` instead. ### \[Aztec.nr] TXE `view_public_incognito` is deprecated[​](#aztecnr-txe-view_public_incognito-is-deprecated "Direct link to aztecnr-txe-view_public_incognito-is-deprecated") `TestEnvironment::view_public_incognito` is now deprecated in favor of `view_public`, which has the same behavior (null `msg_sender`, static call). ``` - env.view_public_incognito(SampleContract::at(addr).some_view()); + env.view_public(SampleContract::at(addr).some_view()); ``` ### \[PXE] `proveTx` takes an options bag[​](#pxe-provetx-takes-an-options-bag "Direct link to pxe-provetx-takes-an-options-bag") `PXE.proveTx` used to accept `scopes` as a positional argument; it now takes an options bag consistent with `simulateTx` and `profileTx`, and adds an optional `senderForTags` field. Update direct callers: ``` - pxe.proveTx(txRequest, scopes); + pxe.proveTx(txRequest, { scopes }); ``` The new `senderForTags` field sets the address recipients use to find private messages (notes, events, logs) emitted by this tx. Most wallets don't need to set it; the wallet SDK derives it from the tx's `from` address: ``` // Most callers: just migrate scopes pxe.proveTx(txRequest, { scopes }); // When from === NO_FROM (e.g. self-paid account deploy), supply the tag sender explicitly: pxe.proveTx(txRequest, { scopes, senderForTags: deployedAddress }); ``` ### \[Aztec.nr] `set_sender_for_tags` is now scoped to the calling contract[​](#aztecnr-set_sender_for_tags-is-now-scoped-to-the-calling-contract "Direct link to aztecnr-set_sender_for_tags-is-now-scoped-to-the-calling-contract") `set_sender_for_tags` previously persisted through nested calls. It is now scoped to the contract that calls it: nested calls, siblings, and parents are unaffected and always start from the initial default. This closes a silent note-discovery DoS vector where any nested callee could overwrite the tag sender for legitimate contracts called below it. The wallet SDK now supplies the default sender-for-tags from the transaction's `from` address, with an optional `sendMessagesAs` override for flows that don't have a signing account (e.g. self-paid deploys, which `DeployAccountMethod` sets automatically). Account contracts therefore no longer need to call `set_sender_for_tags(self.address)` in their entrypoints; those calls have been removed from the standard schnorr and ECDSA account contracts, and you can drop the equivalent call in any custom account contract. The save/restore idiom previously used in account-contract constructors (`get` → `set(self.address)` → work → `set(prev)`) is also no longer needed and has been removed: the override never leaks out of the constructor, so there is nothing to restore. ### \[CLI] `aztec-up` no longer exposes transitive npm bins on PATH[​](#cli-aztec-up-no-longer-exposes-transitive-npm-bins-on-path "Direct link to cli-aztec-up-no-longer-exposes-transitive-npm-bins-on-path") The `aztec-up` installer used to add `$HOME/.aztec/current/node_modules/.bin` to your shell `PATH`, which put \~40 transitive npm bins (`jest`, `tsc`, `tsserver`, `semver`, `uuid`, `json5`, ...) onto your interactive shell and silently shadowed your own installed versions of those tools. Only the seven `@aztec/*`-owned bins (`aztec`, `aztec-wallet`, `bb`, `bb-cli`, `blob-client`, `noir-codegen`, `txe`) are now exposed. If you had an Aztec version installed before this release, your shell profile (`~/.bashrc` or `~/.zshrc`) still contains the old `PATH` line. Re-run the installer once (replacing `[VERSION]` with whichever toolchain version you're on, e.g. `4.2.0`) to replace it: ``` VERSION=[VERSION] bash -i <(curl -sL https://install.aztec.network) ``` Open a fresh shell and confirm the leak is gone: ``` echo $PATH ``` `$HOME/.aztec/current/node_modules/.bin` should no longer appear in the output. You'll also see your own `jest`, `tsc`, etc. again instead of the ones bundled with the Aztec toolchain. ### \[Aztec.nr] `emit_private_log_unsafe` / `emit_raw_note_log_unsafe` are deprecated[​](#aztecnr-emit_private_log_unsafe--emit_raw_note_log_unsafe-are-deprecated "Direct link to aztecnr-emit_private_log_unsafe--emit_raw_note_log_unsafe-are-deprecated") `emit_private_log_unsafe` and `emit_raw_note_log_unsafe` are deprecated and will be removed in a future release. Migrate to the new `emit_private_log_vec_unsafe` / `emit_raw_note_log_vec_unsafe` functions, which take a `BoundedVec` instead of the `(log: [Field; PRIVATE_LOG_CIPHERTEXT_LEN], length: u32)` pair. ``` - context.emit_private_log_unsafe(tag, log, length); + context.emit_private_log_vec_unsafe(tag, bounded_vec_log); - context.emit_raw_note_log_unsafe(tag, log, length, note_hash_counter); + context.emit_raw_note_log_vec_unsafe(tag, bounded_vec_log, note_hash_counter); ``` If you were manually padding an array and passing a shorter length, you can now create a `BoundedVec` from just the meaningful fields: ``` - let padded = payload.concat([0; PRIVATE_LOG_CIPHERTEXT_LEN - 2]); - context.emit_private_log_unsafe(tag, padded, 2); + let log = BoundedVec::from_array(payload); + context.emit_private_log_vec_unsafe(tag, log); ``` If you were passing the full array, wrap it with `BoundedVec::from_array`: ``` - context.emit_private_log_unsafe(tag, ciphertext, ciphertext.len()); + context.emit_private_log_vec_unsafe(tag, BoundedVec::from_array(ciphertext)); ``` ### \[aztec-nr] Nullifier membership witness oracle returns split types[​](#aztec-nr-nullifier-membership-witness-oracle-returns-split-types "Direct link to \[aztec-nr] Nullifier membership witness oracle returns split types") `get_nullifier_membership_witness` and `get_low_nullifier_membership_witness` now return `(NullifierLeafPreimage, MembershipWitness)` instead of the bundled `NullifierMembershipWitness` struct (which has been removed). If you were using these oracle functions directly (e.g. in `schnorr_account_contract`'s `lookup_validity`), update your code to destructure the tuple: ``` - let witness = get_low_nullifier_membership_witness(block_header, siloed_nullifier); - let nullifier_value = witness.leaf_preimage.nullifier; - let index = witness.index; - let path = witness.path; + let (leaf_preimage, witness) = get_low_nullifier_membership_witness(block_header, siloed_nullifier); + let nullifier_value = leaf_preimage.nullifier; + let index = witness.leaf_index; + let path = witness.sibling_path; ``` Note the field renames: `index` is now `leaf_index`, and `path` is now `sibling_path` (matching the protocol circuit's `MembershipWitness` type). This has been done because this is the format expected by the functionality in protocol circuits and given that this is sensitive security-wise it made sense to reuse that functionality in Aztec.nr. ### \[CLI] `aztec init` now scaffolds a Counter example template[​](#cli-aztec-init-now-scaffolds-a-counter-example-template "Direct link to cli-aztec-init-now-scaffolds-a-counter-example-template") `aztec init` previously created a blank contract crate. It now scaffolds a runnable **Counter** example contract with a constructor, `increment`, and `get_counter` functions, plus a test suite, so new developers have a working starting point ([#22751](https://github.com/AztecProtocol/aztec-packages/pull/22751)). * `aztec init` — scaffolds the Counter example (new default). * `aztec new ` — still scaffolds a blank contract, either as a new standalone project or as a new crate added to an existing workspace. **Impact**: any scripts, CI jobs, or onboarding docs that ran `aztec init` expecting an empty contract starting point now get the Counter example. Use `aztec new ` for the blank scaffold. The existing Counter tutorial under [`docs/tutorials/contract_tutorials`](/developers/docs/tutorials/contract_tutorials/counter_contract.md) is unaffected because it uses `aztec new`. ### `aztec new` and `aztec init` now create a 2-crate workspace[​](#aztec-new-and-aztec-init-now-create-a-2-crate-workspace "Direct link to 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 `contract` crate (type = "contract") for your smart contract code * A `test` crate (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 `--contract` and `--lib` flags have been removed from `aztec new` and `aztec init`. These commands now always create a contract workspace. * Contract code is now at `contract/src/main.nr` instead of `src/main.nr`. * The `Nargo.toml` in the project root is now a workspace file. Contract dependencies go in `contract/Nargo.toml`. * Tests should be written in the separate `test` crate (`test/src/lib.nr`) and import the contract by package name (e.g., `use my_contract::MyContract;`) instead of using `crate::`. ### `aztec new` crate directories are now named after the contract[​](#aztec-new-crate-directories-are-now-named-after-the-contract "Direct link to aztec-new-crate-directories-are-now-named-after-the-contract") `aztec new` and `aztec init` now name the generated crate directories after the contract instead of using generic `contract/` and `test/` names. For example, `aztec new counter` now creates: ``` counter/ ├── Nargo.toml # [workspace] members = ["counter_contract", "counter_test"] ├── counter_contract/ │ ├── src/main.nr │ └── Nargo.toml # type = "contract" └── counter_test/ ├── src/lib.nr └── Nargo.toml # type = "lib" ``` This enables adding multiple contracts to a single workspace. Running `aztec new ` inside an existing workspace (a directory with a `Nargo.toml` containing `[workspace]`) now adds a new `_contract` and `_test` crate pair to the workspace instead of creating a new directory. **What changed:** * Crate directories are now `_contract/` and `_test/` instead of `contract/` and `test/`. * Contract code is now at `_contract/src/main.nr` instead of `contract/src/main.nr`. * Contract dependencies go in `_contract/Nargo.toml` instead of `contract/Nargo.toml`. * Tests import the contract by its new crate name (e.g., `use counter_contract::Main;` instead of `use counter::Main;`). ### \[CLI] `--name` flag removed from `aztec new` and `aztec init`[​](#cli---name-flag-removed-from-aztec-new-and-aztec-init "Direct link to cli---name-flag-removed-from-aztec-new-and-aztec-init") The `--name` flag has been removed from both `aztec new` and `aztec init`. For `aztec new`, the positional argument now serves as both the contract name and the directory name. For `aztec init`, the directory name is always used as the contract name. **Migration:** ``` - aztec new my_project --name counter + aztec new counter ``` ``` - aztec init --name counter + aztec init ``` **Impact**: If you were using `--name` to set a contract name different from the directory name, rename your directory or use `aztec new` with the desired contract name directly. ## 4.2.0[​](#420 "Direct link to 4.2.0") ### \[Aztec.js] `GasSettings.default()` renamed to `GasSettings.fallback()`[​](#aztecjs-gassettingsdefault-renamed-to-gassettingsfallback "Direct link to aztecjs-gassettingsdefault-renamed-to-gassettingsfallback") `GasSettings.default()` has been renamed to `GasSettings.fallback()` to clarify that these gas limits are not protocol defaults — the protocol has no concept of "default" gas settings. `fallback()` is a convenience for cases where gas estimation is not being used, but callers should prefer estimating gas via simulation for accurate limits. The old `DEFAULT_GAS_LIMIT` and `DEFAULT_TEARDOWN_GAS_LIMIT` constants have been removed. Gas limits are now derived from protocol-level maximums (`MAX_PROCESSABLE_L2_GAS`, `MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT`) rather than arbitrary fixed values. A new `GasSettings.forEstimation()` method provides intentionally high gas limits for use during simulation. These limits exceed protocol maximums so the simulation doesn't hit gas caps — you must pass `skipTxValidation: true` when simulating with them, then use the results to set accurate gas limits on the actual transaction. `EmbeddedWallet` does this by default. **Migration:** ``` - import { DEFAULT_GAS_LIMIT, DEFAULT_TEARDOWN_GAS_LIMIT } from '@aztec/constants'; - const settings = GasSettings.default({ maxFeesPerGas }); + const settings = GasSettings.fallback({ maxFeesPerGas }); ``` **Impact**: Any code referencing `GasSettings.default()`, `DEFAULT_GAS_LIMIT`, or `DEFAULT_TEARDOWN_GAS_LIMIT` will fail to compile. ### \[PXE] `simulateTx`, `executeUtility`, `profileTx`, and `proveTx` no longer accept `scopes: 'ALL_SCOPES'`[​](#pxe-simulatetx-executeutility-profiletx-and-provetx-no-longer-accept-scopes-all_scopes "Direct link to pxe-simulatetx-executeutility-profiletx-and-provetx-no-longer-accept-scopes-all_scopes") The `AccessScopes` type (`'ALL_SCOPES' | AztecAddress[]`) has been removed. The `scopes` field in `SimulateTxOpts`, `ExecuteUtilityOpts`, and `ProfileTxOpts` now requires an explicit `AztecAddress[]`. Callers that previously passed `'ALL_SCOPES'` must now specify which addresses will be in scope for the call. **Migration:** ``` + const accounts = await pxe.getRegisteredAccounts(); + const scopes = accounts.map(a => a.address); // simulateTx - await pxe.simulateTx(txRequest, { simulatePublic: true, scopes: 'ALL_SCOPES' }); + await pxe.simulateTx(txRequest, { simulatePublic: true, scopes }); // executeUtility - await pxe.executeUtility(call, { scopes: 'ALL_SCOPES' }); + await pxe.executeUtility(call, { scopes }); // profileTx - await pxe.profileTx(txRequest, { profileMode: 'full', scopes: 'ALL_SCOPES' }); + await pxe.profileTx(txRequest, { profileMode: 'full', scopes }); // proveTx - await pxe.proveTx(txRequest, 'ALL_SCOPES'); + await pxe.proveTx(txRequest, scopes); ``` **Impact**: Any code passing `'ALL_SCOPES'` to `simulateTx`, `executeUtility`, `profileTx`, or `proveTx` will fail to compile. Replace with an explicit array of account addresses. ### \[PXE] Capsule operations are now scope-enforced at the PXE level[​](#pxe-capsule-operations-are-now-scope-enforced-at-the-pxe-level "Direct link to \[PXE] Capsule operations are now scope-enforced at the PXE level") The PXE now enforces that capsule operations can only access scopes that were authorized for the current execution. If a contract attempts to access a capsule scope that is not in its allowed scopes list, the PXE will throw an error: ``` Scope 0x1234... is not in the allowed scopes list: [0xabcd...]. ``` The zero address (`AztecAddress::zero()`) is always allowed regardless of the scopes list, preserving backwards compatibility for contracts using the global scope. **Impact**: Contracts that access capsules scoped to addresses not included in the transaction's authorized scopes will now fail at runtime. Ensure the correct scopes are passed when executing transactions. ### \[aztec.js] `EmbeddedWalletOptions` now uses a unified `pxe` field[​](#aztecjs-embeddedwalletoptions-now-uses-a-unified-pxe-field "Direct link to aztecjs-embeddedwalletoptions-now-uses-a-unified-pxe-field") The `pxeConfig` and `pxeOptions` fields on `EmbeddedWalletOptions` have been deprecated in favor of a single `pxe` field that accepts both PXE configuration and dependency overrides (custom prover, store, simulator): ``` const wallet = await EmbeddedWallet.create(nodeUrl, { - pxeConfig: { proverEnabled: true }, - pxeOptions: { proverOrOptions: myCustomProver }, + pxe: { + proverEnabled: true, + proverOrOptions: myCustomProver, + }, }); ``` The old fields still work but will be removed in a future release. ### \[Aztec.nr] Ephemeral arrays replace capsule arrays in PXE oracle interfaces[​](#aztecnr-ephemeral-arrays-replace-capsule-arrays-in-pxe-oracle-interfaces "Direct link to \[Aztec.nr] Ephemeral arrays replace capsule arrays in PXE oracle interfaces") Oracle interfaces between Aztec.nr and PXE now use a new `EphemeralArray` type (`aztec::ephemeral::EphemeralArray`) instead of `CapsuleArray`. Ephemeral arrays live in memory and are scoped by contract call frame, so they no longer need to be addressed by `(contract_address, scope)`. Several public message-discovery and validation functions lost their `recipient`, `scope`, and `contract_address` parameters as a result. Most contracts are not affected, as the macro-generated `sync_state` and `process_message` functions handle these APIs automatically. Only contracts that call these functions directly need to update. **Migration:** ``` attempt_note_discovery( contract_address, tx_hash, unique_note_hashes_in_tx, first_nullifier_in_tx, - recipient, compute_note_hash, compute_note_nullifier, owner, storage_slot, randomness, note_type_id, packed_note, ); - enqueue_note_for_validation(contract_address, owner, storage_slot, randomness, note_nonce, packed_note, note_hash, nullifier, tx_hash, scope); + enqueue_note_for_validation(contract_address, owner, storage_slot, randomness, note_nonce, packed_note, note_hash, nullifier, tx_hash); - enqueue_event_for_validation(contract_address, event_type_id, randomness, serialized_event, event_commitment, tx_hash, scope); + enqueue_event_for_validation(contract_address, event_type_id, randomness, serialized_event, event_commitment, tx_hash); - validate_and_store_enqueued_notes_and_events(contract_address, scope); + validate_and_store_enqueued_notes_and_events(scope); ``` The `sync_inbox` function and the `OffchainInboxSync` type now return `EphemeralArray` instead of `CapsuleArray`. Custom message handlers that bind the returned array to an explicit type must update the type annotation. **Impact**: Contracts that call the above functions directly (rather than relying on macro-generated code) will fail to compile until the trailing `recipient`, `scope`, and `contract_address` parameters are removed. ## 4.2.0-aztecnr-rc.2[​](#420-aztecnr-rc2 "Direct link to 4.2.0-aztecnr-rc.2") ### Custom token FPCs removed from default public setup allowlist[​](#custom-token-fpcs-removed-from-default-public-setup-allowlist "Direct link to Custom token FPCs removed from default public setup allowlist") Token contract functions (like `transfer_in_public` and `_increase_public_balance`) have been removed from the default public setup allowlist. FPCs that accept custom tokens (like the reference `FPC` contract) will not work on public networks, because their setup-phase calls to these functions will be rejected. Token class IDs change with each aztec-nr release, making it impractical to maintain them in the allowlist. FPCs that use only Fee Juice still work on all networks, since FeeJuice is a protocol contract with a fixed address in the allowlist. Custom FPCs should only call protocol contract functions (AuthRegistry, FeeJuice) during setup. `PublicFeePaymentMethod` and `PrivateFeePaymentMethod` in aztec.js are affected, since they use the reference `FPC` contract which calls Token functions during setup. Switch to `FeeJuicePaymentMethodWithClaim` (after [bridging Fee Juice from L1](/developers/docs/aztec-js/how_to_pay_fees.md#bridge-fee-juice-from-l1)) or write an FPC that uses Fee Juice natively. **Migration:** ``` - import { PublicFeePaymentMethod } from '@aztec/aztec.js/fee'; - const paymentMethod = new PublicFeePaymentMethod(fpcAddress, senderAddress, wallet, gasSettings); + import { FeeJuicePaymentMethodWithClaim } from '@aztec/aztec.js/fee'; + const paymentMethod = new FeeJuicePaymentMethodWithClaim(senderAddress, claim); ``` Similarly, the `fpc-public` and `fpc-private` CLI wallet payment methods use the reference Token-based FPC and will not work on public networks. Use `fee_juice` for direct Fee Juice payment, or `fpc-sponsored` on testnet, devnet, and local network. ### \[Aztec.nr] Domain-separated tags on log emission[​](#aztecnr-domain-separated-tags-on-log-emission "Direct link to \[Aztec.nr] Domain-separated tags on log emission") All logs emitted through the Aztec.nr framework now include a domain-separated tag at `fields[0]`. Each log category uses its own domain separator via `compute_log_tag(raw_tag, dom_sep)`: * **Events** (`DOM_SEP__EVENT_LOG_TAG`): the event type ID is the raw tag. * **Message delivery** (`DOM_SEP__UNCONSTRAINED_MSG_LOG_TAG`): the discovery tag is the raw tag. * **Partial note completion logs** (`DOM_SEP__NOTE_COMPLETION_LOG_TAG`): the partial note's `commitment` field is the raw tag. The low-level emit methods now take `tag` as an explicit first parameter and have been renamed with an `_unsafe` suffix. Previously the tag was included as `log[0]` — it has now been extracted into its own parameter, and `log` no longer contains it: ``` - context.emit_private_log(log, length); + context.emit_private_log_unsafe(tag, log, length); - context.emit_raw_note_log(log, length, note_hash_counter); + context.emit_raw_note_log_unsafe(tag, log, length, note_hash_counter); - context.emit_public_log(log); + context.emit_public_log_unsafe(tag, log); ``` Prefer the higher-level APIs (`emit` for events, `MessageDelivery` for messages) which handle tagging automatically. ### \[Aztec.nr] Public events no longer include the event type selector at the end of the payload[​](#aztecnr-public-events-no-longer-include-the-event-type-selector-at-the-end-of-the-payload "Direct link to \[Aztec.nr] Public events no longer include the event type selector at the end of the payload") `emit_event_in_public` previously appended the event type selector as the last field. It now prepends a domain-separated tag at `fields[0]` instead. The payload after the tag contains only the serialized event fields. If you were reading public event directly from node logs (i.e. via `node.getPublicLogs` and not via `wallet.getPublicEvents`), update your parsing: ``` - // Old: fields = [serialized_event..., event_type_selector] - const selector = EventSelector.fromField(fields[fields.length - 1]); - const event = decodeFromAbi([abiType], fields); + // New: fields = [domain_separated_tag, serialized_event...] + const eventFields = log.getEmittedFieldsWithoutTag(); + const event = decodeFromAbi([abiType], eventFields); ``` ### \[Aztec.nr] Capsule operations are now addressed by scope[​](#aztecnr-capsule-operations-are-now-addressed-by-scope "Direct link to \[Aztec.nr] Capsule operations are now addressed by scope") All capsule operations (`store`, `load`, `delete`, `copy`) and `CapsuleArray` now require a `scope: AztecAddress` parameter. This scopes capsule storage by address, providing isolation between different accounts within the same PXE. Contracts that use `CapsuleArray` directly also need to update. **Migration:** ``` - let array: CapsuleArray = CapsuleArray::at(contract_address, slot); + let array: CapsuleArray = CapsuleArray::at(contract_address, slot, scope); ``` The low-level capsule functions are similarly affected: ``` - capsules::store(contract_address, slot, value); + capsules::store(contract_address, slot, value, scope); - capsules::load(contract_address, slot); + capsules::load(contract_address, slot, scope); - capsules::delete(contract_address, slot); + capsules::delete(contract_address, slot, scope); - capsules::copy(contract_address, src_slot, dst_slot, num_entries); + capsules::copy(contract_address, src_slot, dst_slot, num_entries, scope); ``` If you need to stick the old, scope-less behavior, and you are really sure that that's what you need to use, you can use `scope = AztecAddress::zero()`. ### \[Aztec.nr] `process_message` utility function removed[​](#aztecnr-process_message-utility-function-removed "Direct link to aztecnr-process_message-utility-function-removed") The auto-generated `process_message` utility function has been removed. If you need to deliver offchain messages (messages not broadcast via onchain logs), use the `offchain_receive` utility function instead. This function is automatically injected by the `#[aztec]` macro and accepts messages into a persistent inbox scoped by recipient. These messages are then picked up and processed during `sync_state`. **Impact**: Contracts that explicitly called `process_message` must switch to delivering messages via `offchain_receive` and letting `sync_state` handle processing. ### \[Aztec.nr] `CustomMessageHandler` type signature changed[​](#aztecnr-custommessagehandler-type-signature-changed "Direct link to aztecnr-custommessagehandler-type-signature-changed") The `CustomMessageHandler` function type now receives an additional `scope: AztecAddress` parameter: ``` type CustomMessageHandler = unconstrained fn( AztecAddress, // contract_address u64, // msg_type_id u64, // msg_metadata BoundedVec, // msg_content MessageContext, // message_context + AztecAddress, // scope ); ``` **Impact**: Contracts that implement a custom message handler must update the function signature. ### \[aztec.js] `DeployMethod.send()` always returns `{ contract, receipt, instance }`[​](#aztecjs-deploymethodsend-always-returns--contract-receipt-instance- "Direct link to aztecjs-deploymethodsend-always-returns--contract-receipt-instance-") The `returnReceipt` option in deploy wait options has been removed. `DeployMethod.send()` now always returns an object with `contract`, `receipt`, and `instance` at the top level, provided the user waits for the transaction to be included. The `DeployTxReceipt` and `DeployWaitOptions` types have been removed. **Migration:** ``` - const { - receipt: { contract, instance }, - } = await MyContract.deploy(wallet, ...args).send({ - from: address, - wait: { returnReceipt: true }, - }); + const { contract, instance } = await MyContract.deploy(wallet, ...args).send({ + from: address, + }); ``` ### \[aztec.js] `isContractInitialized` is now `initializationStatus` tri-state enum[​](#aztecjs-iscontractinitialized-is-now-initializationstatus-tri-state-enum "Direct link to aztecjs-iscontractinitialized-is-now-initializationstatus-tri-state-enum") `ContractMetadata.isContractInitialized` has been renamed to `ContractMetadata.initializationStatus` and changed from `boolean | undefined` to a `ContractInitializationStatus` enum with values `INITIALIZED`, `UNINITIALIZED`, and `UNKNOWN`. * `INITIALIZED`: the contract has been initialized (initialization nullifier found) * `UNINITIALIZED`: the contract instance is registered but has not been initialized * `UNKNOWN`: the instance is not registered and no public initialization nullifier was found When the instance is not registered, the wallet now attempts to check the public initialization nullifier (computed from address alone) before returning `UNKNOWN`. Previously this case returned `undefined`. **Migration:** ``` + import { ContractInitializationStatus } from '@aztec/aztec.js/wallet'; const metadata = await wallet.getContractMetadata(address); - if (metadata.isContractInitialized) { + if (metadata.initializationStatus === ContractInitializationStatus.INITIALIZED) { // contract is initialized } ``` ### \[Aztec.js] Use `NO_FROM` instead of `AztecAddress.ZERO` to bypass account contract entrypoint[​](#aztecjs-use-no_from-instead-of-aztecaddresszero-to-bypass-account-contract-entrypoint "Direct link to aztecjs-use-no_from-instead-of-aztecaddresszero-to-bypass-account-contract-entrypoint") When sending transactions that should not be mediated by an account contract (e.g., account contract self-deployments), use the explicit `NO_FROM` sentinel instead of `AztecAddress.ZERO`. `NO_FROM` signals that the transaction should be executed directly via the `DefaultEntrypoint`. This replaces the brittle convention of passing `AztecAddress.ZERO` as the `from` field. **Migration:** ``` - import { AztecAddress } from '@aztec/aztec.js'; + import { NO_FROM } from '@aztec/aztec.js/account'; await contract.methods.my_method().send({ - from: AztecAddress.ZERO, + from: NO_FROM, }); ``` Note that `DefaultEntrypoint` only accepts a single call. If you need to execute multiple calls without account contract mediation (e.g., deploying an account contract and paying a fee in the same transaction), wrap them through `DefaultMultiCallEntrypoint` on the app side before sending: ``` import { NO_FROM } from "@aztec/aztec.js/account"; import { DefaultMultiCallEntrypoint } from "@aztec/entrypoints/multicall"; import { mergeExecutionPayloads } from "@aztec/stdlib/tx"; // Merge multiple execution payloads into one const merged = mergeExecutionPayloads([deployPayload, feePayload]); // Wrap through multicall so it becomes a single call for DefaultEntrypoint const multicall = new DefaultMultiCallEntrypoint(); const chainInfo = await wallet.getChainInfo(); const wrappedPayload = await multicall.wrapExecutionPayload(merged, chainInfo); // Send without account contract mediation await wallet.sendTx(wrappedPayload, { from: NO_FROM }); ``` Using other contracts for wrapping (for example, supporting more calls) is also supported, as long as the contract is registered in the wallet. This opens the door to different flows that do not use account entrypoints as the first call in the chain, including app sponsored FPCs. **Impact**: Any code that passes `AztecAddress.ZERO` as the `from` option in `.send()`, `.simulate()`, or deploy options must switch to `NO_FROM`. Wallets use `DefaultEntrypoint` directly for `NO_FROM` transactions, instead of the `DefaultMultiCallEntrypoint` that was used internally before when specifying `AztecAddress.ZERO`. ### \[Aztec.js] `ExecuteUtilityOptions.scope` renamed to `scopes` and type changed to `AztecAddress[]`[​](#aztecjs-executeutilityoptionsscope-renamed-to-scopes-and-type-changed-to-aztecaddress "Direct link to aztecjs-executeutilityoptionsscope-renamed-to-scopes-and-type-changed-to-aztecaddress") The `scope` field in `ExecuteUtilityOptions` has been renamed to `scopes` and changed from a single `AztecAddress` to `AztecAddress[]`. This aligns the wallet's `executeUtility` API with the PXE API and `sendTx` in `Wallet`, which both accept an array of scopes. **Migration:** ``` wallet.executeUtility(call, { - scope: myAddress, + scopes: [myAddress], }); ``` **Impact**: Any code that calls `wallet.executeUtility` directly must update the options object. Wallets must update to adapt to the new interface ### \[Aztec.nr] `attempt_note_discovery` now takes two separate functions instead of one[​](#aztecnr-attempt_note_discovery-now-takes-two-separate-functions-instead-of-one "Direct link to aztecnr-attempt_note_discovery-now-takes-two-separate-functions-instead-of-one") The `attempt_note_discovery` function (and related discovery functions like `do_sync_state`, `process_message_ciphertext`) now takes separate `compute_note_hash` and `compute_note_nullifier` arguments instead of a single combined `compute_note_hash_and_nullifier`. The corresponding type aliases are now `ComputeNoteHash` and `ComputeNoteNullifier` (instead of `ComputeNoteHashAndNullifier`). This split improves performance during nonce discovery: the note hash only needs to be computed once, while the old combined function recomputed it for every candidate nonce. Most contracts are not affected, as the macro-generated `sync_state` and `process_message` functions handle this automatically. Only contracts that call `attempt_note_discovery` directly need to update. **Migration:** ``` attempt_note_discovery( contract_address, tx_hash, unique_note_hashes_in_tx, first_nullifier_in_tx, recipient, - _compute_note_hash_and_nullifier, + _compute_note_hash, + _compute_note_nullifier, owner, storage_slot, randomness, note_type_id, packed_note, ); ``` **Impact**: Contracts that call `attempt_note_discovery` or related discovery functions directly with a custom `_compute_note_hash_and_nullifier` argument. The old combined function is still generated (deprecated) but is no longer used by the framework. Additionally, if you had a custom `_compute_note_hash_and_nullifier` function then compilation will now fail as you'll need to also produce the corresponding `_compute_note_hash` and `_compute_note_nullifier` functions. ### \[Aztec.nr] Made `compute_note_hash_for_nullification` unconstrained[​](#aztecnr-made-compute_note_hash_for_nullification-unconstrained "Direct link to aztecnr-made-compute_note_hash_for_nullification-unconstrained") This function shouldn't have been constrained in the first place, as constrained computation of `HintedNote` nullifiers is dangerous (constrained computation of nullifiers can be performed only on the `ConfirmedNote` type). If you were calling this from a constrained function, consider using `compute_confirmed_note_hash_for_nullification` instead. Unconstrained usage is safe. ### \[Aztec.nr] Changes to standard note hash computation[​](#aztecnr-changes-to-standard-note-hash-computation "Direct link to \[Aztec.nr] Changes to standard note hash computation") Note hashes used to be computed with the storage slot being the last value of the preimage, it is now the first. This is to make it easier to ensure all note hashes have proper domain separation. This change requires no input from your side unless you were testing or relying on hardcoded note hashes. ### Private initialization nullifier now includes `init_hash`[​](#private-initialization-nullifier-now-includes-init_hash "Direct link to private-initialization-nullifier-now-includes-init_hash") The private initialization nullifier is no longer derived from just the contract address. It is now computed as a Poseidon2 hash of `[address, init_hash]` using a dedicated domain separator. This prevents observers from determining whether a fully private contract has been initialized by simply knowing its address. Note that `Wallet.getContractMetadata` now returns `isContractInitialized: undefined` when the wallet does not have the contract instance registered, since `init_hash` is needed to compute the nullifier and initialization status cannot be determined. Previously, this check worked for any address. Callers should check for `undefined` before branching on the boolean value. If you use `assert_contract_was_initialized_by` or `assert_contract_was_not_initialized_by` from `aztec::history::deployment`, these now require an additional `init_hash: Field` parameter: ``` + let instance = get_contract_instance(contract_address); assert_contract_was_initialized_by( block_header, contract_address, + instance.initialization_hash, ); ``` ### Two separate init nullifiers for private and public[​](#two-separate-init-nullifiers-for-private-and-public "Direct link to Two separate init nullifiers for private and public") Contract initialization now emits two separate nullifiers instead of one: a **private init nullifier** and a **public init nullifier**. Each nullifier gates its respective execution domain: * Private external functions check the private init nullifier. * Public external functions check the public init nullifier. **How initializers work:** * **Private initializers** emit the private init nullifier. If the contract has any external public functions, the protocol auto-enqueues a public call to emit the public init nullifier. * **Public initializers** emit both nullifiers directly. * Contracts with no public functions only emit the private init nullifier. **`only_self` functions no longer have init checks.** They behave as if marked `noinitcheck`. **External functions called during private initialization must be `#[only_self]`.** Init nullifiers are emitted at the end of the initializer, so any external functions called on the initializing contract (e.g. via `enqueue_self` or `call_self`) during initialization will fail the init check unless they skip it. **Breaking change for deployment:** If your contract has external public functions and a private initializer, the class must be registered onchain before initialization. You can no longer pass `skipClassPublication: true`, because the auto-enqueued public call requires the class to be available. ``` const deployed = await MyContract.deploy(wallet, ...args).send({ - skipClassPublication: true, }).deployed(); ``` ## 4.1.3[​](#413 "Direct link to 4.1.3") ### \[Aztec.js] `TxReceipt` now includes `epochNumber`[​](#aztecjs-txreceipt-now-includes-epochnumber "Direct link to aztecjs-txreceipt-now-includes-epochnumber") `TxReceipt` now includes an `epochNumber` field that indicates which epoch the transaction was included in. ### \[Aztec.js] `computeL2ToL1MembershipWitness` signature changed[​](#aztecjs-computel2tol1membershipwitness-signature-changed "Direct link to aztecjs-computel2tol1membershipwitness-signature-changed") The function signature has changed to resolve the epoch internally from a transaction hash, rather than requiring the caller to pass the epoch number. **Migration:** ``` - const witness = await computeL2ToL1MembershipWitness(aztecNode, epochNumber, messageHash); - // epoch was passed in by the caller + const witness = await computeL2ToL1MembershipWitness(aztecNode, messageHash, txHash); + // epoch is now available on the returned witness + const epoch = witness.epochNumber; ``` The return type `L2ToL1MembershipWitness` now includes `epochNumber`. An optional `messageIndexInTx` parameter can be passed as the fourth argument to disambiguate when a transaction emits multiple identical L2-to-L1 messages. **Impact**: All call sites that compute L2-to-L1 membership witnesses must update to the new argument order and extract `epochNumber` from the result instead of passing it in. ### \[Aztec.js] `getPublicEvents` now returns an object instead of an array[​](#aztecjs-getpublicevents-now-returns-an-object-instead-of-an-array "Direct link to aztecjs-getpublicevents-now-returns-an-object-instead-of-an-array") `getPublicEvents` now returns a `GetPublicEventsResult` object with `events` and `maxLogsHit` fields instead of a plain array. This enables pagination through large result sets using the new `afterLog` filter option. ``` - const events = await getPublicEvents(node, MyContract.events.MyEvent, filter); + const { events } = await getPublicEvents(node, MyContract.events.MyEvent, filter); ``` The `maxLogsHit` flag indicates whether the log limit was reached, meaning more results may be available. You can use `afterLog` in the filter to fetch the next page. ### \[Aztec.nr] Removed `get_random_bytes`[​](#aztecnr-removed-get_random_bytes "Direct link to aztecnr-removed-get_random_bytes") The `get_random_bytes` unconstrained function has been removed from `aztec::utils::random`. If you were using it, you can replace it with direct calls to the `random` oracle from `aztec::oracle::random` and convert to bytes yourself. ## 4.1.0-rc.2[​](#410-rc2 "Direct link to 4.1.0-rc.2") ### \[Aztec.js] `simulate()`, `send()`, and deploy return types changed to always return objects[​](#aztecjs-simulate-send-and-deploy-return-types-changed-to-always-return-objects "Direct link to aztecjs-simulate-send-and-deploy-return-types-changed-to-always-return-objects") All SDK interaction methods now return structured objects that include offchain output alongside the primary result. This affects `.simulate()`, `.send()`, deploy `.send()`, and `Wallet.sendTx()`. **Impact**: Every call site that uses `.simulate()`, `.send()`, or deploy must destructure the result. This is a mechanical transformation. Custom wallet implementations must update `sendTx()` to return the new object shapes, using `extractOffchainOutput` to decode offchain messages from raw effects. The offchain output includes two fields: * `offchainEffects` — raw offchain effects emitted during execution, other than `offchainMessages` * `offchainMessages` — decoded messages intended for specific recipients We are making this change now so in the future we can add more fields to the responses of this APIs without breaking backwards compatibility, so this won't ever happen again. **`simulate()` — always returns `{ result, offchainEffects, offchainMessages }` object:** ``` - const value = await contract.methods.foo(args).simulate({ from: sender }); + const { result: value } = await contract.methods.foo(args).simulate({ from: sender }); ``` When using `includeMetadata` or `fee.estimateGas`, `stats` and `estimatedGas` are also available as optional fields on the same object: ``` - const { stats, estimatedGas } = await contract.methods.foo(args).simulate({ + const sim = await contract.methods.foo(args).simulate({ from: sender, includeMetadata: true, }); + const stats = sim.stats!; + const estimatedGas = sim.estimatedGas!; ``` `SimulationReturn` is no longer a generic conditional type — it's a single flat type with optional `stats` and `estimatedGas` fields. **`send()` — returns `{ receipt, offchainEffects, offchainMessages }` object:** ``` - const receipt = await contract.methods.foo(args).send({ from: sender }); + const { receipt } = await contract.methods.foo(args).send({ from: sender }); ``` When using `NO_WAIT`, returns `{ txHash, offchainEffects, offchainMessages }` instead of a bare `TxHash`: ``` - const txHash = await contract.methods.foo(args).send({ from: sender, wait: NO_WAIT }); + const { txHash } = await contract.methods.foo(args).send({ from: sender, wait: NO_WAIT }); ``` Offchain messages emitted by the transaction are available on the result: ``` const { receipt, offchainMessages } = await contract.methods .foo(args) .send({ from: sender }); for (const msg of offchainMessages) { console.log( `Message for ${msg.recipient} from contract ${msg.contractAddress}:`, msg.payload, ); } ``` **Deploy — returns `{ contract, receipt, offchainEffects, offchainMessages }` object:** ``` - const myContract = await MyContract.deploy(wallet, ...args).send({ from: sender }); + const { contract: myContract } = await MyContract.deploy(wallet, ...args).send({ from: sender }); ``` The deploy receipt is also available via `receipt` if needed (e.g. for `receipt.txHash` or `receipt.transactionFee`). **Custom wallet implementations — `sendTx()` must return objects:** If you implement the `Wallet` interface (or extend `BaseWallet`), the `sendTx()` method must now return objects that include offchain output. Use `extractOffchainOutput` to split raw effects into decoded messages and remaining effects: ``` + import { extractOffchainOutput } from '@aztec/aztec.js/contracts'; async sendTx(executionPayload, opts) { const provenTx = await this.pxe.proveTx(...); + const offchainOutput = extractOffchainOutput(provenTx.getOffchainEffects()); const tx = await provenTx.toTx(); const txHash = tx.getTxHash(); await this.aztecNode.sendTx(tx); if (opts.wait === NO_WAIT) { - return txHash; + return { txHash, ...offchainOutput }; } const receipt = await waitForTx(this.aztecNode, txHash, opts.wait); - return receipt; + return { receipt, ...offchainOutput }; } ``` ### \[Aztec.js] Removed `SingleKeyAccountContract`[​](#aztecjs-removed-singlekeyaccountcontract "Direct link to aztecjs-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. ### Scope enforcement for private state access (TXE and PXE)[​](#scope-enforcement-for-private-state-access-txe-and-pxe "Direct link to 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)[​](#noir-developers-txe "Direct link to 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)[​](#aztecjs-developers-pxewallet "Direct link to 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`?** 1. **Deploying contracts whose constructor initializes private storage** (e.g., account contracts, or any contract using `SinglePrivateImmutable`/`SinglePrivateMutable` in the constructor). The contract's own address must be in scope so its nullifier key is accessible during initialization. 2. **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`[​](#simulateutility-renamed-to-executeutility "Direct link to 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[​](#400-devnet2-patch0 "Direct link to 4.0.0-devnet.2-patch.0") ### \[Protocol] `include_by_timestamp` renamed to `expiration_timestamp`[​](#protocol-include_by_timestamp-renamed-to-expiration_timestamp "Direct link to 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[​](#cli-dockerless-cli-installation "Direct link to \[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 ``` **New installation:** ``` VERSION= bash -i <(curl -sL https://install.aztec.network/) ``` For example, to install version `4.3.1`: ``` VERSION=4.3.1 bash -i <(curl -sL https://install.aztec.network/4.3.1) ``` **Key changes:** * Docker is no longer required to run the Aztec CLI tools * The `VERSION` environment 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 ` | Install a specific version and switch to it | | `aztec-up use ` | 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`[​](#aztectest-wallet-replaced-by-aztecwallets "Direct link to aztectest-wallet-replaced-by-aztecwallets") 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`[​](#aztecnr-debug_log-module-renamed-to-logging "Direct link to aztecnr-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[​](#aztecnode-sentinel-validator-status-values-renamed "Direct link to \[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[​](#aztecjs-getdecodedpublicevents-renamed-to-getpublicevents-with-new-signature "Direct link to aztecjs-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`[​](#aztecnr-nophasecheck-renamed-as-allow_phase_change "Direct link to aztecnr-nophasecheck-renamed-as-allow_phase_change") ### \[AztecNode] Removed sibling path RPC methods[​](#aztecnode-removed-sibling-path-rpc-methods "Direct link to \[AztecNode] Removed sibling path RPC methods") The following methods have been removed from the `AztecNode` interface: * `getNullifierSiblingPath` * `getNoteHashSiblingPath` * `getArchiveSiblingPath` * `getPublicDataSiblingPath` 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)[​](#protocol-nullifier-secret-key-renamed-to-nullifier-hiding-key-nsk--nhk "Direct link to \[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`[​](#aztecnodeaztecnr-getarchivemembershipwitness-renamed-to-getblockhashmembershipwitness "Direct link to aztecnodeaztecnr-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`[​](#aztecnr-protocol_types-renamed-to-protocol "Direct link to aztecnr-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[​](#protocol-contract-interface-separate-from-protocol-contracts "Direct link to 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[​](#aztec-nr-history-module-refactored-to-use-standalone-functions "Direct link to \[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[​](#aztecjs-transaction-sending-api-redesign "Direct link to \[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[​](#deployment-changes "Direct link to 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[​](#breaking-changes-to-wallet-interface "Direct link to 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 + sendTx( + payload: ExecutionPayload, + options: SendOptions + ): Promise> ``` #### Manual waiting with `waitForTx`[​](#manual-waiting-with-waitfortx "Direct link to 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[​](#aztec-nr-removal-of-intermediate-modules "Direct link to \[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[​](#l1-contracts-fee-asset-pricing-direction-inverted "Direct link to \[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.ethPerFeeAsset` * `RollupConfigInput` now requires `initialEthPerFeeAsset` parameter at deployment * Default value: `1e7` (0.00001 ETH per AZTEC) * Valid range: `100` (1e-10 ETH/AZTEC) to `1e11` (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[​](#l1-contracts-fee-asset-price-modifier-now-in-basis-points "Direct link to \[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: `-100` to `+100` BPS (±1% max change per checkpoint) * A value of `+100` increases the price by 1%, `-100` decreases by 1% * Validated by `MAX_FEE_ASSET_PRICE_MODIFIER_BPS = 100` ### \[Aztec.js] Wallet batching now supports all methods[​](#aztecjs-wallet-batching-now-supports-all-methods "Direct link to \[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[​](#aztecjs-refactored-getcontractmetadata-and-getcontractclassmetadata-in-wallet "Direct link to aztecjs-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 `registerContract` are needed ### \[Aztec.js] Removed `UnsafeContract` and protocol contract helper functions[​](#aztecjs-removed-unsafecontract-and-protocol-contract-helper-functions "Direct link to aztecjs-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[​](#aztecnr-renamed-router-contract "Direct link to \[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[​](#aztec-node-getblockbyhash-and-getblockheaderbyhash-removed "Direct link to 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[​](#aztecnr-oracle-functions-now-take-blockheader-instead-of-block-number "Direct link to aztecnr-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[​](#toolchain-nodejs-upgraded-to-v24 "Direct link to \[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[​](#l1-contracts-renamed-base-fee-to-min-fee "Direct link to \[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` → `getManaMinFeeAt` * `getManaBaseFeeComponentsAt` → `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[​](#aztecjs-renamed-base-fee-to-min-fee "Direct link to \[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[​](#aztecnr-renamed-fee-context-methods "Direct link to \[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[​](#aztecnr-cleaning-up-message-sender-functions "Direct link to \[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 = self.msg_sender(); + let maybe_message_sender: Option = 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[​](#aztecnr-renamed-message-delivery-options "Direct link to \[Aztec.nr] Renamed message delivery options") The following terms have been renamed: * `MessageDelivery::UNCONSTRAINED_OFFCHAIN` -> `MessageDelivery::OFFCHAIN` * `MessageDelivery::UNCONSTRAINED_ONCHAIN` -> `MessageDelivery::ONCHAIN_UNCONSTRAINED` * `MessageDelivery::CONSTRAINED_ONCHAIN` -> `MessageDelivery::ONCHAIN_CONSTRAINED` We believe these names will better convey the meaning of the concepts. ### \[Aztec Node] changes to `getLogsByTags` endpoint[​](#aztec-node-changes-to-getlogsbytags-endpoint "Direct link to aztec-node-changes-to-getlogsbytags-endpoint") `getLogsByTags` endpoint has been optimized for our new log sync algorithm and these are the changes: * The `logsPerTag` pagination 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 `TxScopedL2Log` has been revised to meet the requirements of our new log sync algorithm. * The endpoint has been separated into two versions: `getPrivateLogsByTags` and `getPublicLogsByTagsFromContract`. This change was made because it was never desirable in PXE to mix public and private logs. The public version requires both a `Tag` and a contract address as input. In contrast to the private version—which uses `SiloedTag` (a tag that hashes the raw tag with the emitting contract's address)—the public version uses the raw `Tag` type, 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[​](#avm-gas-cost-multipliers-for-public-execution-to-reach-simulationproving-parity "Direct link to \[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`[​](#pxe-deprecated-getnotes "Direct link to 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[​](#300-devnet20251212 "Direct link to 3.0.0-devnet.20251212") ### \[Aztec node, archiver] Deprecated `getPrivateLogs`[​](#aztec-node-archiver-deprecated-getprivatelogs "Direct link to 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[​](#aztecnr-private-event-emission-api-changes "Direct link to \[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[​](#aztecnr-history-proof-functions-no-longer-require-storage_slot-parameter "Direct link to aztecnr-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` - removed `storage_slot: Field` parameter * `BlockHeader::prove_note_validity` - removed `storage_slot: Field` parameter * `BlockHeader::prove_note_is_nullified` - removed `storage_slot: Field` parameter * `BlockHeader::prove_note_not_nullified` - removed `storage_slot: Field` parameter **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[​](#aztecnr-note-fields-are-now-public "Direct link to \[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` - `value` is now public, `new()` and `get_value()` removed * `AddressNote` - `address` is now public, `new()` and `get_address()` removed * `FieldNote` - `value` is now public, `new()` and `value()` 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`[​](#aztecnr-emit-renamed-to-deliver "Direct link to aztecnr-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`[​](#aztecnr-valuenote-renamed-to-fieldnote-and-value-note-crate-renamed-to-field-note "Direct link to aztecnr-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.toml` dependencies: `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` → `PrivateSet` ### \[Aztec.nr] New `balance-set` library for managing token balances[​](#aztecnr-new-balance-set-library-for-managing-token-balances "Direct link to aztecnr-new-balance-set-library-for-managing-token-balances") A new `balance-set` library has been created that provides `BalanceSet` for managing u128 token balances with `UintNote`. This consolidates balance management functionality that was previously duplicated across contracts. **Features:** * `add(amount: u128)` - Add to balance * `sub(amount: u128)` - Subtract from balance (with change note) * `try_sub(amount: u128, max_notes: u32)` - Attempt to subtract with configurable note limit * `balance_of()` - Get total balance (unconstrained) **Usage:** ``` use balance_set::BalanceSet; #[storage] struct Storage { balances: Owned, 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(); ``` ### \[Aztec.nr] `EasyPrivateUint` deprecated and removed[​](#aztecnr-easyprivateuint-deprecated-and-removed "Direct link to aztecnr-easyprivateuint-deprecated-and-removed") The `EasyPrivateUint` type and `easy-private-state` crate have been deprecated and removed. Use `BalanceSet` from the `balance-set` crate instead. **Migration:** * Remove `easy_private_state` dependency from `Nargo.toml` * Add `balance_set = { path = "../../../../aztec-nr/balance-set" }` to `Nargo.toml` * Update storage: `EasyPrivateUint` → `Owned, Context>` * Update method calls: * `add(amount, owner)` → `at(owner).add(amount).deliver(owner, MessageDelivery.CONSTRAINED_ONCHAIN)` * `sub(amount, owner)` → `at(owner).sub(amount).deliver(owner, MessageDelivery.CONSTRAINED_ONCHAIN)` * `get_value(owner)` → `at(owner).balance_of()` (returns `u128` instead of `Field`) ### \[Aztec.nr] `balance_utils` removed from `value-note` (now `field-note`)[​](#aztecnr-balance_utils-removed-from-value-note-now-field-note "Direct link to aztecnr-balance_utils-removed-from-value-note-now-field-note") The `balance_utils` module has been removed from the `field-note` crate (formerly `value-note`). If you need similar functionality, implement it locally in your contract or use `BalanceSet` for u128 balances. ### \[Aztec.nr] `filter_notes_min_sum` removed from `value-note` (now `field-note`)[​](#aztecnr-filter_notes_min_sum-removed-from-value-note-now-field-note "Direct link to aztecnr-filter_notes_min_sum-removed-from-value-note-now-field-note") The `filter_notes_min_sum` function has been removed from the `field-note` crate (formerly in `value-note`). If you need this functionality, copy it to your contract locally. This function was only used in specific test contracts and doesn't belong in the general-purpose note library. ### \[Aztec.nr] `derive_ecdh_shared_secret_using_aztec_address` removed[​](#aztecnr-derive_ecdh_shared_secret_using_aztec_address-removed "Direct link to aztecnr-derive_ecdh_shared_secret_using_aztec_address-removed") This function made it annoying to deal with invalid addresses in circuits. If you were using it, replace it with `derive_ecdh_shared_secret` instead: ``` -let shared_secret = derive_ecdh_shared_secret_using_aztec_address(secret, address).unwrap(); +let shared_secret = derive_ecdh_shared_secret(secret, address.to_address_point().unwrap().inner); ``` ### \[Aztec.nr] Note owner is now enshrined[​](#aztecnr-note-owner-is-now-enshrined "Direct link to \[Aztec.nr] Note owner is now enshrined") It turns out that in all cases a note always has a logical owner. For this reason we have decided to enshrine the concept of a note owner and you should drop the field from your note: ``` #[derive(Deserialize, Eq, Packable, Serialize)] #[note] pub struct ValueNote { value: Field, - owner: AztecAddress, } ``` The owner being enshrined means that our API explicitly expects it on the input. The `NoteHash` trait got modified as follows: ``` pub trait NoteHash { fn compute_note_hash( self, + owner: AztecAddress, storage_slot: Field, randomness: Field, ) -> Field; fn compute_nullifier( self, context: &mut PrivateContext, + owner: AztecAddress, note_hash_for_nullification: Field, ) -> Field; unconstrained fn compute_nullifier_unconstrained( self, + owner: AztecAddress, note_hash_for_nullification: Field, ) -> Field; } ``` Our low-level note utilities now also accept owner as a parameter: ``` pub fn create_note( context: &mut PrivateContext, + owner: AztecAddress, storage_slot: Field, note: Note, ) -> NoteEmission where Note: NoteType + NoteHash + Packable, { ... } ``` Signature of some functions like `destroy_note_unsafe` is unchanged: ``` pub fn destroy_note_unsafe( context: &mut PrivateContext, hinted_note: HintedNote, note_hash_read: NoteHashRead, ) where Note: NoteHash, { ... } ``` because `HintedNote` now contains owner. `PrivateImmutable`, `PrivateMutable` and `PrivateSet` got modified to directly contain the owner instead of implicitly "containing it" by including it in the storage slot via a `Map`. These state variables now implement a newly introduced `OwnedStateVariable` trait (see docs of `OwnedStateVariable` for explanation of what it is). These changes make the state variables incompatible with `Map` and now instead these should be wrapped in new `Owned` state variable: ``` #[storage] struct Storage { - private_nfts: Map, Context>, + private_nfts: Owned, Context>, } ``` Note that even though the types of your state variables are changing from `Map` to `Owned`, usage remains unchanged: ``` let nft_notes = self.storage.private_nfts.at(from).pop_notes(NoteGetterOptions::new().select(NFTNote::properties().token_id, Comparator.EQ, token_id).set_limit(1)); ``` With this change the underlying notes will inherit the storage slot of the `Owned` state variable. This is unlike `Map` where the nested state variable got the storage slot computed as `hash([map_storage_slot, key])`. if you had `PrivateImmutable` or `PrivateMutable` defined out of a `Map`, e.g.: ``` #[storage] struct Storage { signing_public_key: PrivateImmutable, } ``` you were most likely dealing with some kind of admin flow where only the admin can modify the state variable. Now, unfortunately, there is a bit of a regression and you will need to wrap the state variable in `Owned` and call `at` on the state var: ``` + use aztec::state_vars::Owned; #[storage] struct Storage { - signing_public_key: PrivateImmutable, + signing_public_key: Owned, Context>, } #[external("private")] fn my_external_function() { - self.storage.signing_public_key.initialize(pub_key_note) + self.storage.signing_public_key.at(self.address).initialize(pub_key_note) .emit(self.address, MessageDelivery.CONSTRAINED_ONCHAIN); } ``` We are likely to come up with a concept of admin state variables in the future. None of the reference notes now contain the owner so if you manually construct `AddressNote`, `UintNote` or `ValueNote` you need to update the call to `new` method: ``` - let note = UintNote::new(156, owner); + let note = UintNote::new(156); ``` ### \[Aztec.nr] Note randomness is now handled internally[​](#aztecnr-note-randomness-is-now-handled-internally "Direct link to \[Aztec.nr] Note randomness is now handled internally") In order to prevent pre-image attacks, it is necessary to inject randomness to notes. Aztec.nr users were previously expected to add said randomness to their custom note types. From now on, Aztec.nr takes care of handling randomness as built-in note metadata, making it impossible to miss for library users. This change breaks backwards compatibility as we'll discuss below. #### Changes to Aztec.nr note types[​](#changes-to-aztecnr-note-types "Direct link to Changes to Aztec.nr note types") If you're using any of the following note types, please be aware that `randomness` no longer is an explicit attribute in them. * ValueNote * UintNote * NFTNote * AddressNote #### Migrating your custom note types: refer to UintNote as an example of how to migrate[​](#migrating-your-custom-note-types-refer-to-uintnote-as-an-example-of-how-to-migrate "Direct link to Migrating your custom note types: refer to UintNote as an example of how to migrate") We show the changes to `UintNote` below since it serves as a good example of the adjustments you will need to make to your own custom note types, including those that need to support partial notes. ##### Remove `randomness` from note struct[​](#remove-randomness-from-note-struct "Direct link to remove-randomness-from-note-struct") ``` pub struct UintNote { /// The owner of the note, i.e. the account whose nullifier secret key is required to compute the nullifier. owner: AztecAddress, - /// Random value, protects against note hash preimage attacks. - randomness: Field, /// The number stored in the note. value: u128, } impl UintNote { pub fn new(value: u128, owner: AztecAddress) -> Self { - let randomness = unsafe { random() }; - Self { value, owner, randomness } + Self { value, owner } } ``` ##### Add `randomness` to `compute_note_hash` implementation[​](#add-randomness-to-compute_note_hash-implementation "Direct link to add-randomness-to-compute_note_hash-implementation") The `NoteHash` trait now requires `compute_note_hash` to receive a `randomness` field. This impacts ``` pub trait NoteHash { /// ... - fn compute_note_hash(self, storage_slot: Field) -> Field; + fn compute_note_hash(self, storage_slot: Field, randomness: Field) -> Field; ``` Then in trait implementations: ``` impl NoteHash for UintNote { - fn compute_note_hash(self, storage_slot: Field) -> Field { + fn compute_note_hash(self, storage_slot: Field, randomness: Field) -> Field { /// ... - let private_content = - UintPartialNotePrivateContent { owner: self.owner, randomness: self.randomness }; - let partial_note = PartialUintNote { - commitment: private_content.compute_partial_commitment(storage_slot), - }; + let private_content = + UintPartialNotePrivateContent { owner: self.owner }; + let partial_note = PartialUintNote { + commitment: private_content.compute_partial_commitment(storage_slot, randomness), + }; ``` It's worth noting that this change also affects how partial notes are structured and handled. ``` pub fn partial( owner: AztecAddress, storage_slot: Field, randomness: Field, context: &mut PrivateContext, recipient: AztecAddress, completer: AztecAddress, ) -> PartialUintNote { - let commitment = UintPartialNotePrivateContent { owner, randomness } - .compute_partial_commitment(storage_slot); + let commitment = UintPartialNotePrivateContent { owner } + .compute_partial_commitment(storage_slot, randomness); let private_log_content = - UintPartialNotePrivateLogContent { owner, randomness, public_log_tag: commitment }; + UintPartialNotePrivateLogContent { owner, public_log_tag: commitment }; let encrypted_log = note::compute_partial_note_private_content_log( private_log_content, storage_slot, + randomness, recipient, ); /// ... } struct UintPartialNotePrivateContent { owner: AztecAddress, - randomness: Field, } impl UintPartialNotePrivateContent { - fn compute_partial_commitment(self, storage_slot: Field) -> Field { + fn compute_partial_commitment(self, storage_slot: Field, randomness: Field) -> Field { poseidon2_hash_with_separator( - self.pack().concat([storage_slot]), + self.pack().concat([storage_slot, randomness]), DOM_SEP__NOTE_HASH, ) } } struct UintPartialNotePrivateLogContent { public_log_tag: Field, owner: AztecAddress, - randomness: Field, } ``` ##### Note size[​](#note-size "Direct link to Note size") As a result of this change, the maximum packed length of the content of a note is 11 fields, down from 12. This is a direct consequence of moving the randomness field from the note content structure to the note's metadata. #### HintedNote now includes randomness field[​](#hintednote-now-includes-randomness-field "Direct link to HintedNote now includes randomness field") ``` pub struct HintedNote { pub note: Note, pub contract_address: AztecAddress, + pub randomness: Field, pub metadata: NoteMetadata, } ``` ### \[L1 Contracts] `Block` is now `Checkpoint`[​](#l1-contracts-block-is-now-checkpoint "Direct link to l1-contracts-block-is-now-checkpoint") A `checkpoint` is now the primary unit handled by the L1 contracts. A checkpoint may contain one or more L2 blocks. The protocol circuits already support producing multiple blocks per checkpoint. Updating the L1 contracts to operate on checkpoints allow L2 blockchain to advance faster. Below are the API and event renames reflecting this change: ``` - event L2BlockProposed + event CheckpointProposed ``` ``` - event BlockInvalidated + event CheckpointInvalidated ``` ``` - function getEpochForBlock(uint256 _blockNumber) external view returns (Epoch); + function getEpochForCheckpoint(uint256 _checkpointNumber) external view returns (Epoch); ``` ``` - function getProvenBlockNumber() external view returns (uint256); + function getProvenCheckpointNumber() external view returns (uint256); ``` ``` - function getPendingBlockNumber() external view returns (uint256); + function getPendingCheckpointNumber() external view returns (uint256); ``` ``` - function getBlock(uint256 _blockNumber) external view returns (BlockLog memory); + function getCheckpoint(uint256 _checkpointNumber) external view returns (CheckpointLog memory); ``` ``` - function getBlockReward() external view returns (uint256); + function getCheckpointReward() external view returns (uint256); ``` Additionally, any function or struct that previously referenced an L2 block number now uses a checkpoint number instead: ``` - function status(uint256 _blockNumber) external view returns ( + function status(uint256 _checkpointNumber) external view returns ( - uint256 provenBlockNumber, + uint256 provenCheckpointNumber, bytes32 provenArchive, - uint256 pendingBlockNumber, + uint256 pendingCheckpointNumber, bytes32 pendingArchive, bytes32 archiveOfMyBlock, Epoch provenEpochNumber ); ``` Note: current node softwares still produce exactly one L2 block per checkpoint, so for now checkpoint numbers and L2 block numbers remain equal. This may change once multi-block checkpoints are enabled. ### \[L1 Contracts] L2-to-L1 messages are now grouped by epoch.[​](#l1-contracts-l2-to-l1-messages-are-now-grouped-by-epoch "Direct link to \[L1 Contracts] L2-to-L1 messages are now grouped by epoch.") L2-to-L1 messages are now aggregated and organized per epoch rather than per block. This change affects how you compute membership witnesses for consuming messages on L1. You now need to know the epoch number in which the message was emitted to retrieve and consume the message. **Note**: This is only an API change. The protocol behavior remains the same - messages can still only be consumed once an epoch is proven as before. #### What changed[​](#what-changed "Direct link to What changed") Previously, you might have computed the membership witness without explicitly needing the epoch: ``` const witness = await computeL2ToL1MembershipWitness( node, l2TxReceipt.blockNumber, l2ToL1Message, ); ``` Now, you should provide the epoch number: ``` const epoch = await rollup.getEpochNumberForCheckpoint( CheckpointNumber.fromBlockNumber(l2TxReceipt.blockNumber), ); const witness = await computeL2ToL1MembershipWitness( node, epoch, l2ToL1Message, ); ``` ### \[Aztec.js] Wallet interface changes[​](#aztecjs-wallet-interface-changes "Direct link to \[Aztec.js] Wallet interface changes") #### `simulateTx` is now batchable[​](#simulatetx-is-now-batchable "Direct link to simulatetx-is-now-batchable") The `simulateTx` method on the `Wallet` interface is now batchable, meaning it can be called as part of a batch operation using `wallet.batch()`. This allows you to batch simulations together with other wallet operations like `registerContract`, `sendTx`, and `registerSender`. ``` - // Could not batch simulations - const simulationResult = await wallet.simulateTx(executionPayload, options); + // Can now batch simulations with other operations + const results = await wallet.batch([ + { name: 'registerContract', args: [instance, artifact] }, + { name: 'simulateTx', args: [executionPayload, options] }, + { name: 'sendTx', args: [anotherPayload, sendOptions] }, + ]); ``` #### `ExecutionPayload` moved to `@aztec/stdlib/tx`[​](#executionpayload-moved-to-aztecstdlibtx "Direct link to executionpayload-moved-to-aztecstdlibtx") The `ExecutionPayload` type has been moved from `@aztec/aztec.js` to `@aztec/stdlib/tx`. Update your imports accordingly. ``` - import { ExecutionPayload } from '@aztec/aztec.js'; + import { ExecutionPayload } from '@aztec/stdlib/tx'; + // Or import from the re-export in aztec.js/tx: + import { ExecutionPayload } from '@aztec/aztec.js/tx'; ``` #### `ExecutionPayload` now includes `feePayer` property[​](#executionpayload-now-includes-feepayer-property "Direct link to executionpayload-now-includes-feepayer-property") The `ExecutionPayload` class now includes an optional `feePayer` property that specifies which address is paying for the fee in the execution payload (if any) ``` const payload = new ExecutionPayload( calls, authWitnesses, capsules, extraHashedArgs, + feePayer // optional AztecAddress ); ``` This was previously provided as part of the `SendOptions` (and others) in the wallet interface, which could cause problems if a payload was assembled with a payment method and the parameter was later omitted. This means `SendOptions` now loses `embeddedPaymentMethodFeePayer` ``` -wallet.simulateTx(executionPayload, { from: address, embeddedFeePaymentMethodFeePayer: feePayer }); +wallet.simulateTx(executionPayload, { from: address }); ``` #### `simulateUtility` signature and return type changed[​](#simulateutility-signature-and-return-type-changed "Direct link to simulateutility-signature-and-return-type-changed") The `simulateUtility` method signature has changed to accept a `FunctionCall` object instead of separate `functionName`, `args`, and `to` parameters. Additionally, the return type has changed from `AbiDecoded` to `Fr[]`. ``` - const result: AbiDecoded = await wallet.simulateUtility(functionName, args, to, authWitnesses); + const result: UtilitySimulationResult = await wallet.simulateUtility(functionCall, authWitnesses?); + // result.result is now Fr[] instead of AbiDecoded ``` The new signature takes: * `functionCall`: A `FunctionCall` object containing `name`, `args`, `to`, `selector`, `type`, `isStatic`, `hideMsgSender`, and `returnTypes` * `authWitnesses` (optional): An array of `AuthWitness` objects The first argument is exactly the same as what goes into `ExecutionPayload.calls`. As such, the data is already encoded. The return value is now `UtilitySimulationResult` with `result: Fr[]` instead of returning an `AbiDecoded` value directly. You'll need to decode the `Fr[]` array yourself if you need typed results. #### `Contract.at()` is now synchronous and no longer calls `registerContract`[​](#contractat-is-now-synchronous-and-no-longer-calls-registercontract "Direct link to contractat-is-now-synchronous-and-no-longer-calls-registercontract") The `Contract.at()` method (and generated contract `.at()` methods) is now synchronous and no longer automatically registers the contract with the wallet. This reduces unnecessary artifact storage and RPC calls. ``` - const contract = await TokenContract.at(address, wallet); + const contract = TokenContract.at(address, wallet); ``` **Important:** You now need to explicitly call `registerContract` if you want the wallet to store the contract instance and artifact. This is only necessary when: * An app first registers a contract * An app tries to update a contract's artifact If you need to register the contract, do so explicitly: ``` // Get the instance from deployment const { contract, instance } = await TokenContract.deploy(wallet, ...args) .send({ from: address }) .wait(); // wallet already has it registered, since the deploy method does it by default // to avoid it, set skipContractRegistration: true in the send options. // Register it with another wallet await otherWallet.registerContract(instance, TokenContract.artifact); // Now you can use the contract const otherContract = TokenContract.at(instance.address, otherWallet); ``` Publicly deployed contract instances can be retrieved via `node.getContract(address)`. Otherwise and if deployment parameters are known, an instance can be computed via the `getContractInstanceFromInstantiationParams` from `@aztec/aztec.js/contracts` #### `registerContract` signature simplified[​](#registercontract-signature-simplified "Direct link to registercontract-signature-simplified") The `registerContract` method now takes a `ContractInstanceWithAddress` instead of a `Contract` object, and the `artifact` parameter is now optional. If the artifact is not provided, the wallet will attempt to look it up from its contract class storage. ``` - await wallet.registerContract(contract); + await wallet.registerContract(instance, artifact?); ``` The method now only accepts: * `instance`: A `ContractInstanceWithAddress` object * `artifact` (optional): A `ContractArtifact` object * `secretKey` (optional): A secret key for privacy keys registration #### Return value of `getNotes` no longer contains a recipient and it contains some other additional info[​](#return-value-of-getnotes-no-longer-contains-a-recipient-and-it-contains-some-other-additional-info "Direct link to return-value-of-getnotes-no-longer-contains-a-recipient-and-it-contains-some-other-additional-info") Return value of `getNotes` used to be defined as `Promise` and is now defined as `Promise`. `NoteDao` is mostly a super-set of `UniqueNote` but it doesn't contain a `recipient`. Having the recipient in the return value has been redundant as the same outcome can be achieved by populating the `scopes` array in `NoteFilter` with the `recipient` value. #### Changes to `getPrivateEvents`[​](#changes-to-getprivateevents "Direct link to changes-to-getprivateevents") The signature of `getPrivateEvents` has changed for two reasons: 1. To align it with how other query methods that include filtering by block range work (for example, `AztecNode#getPublicLogs`) 2. To enrich the returned private events with metadata. ``` getPrivateEvents( - contractAddress: AztecAddress, - eventMetadata: EventMetadataDefinition, - from: number, - numBlocks: number, - recipients: AztecAddress[], - ): Promise; + eventFilter: PrivateEventFilter, + ): Promise[]>; ``` `PrivateEvent` bundles together an ABI decoded event of type `T`, with `metadata` of type `InTx`: ``` export type InBlock = { l2BlockNumber: BlockNumber; l2BlockHash: L2BlockHash; }; export type InTx = InBlock & { txHash: TxHash; }; export type PrivateEvent = { event: T; metadata: InTx; }; ``` You will need to update any calls to `Wallet#getPrivateEvents` accordingly. See below for before/after comparison which conserves semantics. Pay special attention to the fact that the old method expects a `numBlocks` parameter that instructs it to return `numBlocks` blocks after `fromBlock`, whereas the new version expects an (exclusive) `toBlock` block number. Also note we're replacing *recipient* terminology with *scope*. While underlying data types are equivalent (they are Aztec addresses), they have different semantics. Messages have a recipient who will be able to receive and process them. As a result of processing messages for a given recipient address, PXE might discover events. Those events are then said to be *in scope* for that address. ``` - const events = await context.client.getPrivateEvents(contractAddress, eventMetadata, 42, 10, [recipient]); - doSomethingWithAnEvent(events[0]); + const events = await context.client.getPrivateEvents(eventMetadata, { + contractAddress, + fromBlock: BlockNumber(42), + toBlock: BlockNumber(42 + 10), + scopes: [scope], + }); + doSomethingWithAnEvent(events[0].event); ``` Please refer to the wallet interface js-docs for further details. ### \[CLI] Command refactor[​](#cli-command-refactor "Direct link to \[CLI] Command refactor") The sandbox command has been renamed and remapped to "local network". We believe this conveys better what is actually being spun up when running it. **REMOVED/RENAMED**: * `aztec start --sandbox`: now `aztec start --local-network` ### \[Aztec.nr] - Contract API redesign[​](#aztecnr---contract-api-redesign "Direct link to \[Aztec.nr] - Contract API redesign") In this release we decided to largely redesign our contract API. Most of the changes here are not a breaking change (only renaming of original `#[internal]` to `#[only_self]` and `storage` now being available on the newly introduced `self` struct are a breaking change). #### 1. Renaming of original #\[internal] as #\[only\_self][​](#1-renaming-of-original-internal-as-only_self "Direct link to 1. Renaming of original #\[internal] as #\[only_self]") We want for internal to mean the same as in Solidity where internal function can be called only from the same contract and is also inlined (EVM JUMP opcode and not EVM CALL). The original implementation of our `#[internal]` macro also results in the function being callable only from the same contract but it results in a different call (hence it doesn't map to EVM JUMP). This is very confusing for people that know Solidity hence we are doing the rename. A true `#[internal]` will be introduced in the future. To migrate your contracts simply rename all the occurrences of `#[internal]` with `#[only_self]` and update the imports: ``` - use aztec::macros::functions::internal; + use aztec::macros::functions::only_self; ``` ``` #[external("public")] - #[internal] + #[only_self] fn _deduct_public_balance(owner: AztecAddress, amount: u64) { ... } ``` #### 2. Introducing of new #\[internal][​](#2-introducing-of-new-internal "Direct link to 2. Introducing of new #\[internal]") Same as in Solidity internal functions are functions that are callable from inside the contract. Unlike #\[only\_self] functions, internal functions are inlined (e.g. akin to EVM's JUMP and not EVM's CALL). Internal function can be called using the following API which leverages the new `self` struct (see change 3 below for details): ``` self.internal.my_internal_function(...) ``` Private internal functions can only be called from other private external or internal functions. Public internal functions can only be called from other public external or internal functions. #### 3. Introducing `self` in contracts and a new call interface[​](#3-introducing-self-in-contracts-and-a-new-call-interface "Direct link to 3-introducing-self-in-contracts-and-a-new-call-interface") Aztec contracts now automatically inject a `self` parameter into every contract function, providing a unified interface for accessing the contract's address, storage, calling of function and an execution context. ##### What is `self`?[​](#what-is-self "Direct link to what-is-self") `self` is an instance of `ContractSelf` that provides: * `self.address` - The contract's own address * `self.storage` - Access to your contract's storage * `self.context` - The execution context (private, public, or utility) * `self.msg_sender()` - Get the address of the caller * `self.emit(...)` - Emit events * `self.call(...)` - Call an external function * `self.view(...)` - Call an external function statically * `self.enqueue(...)` - Enqueue a call to an external function * `self.enqueue_view(...)` - Enqueue a call to an external function * `self.enqueue_incognito(...)` - Enqueue a call to an external function but hides the `msg_sender` * `self.enqueue_view_incognito(...)` - Enqueue a static call to an external function but hides the `msg_sender` * `self.set_as_teardown(...)` - Enqueue a call to an external public function and sets the call as teardown * `self.set_as_teardown_incognito(...)` - Enqueue a call to an external public function and sets the call as teardown and hides the `msg_sender` * `self.internal.my_internal_fn(...)` - Call an internal function `self` also provides you with convenience API to call and enqueue calls to external functions from within the same contract (this is just a convenience API as `self.call(MyContract::at(self.address).my_external_fn(...))` would also work): * `self.call_self.my_external_fn(...)` - Call external function from within the same contract * `self.enqueue_self.my_public_external_fn(...)` * `self.call_self_static.my_static_external_fn(...)` * `self.enqueue_self_static.my_static_external_public_fn(...)` ##### How it works[​](#how-it-works "Direct link to How it works") The `#[external(...)]` macro automatically injects `self` into your function. When you write: ``` #[external("private")] fn transfer(amount: u128, recipient: AztecAddress) { let sender = self.msg_sender().unwrap(); self.storage.balances.at(sender).sub(amount); self.storage.balances.at(recipient).add(amount); } ``` The macro transforms it to initialize `self` with the context and storage before your code executes. ##### Migration guide[​](#migration-guide "Direct link to Migration guide") **Before:** Access context and storage as separate parameters ``` #[external("private")] fn old_transfer(amount: u128, recipient: AztecAddress) { let storage = Storage::init(context); let sender = context.msg_sender().unwrap(); storage.balances.at(sender).sub(amount); } ``` **After:** Use `self` to access everything ``` #[external("private")] fn new_transfer(amount: u128, recipient: AztecAddress) { let sender = self.msg_sender().unwrap(); self.storage.balances.at(sender).sub(amount); } ``` ##### Key changes[​](#key-changes "Direct link to Key changes") 1. **Storage and context access:** Storage and context are no longer injected into the function as standalone variables and instead you need to access them via `self`: ``` - let balance = storage.balances.at(owner).read(); + let balance = self.storage.balances.at(owner).read(); ``` ``` - context.push_nullifier(nullifier); + self.context.push_nullifier(nullifier); ``` Note that `context` is expected to be use only when needing to access a low-level API (like directly emitting a nullifier). 2. **Getting caller address:** Use `self.msg_sender()` instead of `context.msg_sender()` ``` - let caller = context.msg_sender().unwrap(); + let caller = self.msg_sender().unwrap(); ``` 3. **Getting contract address:** Use `self.address` instead of `context.this_address()` ``` - let this_contract = context.this_address(); + let this_contract = self.address; ``` 4. **Emitting events:** In private functions: ``` - emit_event_in_private(event, context, recipient, delivery_mode); + self.emit(event, recipient, delivery_mode); ``` In public functions: ``` - emit_event_in_public(event, context); + self.emit(event); ``` 5. **Calling functions:** In private functions: ``` - Token::at(stable_coin).mint_to_public(to, amount).call(&mut context); + self.call(Token::at(stable_coin).mint_to_public(to, amount)); ``` ##### Example: Full contract migration[​](#example-full-contract-migration "Direct link to Example: Full contract migration") **Before:** ``` #[external("private")] fn withdraw(amount: u128, recipient: AztecAddress) { let storage = Storage::init(context); let sender = context.msg_sender().unwrap(); let token = storage.donation_token.get_note().get_address(); // ... withdrawal logic emit_event_in_private(Withdraw { withdrawer, amount }, context, withdrawer, MessageDelivery.UNCONSTRAINED_ONCHAIN); } ``` **After:** ``` #[external("private")] fn withdraw(amount: u128, recipient: AztecAddress) { let sender = self.msg_sender().unwrap(); let token = self.storage.donation_token.get_note().get_address(); // ... withdrawal logic self.emit(Withdraw { withdrawer, amount }, withdrawer, MessageDelivery.UNCONSTRAINED_ONCHAIN); } ``` #### No-longer allowing calling of non-view function statically via the old higher-level API[​](#no-longer-allowing-calling-of-non-view-function-statically-via-the-old-higher-level-api "Direct link to No-longer allowing calling of non-view function statically via the old higher-level API") We used to allow calling of non-view function statically as follows: ``` MyContract::at(address).my_non_view_function(...).view(context); MyContract::at(address).my_non_view_function(...).enqueue_view(context); ``` This is no-longer allowed and if you will want to call a function statically you will need to mark the function with `#[view]`. ### Phase checks[​](#phase-checks "Direct link to Phase checks") Now private external functions check by default that no phase change from non revertible to revertible happens during the execution of the function or any of its nested calls. If you're developing a function that handles phase change (you call `context.end_setup()` or call a function that you expect will change phase) you need to opt out of the phase check using the `#[nophasecheck]` macro. Also, now it's possible to know if you're in the revertible phase of the transaction at any point using `self.context.in_revertible_phase()`. ### \[`aztec` command] Moving functionality of `aztec-nargo` to `aztec` command[​](#aztec-command-moving-functionality-of-aztec-nargo-to-aztec-command "Direct link to aztec-command-moving-functionality-of-aztec-nargo-to-aztec-command") `aztec-nargo` has been deprecated and all workflows should now migrate to the `aztec` command that fully replaces `aztec-nargo`: * **For contract initialization:** ``` aztec init ``` (Behaves like `nargo init`, but defaults to a contract project.) * **For testing:** ``` aztec test ``` (Starts the Aztec TXE and runs your tests.) * **For compiling contracts:** ``` aztec compile ``` (Transpiles your contracts and generates verification keys.) ## 3.0.0-devnet.4[​](#300-devnet4 "Direct link to 3.0.0-devnet.4") ## \[aztec.js] Removal of barrel export[​](#aztecjs-removal-of-barrel-export "Direct link to \[aztec.js] Removal of barrel export") `aztec.js` is now divided into granular exports, which improves loading performance in node.js and also makes the job of web bundlers easier: ``` -import { AztecAddress, Fr, getContractInstanceFromInstantiationParams, type Wallet } from '@aztec/aztec.js'; +import { AztecAddress } from '@aztec/aztec.js/addresses'; +import { getContractInstanceFromInstantiationParams } from '@aztec/aztec.js/contracts'; +import { Fr } from '@aztec/aztec.js/fields'; +import type { Wallet } from '@aztec/aztec.js/wallet'; ``` Additionally, some general utilities reexported from `foundation` have been removed: ``` -export { toBigIntBE } from '@aztec/foundation/bigint-buffer'; -export { sha256, Grumpkin, Schnorr } from '@aztec/foundation/crypto'; -export { makeFetch } from '@aztec/foundation/json-rpc/client'; -export { retry, retryUntil } from '@aztec/foundation/retry'; -export { to2Fields, toBigInt } from '@aztec/foundation/serialize'; -export { sleep } from '@aztec/foundation/sleep'; -export { elapsed } from '@aztec/foundation/timer'; -export { type FieldsOf } from '@aztec/foundation/types'; -export { fileURLToPath } from '@aztec/foundation/url'; ``` ### `getSenders` renamed to `getAddressBook` in wallet interface[​](#getsenders-renamed-to-getaddressbook-in-wallet-interface "Direct link to getsenders-renamed-to-getaddressbook-in-wallet-interface") An app could request "contacts" from the wallet, which don't necessarily have to be senders in the wallet's PXE. This method has been renamed to reflect that fact: ``` -wallet.getSenders(); +wallet.getAddressBook(); ``` ### Removal of `proveTx` from `Wallet` interface[​](#removal-of-provetx-from-wallet-interface "Direct link to removal-of-provetx-from-wallet-interface") Exposing this method on the interface opened the door for certain types of attacks, were an app could route proven transactions through malicious nodes (that stored them for later decryption, or collected user IPs for example). It also made transactions difficult to track for the wallet, since they could be sent without their knowledge at any time. This change also affects `ContractFunctionInteraction` and `DeployMethod`, which no longer expose a `prove()` method. ### `msg_sender` is now an `Option` type.[​](#msg_sender-is-now-an-optionaztecaddress-type "Direct link to msg_sender-is-now-an-optionaztecaddress-type") Because Aztec has native account abstraction, the very first function call of a tx has no `msg_sender`. (Recall, the first function call of an Aztec transaction is always a *private* function call). Previously (before this change) we'd been silently setting this first `msg_sender` to be `AztecAddress::from_field(-1);`, and enforcing this value in the protocol's kernel circuits. Now we're passing explicitness to smart contract developers by wrapping `msg_sender` in an `Option` type. We'll explain the syntax shortly. We've also added a new protocol feature. Previously (before this change) whenever a public function call was enqueued by a private function (a so-called private->public call), the called public function (and hence the whole world) would be able to see `msg_sender`. For some use cases, visibility of `msg_sender` is important, to ensure the caller executed certain checks in private-land. For `#[only_self]` public functions, visibility of `msg_sender` is unavoidable (the caller of an `#[only_self]` function must be the same contract address by definition). But for *some* use cases, a visible `msg_sender` is an unnecessary privacy leakage. We therefore have added a feature where `msg_sender` can be optionally set to `Option::none()` for enqueued public function calls (aka private->public calls). We've been colloquially referring to this as "setting msg\_sender to null". #### Aztec.nr diffs[​](#aztecnr-diffs "Direct link to Aztec.nr diffs") > Note: we'll be doing another pass at this aztec.nr syntax in the near future. Given the above, the syntax for accessing `msg_sender` in Aztec.nr is slightly different: For most public and private functions, to adjust to this change, you can make this change to your code: ``` - let sender: AztecAddress = context.msg_sender(); + let sender: AztecAddress = context.msg_sender().unwrap(); ``` Recall that `Option::unwrap()` will throw if the Option is "none". Indeed, most smart contract functions will require access to a proper contract address (instead of a "null" value), in order to do bookkeeping (allocation of state variables against user addresses), and so in such cases throwing is sensible behaviour. If you want to output a useful error message when unwrapping fails, you can use `Option::expect`: ``` - let sender: AztecAddress = context.msg_sender(); + let sender: AztecAddress = context.msg_sender().expect(f"Sender must not be none!"); ``` For a minority of functions, a "null" msg\_sender will be acceptable: * A private entrypoint function. * A public function which doesn't seek to do bookkeeping against `msg_sender`. Some apps might even want to *assert* that the `msg_sender` is "null" to force their users into strong privacy practices: ``` let sender: Option = context.msg_sender(); assert(sender.is_none()); ``` ##### Enqueueing public function calls[​](#enqueueing-public-function-calls "Direct link to Enqueueing public function calls") ###### Auto-generated contract interfaces[​](#auto-generated-contract-interfaces "Direct link to Auto-generated contract interfaces") When you use the `#[aztec]` macro, it will generate a noir contract interface for your contract, behind the scenes. This provides pretty syntax when you come to call functions of that contract. E.g.: ``` Token::at(context.this_address())._increase_public_balance(to, amount).enqueue(&mut context); ``` In keeping with this new feature of being able to enqueue public function calls with a hidden `msg_sender`, there are some new methods that can be chained instead of `.enqueue(...)`: * `enqueue_incognito` -- akin to `enqueue`, but `msg_sender` is set "null". * `enqueue_view_incognito` -- akin to `enqueue_view`, but `msg_sender` is "null". * `set_as_teardown_incognito` -- akin to `set_as_teardown`, but `msg_sender` is "null". > The name "incognito" has been chosen to imply "msg\_sender will not be visible to observers". These new functions enable the *calling* contract to specify that it wants its address to not be visible to the called public function. This is worth re-iterating: it is the *caller's* choice. A smart contract developer who uses these functions must be sure that the target public function will accept a "null" `msg_sender`. It would not be good (for example) if the called public function did `context.msg_sender().unwrap()`, because then a public function that is called via `enqueue_incognito` would *always fail*! Hopefully smart contract developers will write sufficient tests to catch such problems during development! ###### Making lower-level public function calls from the private context[​](#making-lower-level-public-function-calls-from-the-private-context "Direct link to Making lower-level public function calls from the private context") This is discouraged vs using the auto-generated contract interfaces described directly above. If you do use any of these low-level methods of the `PrivateContext` in your contract: * `call_public_function` * `static_call_public_function` * `call_public_function_no_args` * `static_call_public_function_no_args` * `call_public_function_with_calldata_hash` * `set_public_teardown_function` * `set_public_teardown_function_with_calldata_hash` ... there is a new `hide_msg_sender: bool` parameter that you will need to specify. #### Aztec.js diffs[​](#aztecjs-diffs "Direct link to Aztec.js diffs") > Note: we'll be doing another pass at this aztec.js syntax in the near future. When lining up a new tx, the `FunctionCall` struct has been extended to include a `hide_msg_sender: bool` field. * `is_public & hide_msg_sender` -- will make a public call with `msg_sender` set to "null". * `is_public & !hide_msg_sender` -- will make a public call with a visible `msg_sender`, as was the case before this new feature. * `!is_public & hide_msg_sender` -- Incompatible flags. * `!is_public & !hide_msg_sender` -- will make a private call with a visible `msg_sender` (noting that since it's a private function call, the `msg_sender` will only be visible to the called private function, but not to the rest of the world). ## \[cli-wallet][​](#cli-wallet "Direct link to \[cli-wallet]") The `deploy-account` command now requires the address (or alias) of the account to deploy as an argument, not a parameter ``` +aztec-wallet deploy-account main -aztec-wallet deploy-account -f main ``` This release includes a major architectural change to the system. The PXE JSON RPC Server has been removed, and PXE is now available only as a library to be used by wallets. ## \[Aztec node][​](#aztec-node "Direct link to \[Aztec node]") Network config. The node now pulls default configuration from the public repository [AztecProtocol/networks](https://github.com/AztecProtocol/networks) after it applies the configuration it takes from the running environment and the configuration values baked into the source code. See associated [Design document](https://github.com/AztecProtocol/engineering-designs/blob/15415a62a7c8e901acb8e523625e91fc6f71dce4/docs/network-config/dd.md) ## \[Aztec.js][​](#aztecjs "Direct link to \[Aztec.js]") ### Removing Aztec cheatcodes[​](#removing-aztec-cheatcodes "Direct link to Removing Aztec cheatcodes") The Aztec cheatcodes class has been removed. Its functionality can be replaced by using the `getNotes(...)` function directly available on our `TestWallet`, along with the relevant functions available on the Aztec Node interface (note that the cheatcodes were generally just a thin wrapper around the Aztec Node interface). ### CLI Wallet commands dropped from `aztec` command[​](#cli-wallet-commands-dropped-from-aztec-command "Direct link to cli-wallet-commands-dropped-from-aztec-command") The following commands used to be exposed by both the `aztec` and the `aztec-wallet` commands: * import-test-accounts * create-account * deploy-account * deploy * send * simulate * profile * bridge-fee-juice * create-authwit * authorize-action * get-tx * cancel-tx * register-sender * register-contract These were dropped from `aztec` and now are exposed only by the `cli-wallet` command exposed by the `@aztec/cli-wallet` package. ### PXE commands dropped from `aztec` command[​](#pxe-commands-dropped-from-aztec-command "Direct link to pxe-commands-dropped-from-aztec-command") The following commands were dropped from the `aztec` command: * `add-contract`: use can be replaced with `register-contract` on our `cli-wallet` * `get-contract-data`: debug-only and not considered important enough to need a replacement * `get-accounts`: debug-only and can be replaced by loading aliases from `cli-wallet` * `get-account`: debug-only and can be replaced by loading aliases from `cli-wallet` * `get-pxe-info`: debug-only and not considered important enough to need a replacement ## \[Aztec.nr][​](#aztecnr "Direct link to \[Aztec.nr]") ### Replacing #\[private], #\[public], #\[utility] with #\[external(...)] macro[​](#replacing-private-public-utility-with-external-macro "Direct link to Replacing #\[private], #\[public], #\[utility] with #\[external(...)] macro") The original naming was not great in that it did not sufficiently communicate what the given macro did. We decided to rename `#[private]` as `#[external("private")]`, `#[public]` as `#[external("public")]`, and `#[utility]` as `#[external("utility")]` to better communicate that these functions are externally callable and to specify their execution context. In this sense, `external` now means the exact same thing as in Solidity, i.e. a function that can be called from other contracts, and that can only be invoked via a contract call (i.e. the `CALL` opcode in the EVM, and a kernel call/AVM `CALL` opcode in Aztec). You have to do the following changes in your contracts: Update import: ``` - use aztec::macros::functions::private; - use aztec::macros::functions::public; - use aztec::macros::functions::utility; + use aztec::macros::functions::external; ``` Update attributes of your functions: ``` - #[private] + #[external("private")] fn my_private_func() { ``` ``` - #[public] + #[external("public")] fn my_public_func() { ``` ``` - #[utility] + #[external("utility")] fn my_utility_func() { ``` ### Dropping remote mutable references to public context[​](#dropping-remote-mutable-references-to-public-context "Direct link to Dropping remote mutable references to public context") `PrivateContext` generally needs to be passed as a mutable reference to functions because it does actually hold state we're mutating. This is not the case for `PublicContext`, or `UtilityContext` - these are just marker objects that indicate the current execution mode and make available the correct subset of the API. For this reason we have dropped the mutable reference from the API. If you've passed the context as an argument to custom functions you will need to do the following migration (example from our token contract): ``` #[contract_library_method] fn _finalize_transfer_to_private( from_and_completer: AztecAddress, amount: u128, partial_note: PartialUintNote, - context: &mut PublicContext, - storage: Storage<&mut PublicContext>, + context: PublicContext, + storage: Storage, ) { ... } ``` ### Authwit Test Helper now takes `env`[​](#authwit-test-helper-now-takes-env "Direct link to authwit-test-helper-now-takes-env") The `add_private_authwit_from_call_interface` test helper available in `test::helpers::authwit` now takes a `TestEnvironment` parameter, mirroring `add_public_authwit_from_call_interface`. This adds some unfortunate verbosity, but there are bigger plans to improve authwit usage in Noir tests in the near future. ``` add_private_authwit_from_call_interface( + env, on_behalf_of, caller, call_interface, ); ``` ### Historical block renamed as anchor block[​](#historical-block-renamed-as-anchor-block "Direct link to Historical block renamed as anchor block") A historical block term has been used as a term that denotes the block against which a private part of a tx has been executed. This name is ambiguous and for this reason we've introduced "anchor block". This naming change resulted in quite a few changes and if you've access private context's or utility context's block header you will need to update your code: ``` - let header = context.get_block_header(); + let header = context.get_anchor_block_header(); ``` ### Removed ValueNote utils[​](#removed-valuenote-utils "Direct link to Removed ValueNote utils") The `value_note::utils` module has been removed because it was incorrect to have those in the value note package. For the increment function you can easily just insert the note: ``` - use value_note::utils; - utils::increment(storage.notes.at(owner), value, owner, sender); + let note = ValueNote::new(value, owner); + storage.notes.at(owner).insert(note).emit(&mut context, owner, MessageDelivery.CONSTRAINED_ONCHAIN); ``` ### PrivateMutable: replace / initialize\_or\_replace behaviour change[​](#privatemutable-replace--initialize_or_replace-behaviour-change "Direct link to PrivateMutable: replace / initialize_or_replace behaviour change") **Motivation:** Updating a note used to require reading it first (via `get_note`, which nullifies and recreates it) and then calling `replace` — effectively proving a note twice. Now, `replace` accepts a callback that transforms the current note directly, and `initialize_or_replace` simply uses this updated `replace` internally. This reduces circuit cost while maintaining exactly one current note. **Key points:** 1. `replace(self, new_note)` (old) → `replace(self, f)` (new), where `f` takes the current note and returns a transformed note. 2. `initialize_or_replace(self, note)` (old) → `initialize_or_replace(self, f)` (new), where `f` takes an `Option` with the current note, or `none` if uninitialized. 3. Previous note is automatically nullified before the new note is inserted. 4. `NoteEmission` still requires `.emit()` or `.discard()`. **Example Migration:** ``` - let current_note = storage.my_var.get_note(); - let new_note = f(current_note); - storage.my_var.replace(new_note); + storage.my_var.replace(|current_note| f(current_note)); ``` ``` - storage.my_var.initialize_or_replace(new_note); + storage.my_var.initialize_or_replace(|_| new_note); ``` This makes it easy and efficient to handle both initialization and current value mutation via `initialize_or_replace`, e.g. if implementing a note that simply counts how many times it has been read: ``` + storage.my_var.initialize_or_replace(|opt_current: Option| opt_current.unwrap_or(0 /* initial value */) + 1); ``` * The callback can be a closure (inline) or a named function. * Any previous assumptions that replace simply inserts a new\_note directly must be updated. ### Unified oracles into single get\_utility\_context oracle[​](#unified-oracles-into-single-get_utility_context-oracle "Direct link to Unified oracles into single get_utility_context oracle") The following oracles: 1. get\_contract\_address, 2. get\_block\_number, 3. get\_timestamp, 4. get\_chain\_id, 5. get\_version were replaced with a single `get_utility_context` oracle whose return value contains all the values returned from the removed oracles. If you have used one of these removed oracles before, update the import, e.g.: ``` - aztec::oracle::execution::get_chain_id; + aztec::oracle::execution::get_utility_context ``` and get the value out of the returned utility context: ``` - let chain_id = get_chain_id(); + let chain_id = get_utility_context().chain_id(); ``` ### Note emission API changes[​](#note-emission-api-changes "Direct link to Note emission API changes") The note emission API has been significantly reworked to provide clearer semantics around message delivery guarantees. The key changes are: 1. `encode_and_encrypt_note` has been removed in favor of calling `emit` directly with `MessageDelivery.CONSTRAINED_ONCHAIN` 2. `encode_and_encrypt_note_unconstrained` has been removed in favor of calling `emit` directly with `MessageDelivery.UNCONSTRAINED_ONCHAIN` 3. `encode_and_encrypt_note_and_emit_as_offchain_message` has been removed in favor of using `emit` with `MessageDelivery.UNCONSTRAINED_OFFCHAIN` 4. Note emission now takes a `delivery_mode` parameter with the following values: * `CONSTRAINED_ONCHAIN`: For onchain delivery with cryptographic guarantees that recipients can discover and decrypt messages. Uses constrained encryption but is slower to prove. Best for critical messages that contracts need to verify. * `UNCONSTRAINED_ONCHAIN`: For onchain delivery without encryption constraints. Faster proving but trusts the sender. Good when the sender is incentivized to perform encryption correctly (e.g. they are buying something and will only get it if the recipient sees the note). No guarantees that recipients will be able to find or decrypt messages. * `UNCONSTRAINED_OFFCHAIN`: For offchain delivery (e.g. cloud storage) without constraints. Lowest cost since no onchain storage needed. Requires custom infrastructure for delivery. No guarantees that messages will be delivered or that recipients will ever find them. 5. The `context` object no longer needs to be passed to these functions Example migration: First you need to update imports in your contract: ``` - aztec::messages::logs::note::encode_and_encrypt_note; - aztec::messages::logs::note::encode_and_encrypt_note_unconstrained; - aztec::messages::logs::note::encode_and_encrypt_note_and_emit_as_offchain_message; + aztec::messages::message_delivery::MessageDelivery; ``` Then update the emissions: ``` - storage.balances.at(from).sub(from, amount).emit(encode_and_encrypt_note(&mut context, from)); + storage.balances.at(from).sub(from, amount).emit(&mut context, from, MessageDelivery.CONSTRAINED_ONCHAIN); ``` ``` - storage.balances.at(from).add(from, change).emit(encode_and_encrypt_note_unconstrained(&mut context, from)); + storage.balances.at(from).add(from, change).emit(&mut context, from, MessageDelivery.UNCONSTRAINED_ONCHAIN); ``` ``` - storage.balances.at(owner).insert(note).emit(encode_and_encrypt_note_and_emit_as_offchain_message(&mut context, context.msg_sender()); + storage.balances.at(owner).insert(note).emit(&mut context, context.msg_sender(), MessageDelivery.UNCONSTRAINED_OFFCHAIN); ``` ## 2.0.2[​](#202 "Direct link to 2.0.2") ## \[Public functions][​](#public-functions "Direct link to \[Public functions]") The L2 gas cost of the different AVM opcodes have been updated to reflect more realistic proving costs. Developers should review the L2 gas costs of executing public functions and reevaluate any hardcoded L2 gas limits. ## \[Aztec Tools][​](#aztec-tools "Direct link to \[Aztec Tools]") ### Contract compilation now requires two steps[​](#contract-compilation-now-requires-two-steps "Direct link to Contract compilation now requires two steps") The `aztec-nargo` command is now a direct pass-through to vanilla nargo, without any special compilation flags or postprocessing. Contract compilation for Aztec now requires two explicit steps: 1. Compile your contracts with `aztec-nargo compile` 2. Run postprocessing with the new `aztec-postprocess-contract` command The postprocessing step includes: * Transpiling functions for the Aztec VM * Generating verification keys for private functions * Caching verification keys for faster subsequent compilations Update your build scripts accordingly: ``` - aztec-nargo compile + aztec-nargo compile + aztec-postprocess-contract ``` If you're using the `aztec-up` installer, the `aztec-postprocess-contract` command will be automatically installed alongside `aztec-nargo`. ## \[Aztec.js] Mandatory `from`[​](#aztecjs-mandatory-from "Direct link to aztecjs-mandatory-from") As we prepare for a bigger `Wallet` interface refactor and the upcoming `WalletSDK`, a new parameter has been added to contract interactions, which now should indicate *explicitly* the address of the entrypoint (usually the account contract) that will be used to authenticate the request. This will be checked in runtime against the current `this.wallet.getAddress()` value, to ensure consistent behavior while the rest of the API is reworked. ``` - await contract.methods.my_func(arg).send().wait(); + await contract.methods.my_func(arg).send({ from: account1Address }).wait(); ``` ## \[Aztec.nr][​](#aztecnr-1 "Direct link to \[Aztec.nr]") ### `emit_event_in_public_log` function renamed as `emit_event_in_public`[​](#emit_event_in_public_log-function-renamed-as-emit_event_in_public "Direct link to emit_event_in_public_log-function-renamed-as-emit_event_in_public") This change was done to make the naming consistent with the private counterpart (`emit_event_in_private`). ### Private event emission API changes[​](#private-event-emission-api-changes "Direct link to Private event emission API changes") The private event emission API has been significantly reworked to provide clearer semantics around message delivery guarantees. The key changes are: 1. `emit_event_in_private_log` has been renamed to `emit_event_in_private` and now takes a `delivery_mode` parameter instead of `constraints` 2. `emit_event_as_offchain_message` has been removed in favor of using `emit_event_in_private` with `MessageDelivery.UNCONSTRAINED_OFFCHAIN` 3. `PrivateLogContent` enum has been replaced with `MessageDelivery` enum with the following values: * `CONSTRAINED_ONCHAIN`: For onchain delivery with cryptographic guarantees that recipients can discover and decrypt messages. Uses constrained encryption but is slower to prove. Best for critical messages that contracts need to verify. * `UNCONSTRAINED_ONCHAIN`: For onchain delivery without encryption constraints. Faster proving but trusts the sender. Good when the sender is incentivized to perform encryption correctly (e.g. they are buying something and will only get it if the recipient sees the note). No guarantees that recipients will be able to find or decrypt messages. * `UNCONSTRAINED_OFFCHAIN`: For offchain delivery (e.g. cloud storage) without constraints. Lowest cost since no onchain storage needed. Requires custom infrastructure for delivery. No guarantees that messages will be delivered or that recipients will ever find them. ### Contract functions can no longer be `pub` or `pub(crate)`[​](#contract-functions-can-no-longer-be-pub-or-pubcrate "Direct link to contract-functions-can-no-longer-be-pub-or-pubcrate") With the latest changes to `TestEnvironment`, making contract functions have public visibility is no longer required given the new `call_public` and `simulate_utility` functions. To avoid accidental direct invocation, and to reduce confusion with the autogenerated interfaces, we're forbidding them being public. ``` - pub(crate) fn balance_of_private(account: AztecAddress) -> 128 { + fn balance_of_private(account: AztecAddress) -> 128 { ``` ### Notes require you to manually implement or derive Packable[​](#notes-require-you-to-manually-implement-or-derive-packable "Direct link to Notes require you to manually implement or derive Packable") We have decided to drop auto-derivation of `Packable` from the `#[note]` macro because we want to make the macros less magical. With this change you will be forced to either apply `#[derive(Packable)` on your notes: ``` +use aztec::protocol::traits::Packable; +#[derive(Packable)] #[note] pub struct UintNote { owner: AztecAddress, randomness: Field, value: u128, } ``` or to implement it manually yourself: ``` impl Packable for UintNote { let N: u32 = 3; fn pack(self) -> [Field; Self::N] { [self.owner.to_field(), randomness, value as Field] } fn unpack(fields: [Field; Self::N]) -> Self { let owner = AztecAddress::from_field(fields[0]); let randomness = fields[1]; let value = fields[2] as u128; UintNote { owner, randomness, value } } } ``` ### Tagging sender now managed via oracle functions[​](#tagging-sender-now-managed-via-oracle-functions "Direct link to Tagging sender now managed via oracle functions") Now, instead of manually needing to pass a tagging sender as an argument to log emission functions (e.g. `encode_and_encrypt_note`, `encode_and_encrypt_note_unconstrained`, `emit_event_in_private_log`, ...) we automatically load the sender via the `get_sender_for_tags()` oracle. This value is expected to be populated by account contracts that should call `set_sender_for_tags()` in their entry point functions. The changes you need to do in your contracts are quite straightforward. You simply need to drop the `sender` arg from the callsites of the log emission functions. E.g. note emission: ``` storage.balances.at(from).sub(from, amount).emit(encode_and_encrypt_note( &mut context, from, - tagging_sender, )); ``` E.g. private event emission: ``` emit_event_in_private_log( Transfer { from, to, amount }, &mut context, - tagging_sender, to, PrivateLogContent.NO_CONSTRAINTS, ); ``` This change affected arguments `prepare_private_balance_increase` and `mint_to_private` functions on the `Token` contract. Drop the `from` argument when calling these. Example in TypeScript test: ``` - await token.methods.mint_to_private(fundedWallet.getAddress(), alice, mintAmount).send().wait(); + await token.methods.mint_to_private(alice, mintAmount).send().wait(); ``` Example when ``` let token_out_partial_note = Token::at(token_out).prepare_private_balance_increase( sender, - tagging_sender ).call(&mut context); ``` ### SharedMutable -> DelayedPublicMutable[​](#sharedmutable---delayedpublicmutable "Direct link to SharedMutable -> DelayedPublicMutable") The `SharedMutable` state variable has been renamed to `DelayedPublicMutable`. It is a public mutable with a delay before state changes take effect. It can be read in private during the delay period. The name "shared" confuses developers who actually wish to work with so-called "shared private state". Also, we're working on a `DelayedPrivateMutable` which will have similar properties, except writes will be scheduled from private instead. With this new state variable in mind, the new name works nicely. ## \[TXE] - Testing Aztec Contracts using Noir[​](#txe---testing-aztec-contracts-using-noir "Direct link to \[TXE] - Testing Aztec Contracts using Noir") ### Full `TestEnvironment` API overhaul[​](#full-testenvironment-api-overhaul "Direct link to full-testenvironment-api-overhaul") As part of a broader effort to make Noir tests that leverage TXE easier to use and reason about, large parts of it were changed or adapted, resulting in the API now being quite different. No functionality was lost, so it should be possible to migrate any older Noir test to use the new API. #### Network State Manipulation[​](#network-state-manipulation "Direct link to Network State Manipulation") * `committed_timestamp` removed: this function did not work correctly * `private_at_timestamp`: this function was not really meaningful: private contexts are built from block numbers, not timestamps * `pending_block_number` was renamed to `next_block_number`. `pending_timestamp` was removed since it was confusing and not useful * `committed_block_number` was renamed to `last_block_number` * `advance_timestamp_to` and `advance_timestamp_by` were renamed to `set_next_block_timestamp` and `advance_next_block_timestamp_by` respectively * `advance_block_to` was renamed to `mine_block_at`, which takes a timestamp instead of a target block number * `advance_block_by` was renamed to `mine_block`, which now mines a single block #### Account Management[​](#account-management "Direct link to Account Management") * `create_account` was renamed to `create_light_account` * `create_account_contract` was renamed to `create_contract_account` #### Contract Deployment[​](#contract-deployment "Direct link to Contract Deployment") * `deploy_self` removed: merged into `deploy` * `deploy` now accepts both local and external contracts #### Contract Interactions[​](#contract-interactions "Direct link to Contract Interactions") The old way of calling contract functions is gone. Contract functions are now invoked via the `call_private`, `view_private`, `call_public`, `view_public` and `simulate_utility` `TestEnvironment` methods. These take a `CallInterface`, like their old counterparts, but now also take an explicit `from` parameter (for the `call` variants - this is left out of the `view` and `simulate` methods for simplicity). #### Raw Context Access[​](#raw-context-access "Direct link to Raw Context Access") The `private` and `public` methods are gone. Private, public and utility contexts can now be crated with the `private_context`, `public_context` and `utility_context` functions, all of which takes a callback function that is called with the corresponding context. This functions are expected to be defined in-line as lambdas, and contain the user-defined test logic. This helps delineate where contexts begin and end. Contexts automatically mine blocks on closing, when appropriate. #### Error-expecting Functions[​](#error-expecting-functions "Direct link to Error-expecting Functions") `assert_public_call_revert` and variants have been removed. Use `#[test(should_fail_with = "message")]` instead. #### Example Migration[​](#example-migration "Direct link to Example Migration") The following are two tests using the older version of `TestEnvironment`: ``` #[test] unconstrained fn initial_empty_value() { let mut env = TestEnvironment::new(); // Setup without account contracts. We are not using authwits here, so dummy accounts are enough let admin = env.create_account(1); let initializer_call_interface = Auth::interface().constructor(admin); let auth_contract = env.deploy_self("Auth").with_public_void_initializer(admin, initializer_call_interface); let auth_contract_address = auth_contract.to_address(); env.impersonate(admin); let authorized = Auth::at(auth_contract_address).get_authorized().view(&mut env.public()); assert_eq(authorized, AztecAddress::from_field(0)); } #[test] unconstrained fn non_admin_cannot_set_authorized() { let mut env = TestEnvironment::new(); // Setup without account contracts. We are not using authwits here, so dummy accounts are enough let admin = env.create_account(1); let other = env.create_account(2); let initializer_call_interface = Auth::interface().constructor(admin); let auth_contract = env.deploy_self("Auth").with_public_void_initializer(admin, initializer_call_interface); let auth_contract_address = auth_contract.to_address(); env.impersonate(other); env.assert_public_call_fails(Auth::at(auth_contract_address).set_authorized(to_authorize)); } ``` These now look like this: ``` #[test] unconstrained fn authorized_initially_unset() { let mut env = TestEnvironment::new(); let admin = env.create_light_account(); // Manual secret management gone let auth_contract_address = env.deploy("Auth").with_public_initializer(admin, Auth::interface().constructor(admin)); // deploy_self replaced let auth = Auth::at(auth_contract_address); assert_eq(env.view_public(auth.get_authorized()), AztecAddress::zero()); // .view_public() instead of .public() } #[test(should_fail_with = "caller is not admin")] unconstrained fn non_admin_cannot_set_unauthorized() { let mut env = TestEnvironment::new(); let admin = env.create_light_account(); let other = env.create_light_account(); let auth_contract_address = env.deploy("Auth").with_public_initializer(admin, Auth::interface().constructor(admin)); // deploy_self replaced let auth = Auth::at(auth_contract_address); env.call_public(other, auth.set_authorized(other)); // .call_public(), should_fail_with } ``` ## \[Aztec.js][​](#aztecjs-1 "Direct link to \[Aztec.js]") ### Cheatcodes[​](#cheatcodes "Direct link to Cheatcodes") Cheatcodes where moved out of the `@aztec/aztec.js` package to `@aztec/ethereum` and `@aztec/aztec` packages. While all of the cheatcodes can be imported from the `@aztec/aztec` package `EthCheatCodes` and `RollupCheatCodes` reside in `@aztec/ethereum` package and if you need only those importing only that package should result in a lighter build. ### Note exports dropped from artifact[​](#note-exports-dropped-from-artifact "Direct link to Note exports dropped from artifact") Notes are no longer exported in the contract artifact. Exporting notes was technical debt from when we needed to interpret notes in TypeScript. The following code will no longer work since `notes` is no longer available on the artifact: ``` const valueNoteTypeId = StatefulTestContractArtifact.notes['ValueNote'].id; ``` ## \[core protocol, Aztec.nr, Aztec.js] Max block number property changed to be seconds based[​](#core-protocol-aztecnr-aztecjs-max-block-number-property-changed-to-be-seconds-based "Direct link to \[core protocol, Aztec.nr, Aztec.js] Max block number property changed to be seconds based") ### `max_block_number` -> `include_by_timestamp`[​](#max_block_number---include_by_timestamp "Direct link to max_block_number---include_by_timestamp") The transaction expiration mechanism has been updated to use seconds rather than number of blocks. As part of this change, the transaction property `max_block_number` has been renamed to `include_by_timestamp`. This change significantly impacts the `SharedMutable` state variable in `Aztec.nr`, which now operates on a seconds instead of number of blocks. If your contract uses `SharedMutable`, you'll need to: 1. Update the `INITIAL_DELAY` numeric generic to use seconds instead of blocks 2. Modify any related logic to account for timestamp-based timing 3. Note that timestamps use `u64` values while block numbers use `u32` ### Removed `prelude`, so your `dep::aztec::prelude::...` imports will need to be amended.[​](#removed-prelude-so-your-depaztecprelude-imports-will-need-to-be-amended "Direct link to removed-prelude-so-your-depaztecprelude-imports-will-need-to-be-amended") Instead of importing common types from `dep::aztec::prelude...`, you'll now need to import them from their lower-level locations. The Noir Language Server vscode extension is now capable of autocompleting imports: just type some of the import and press 'tab' when it pops up with the correct item, and the import will be inserted at the top of the file. As a quick reference, here are the paths to the types that were previously in the `prelude`. So, for example, if you were previously using `dep::aztec::prelude::AztecAddress`, you'll need to replace it with `dep::aztec::protocol::address::AztecAddress`. Apologies for any pain this brings. The reasoning is that these types were somewhat arbitrary, and it was unclear which types were worthy enough to be included here. ``` use dep::aztec::{ context::{PrivateCallInterface, PrivateContext, PublicContext, UtilityContext, ReturnsHash}, note::{ note_getter_options::NoteGetterOptions, note_interface::{NoteHash, NoteType}, note_viewer_options::NoteViewerOptions, hinted_note::HintedNote, }, state_vars::{ map::Map, private_immutable::PrivateImmutable, private_mutable::PrivateMutable, private_set::PrivateSet, public_immutable::PublicImmutable, public_mutable::PublicMutable, shared_mutable::SharedMutable, }, }; use dep::aztec::protocol::{ abis::function_selector::FunctionSelector, address::{AztecAddress, EthAddress}, point::Point, traits::{Deserialize, Serialize}, }; ``` ### `include_by_timestamp` is now mandatory[​](#include_by_timestamp-is-now-mandatory "Direct link to include_by_timestamp-is-now-mandatory") Each transaction must now include a valid `include_by_timestamp` that satisfies the following conditions: * It must be greater than the historical block’s timestamp. * The duration between the `include_by_timestamp` and the historical block’s timestamp must not exceed the maximum allowed (currently 24 hours). * It must be greater than or equal to the timestamp of the block in which the transaction is included. The protocol circuits compute the `include_by_timestamp` for contract updates during each private function iteration. If a contract does not explicitly specify a value, the default will be the maximum allowed duration. This ensures that `include_by_timestamp` is never left unset. No client-side changes are required. However, please note that transactions now have a maximum lifespan of 24 hours and will be removed from the transaction pool once expired. ## 0.88.0[​](#0880 "Direct link to 0.88.0") ## \[Aztec.nr] Deprecation of the `authwit` library[​](#aztecnr-deprecation-of-the-authwit-library "Direct link to aztecnr-deprecation-of-the-authwit-library") It is now included in `aztec-nr`, so imports must be updated: ``` -dep::authwit::... +dep::aztec::authwit... ``` and stale dependencies removed from `Nargo.toml` ``` -authwit = { path = "../../../../aztec-nr/authwit" } ``` ## 0.87.0[​](#0870 "Direct link to 0.87.0") ## \[Aztec.js/TS libraries][​](#aztecjsts-libraries "Direct link to \[Aztec.js/TS libraries]") We've bumped our minimum supported node version to v20, as v18 is now EOL. As a consequence, the deprecated type assertion syntax has been replaced with modern import attributes whenever contract artifact JSONs are loaded: ``` -import ArtifactJson from '../artifacts/contract-Contract.json' assert { type: 'json' }; +import ArtifactJson from '../artifacts/contract-Contract.json' with { type: 'json' }; ``` ## \[Aztec.js/PXE] `simulateUtility` return type[​](#aztecjspxe-simulateutility-return-type "Direct link to aztecjspxe-simulateutility-return-type") `pxe.simulateUtility()` now returns a complex object (much like `.simulateTx()`) so extra information can be provided such as simulation timings. This information can be accessed setting the `includeMetadata` flag in `SimulateMethodOptions` to `true`, but not providing it (which is the default) will NOT change the behavior of the current code. ``` -const result = await pxe.simulateUtility(...); +const { meta, result } = await pxe.simulateUtility(...); const result = await Contract.methods.myFunction(...).simulate(); const { result, meta} = await Contract.methods.myFunction(...).simulate({ includeMetadata: true }); ``` ## \[Aztec.js] Removed mandatory simulation before proving in contract interfaces[​](#aztecjs-removed-mandatory-simulation-before-proving-in-contract-interfaces "Direct link to \[Aztec.js] Removed mandatory simulation before proving in contract interfaces") Previously, our autogenerated contract classes would perform a simulation when calling `.prove` or `.send` on them. This could potentially catch errors earlier, but took away control from the app/wallets on how to handle network interactions. Now this process has to be triggered manually, which means just proving an interaction (or proving and sending it to the network in one go via `.send`) is much faster. *WARNING:* This means users can incurr in network fees if a transaction that would otherwise be invalid is sent without sanity checks. To ensure this, it is recommended to do: ``` +await Contract.method.simulate(); await Contract.method.send().wait(); ``` ## 0.86.0[​](#0860 "Direct link to 0.86.0") ### \[PXE] Removed PXE\_L2\_STARTING\_BLOCK environment variable[​](#pxe-removed-pxe_l2_starting_block-environment-variable "Direct link to \[PXE] Removed PXE_L2_STARTING_BLOCK environment variable") PXE now fast-syncs by skipping finalized blocks and never downloads all blocks, so there is no longer a need to specify a starting block. ### \[Aztec.nr] Logs and messages renaming[​](#aztecnr-logs-and-messages-renaming "Direct link to \[Aztec.nr] Logs and messages renaming") The following renamings have taken place: * `encrypted_logs` to `messages`: this module now handles much more than just encrypted logs (including unconstrained message delivery, message encoding, etc.) * `log_assembly_strategies` to `logs` * `discovery` moved to `messages`: given that what is discovered are messages * `default_aes128` removed Most contracts barely used these modules directly. The frequently used `encode_and_encrypt` function imports remain unchanged: ``` use dep::aztec::messages::logs::note::encode_and_encrypt_note; ``` ### \[noir-contracts] Reference Noir contracts directory structure change[​](#noir-contracts-reference-noir-contracts-directory-structure-change "Direct link to \[noir-contracts] Reference Noir contracts directory structure change") `noir-projects/noir-contracts/contracts` directory became too cluttered so we grouped contracts into `account`, `app`, `docs`, `fees`, `libs`, `protocol` and `test` dirs. If you import contract from the directory make sure to update the paths accordingly. E.g. for a token contract: ``` #[dependencies] -token = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v0.83.0", directory = "noir-projects/noir-contracts/contracts/src/token_contract" } +token = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v0.83.0", directory = "noir-projects/noir-contracts/contracts/app/src/token_contract" } ``` ### \[Aztec.nr] #\[utility] contract functions[​](#aztecnr-utility-contract-functions "Direct link to \[Aztec.nr] #\[utility] contract functions") Aztec contracts have three kinds of functions: `#[private]`, `#[public]` and what was sometimes called 'top-level unconstrained': an unmarked unconstrained function in the contract module. These are now called `[#utility]` functions, and must be explicitly marked as such: ``` + #[utility] unconstrained fn balance_of_private(owner: AztecAddress) -> u128 { storage.balances.at(owner).balance_of() } ``` Utility functions are standalone unconstrained functions that cannot be called from private or public functions: they are meant to be called by *applications* to perform auxiliary tasks: query contract state (e.g. a token balance), process messages received offchain, etc. All functions in a `contract` block must now be marked as one of either `#[private]`, `#[public]`, `#[utility]`, `#[contract_library_method]`, or `#[test]`. Additionally, the `UnconstrainedContext` type has been renamed to `UtilityContext`. This led us to rename the `unkonstrained` method on `TestEnvironment` to `utility`, so any tests using it also need updating: ``` - SharedMutable::new(env.unkonstrained(), storage_slot) + SharedMutable::new(env.utility(), storage_slot) ``` ### \[AuthRegistry] function name change[​](#authregistry-function-name-change "Direct link to \[AuthRegistry] function name change") As part of the broader transition from "top-level unconstrained" to "utility" name (detailed in the note above), the `unconstrained_is_consumable` function in AuthRegistry has been renamed to `utility_is_consumable`. The function's signature and behavior remain unchanged - only the name has been updated to align with the new convention. If you're currently using this function, a simple rename in your code will suffice. ## 0.83.0[​](#0830 "Direct link to 0.83.0") ### \[aztec.js] AztecNode.getPrivateEvents API change[​](#aztecjs-aztecnodegetprivateevents-api-change "Direct link to \[aztec.js] AztecNode.getPrivateEvents API change") The `getPrivateEvents` method signature has changed to require an address of a contract that emitted the event and use recipient addresses instead of viewing public keys: ``` - const events = await wallet.getPrivateEvents(TokenContract.events.Transfer, 1, 1, [recipient.getCompleteAddress().publicKeys.masterIncomingViewingPublicKey()]); + const events = await wallet.getPrivateEvents(token.address, TokenContract.events.Transfer, 1, 1, [recipient.getAddress()]); ``` ### \[portal contracts] Versions and Non-following message boxes[​](#portal-contracts-versions-and-non-following-message-boxes "Direct link to \[portal contracts] Versions and Non-following message boxes") The version number is no longer hard-coded to be `1` across all deployments (it not depends on where it is deployed to and with what genesis and logic). This means that if your portal were hard-coding `1` it will now fail when inserting into the `inbox` or consuming from the `outbox` because of a version mismatch. Instead you can get the real version (which don't change for a deployment) by reading the `VERSION` on inbox and outbox, or using `getVersion()` on the rollup. New Deployments of the protocol do not preserve former state/across each other. This means that after a new deployment, any "portal" following the registry would try to send messages into this empty rollup to non-existent contracts. To solve, the portal should be linked to a specific deployment, e.g., a specific inbox. This can be done by storing the inbox/outbox/version at the time of deployment or initialize and not update them. Both of these issues were in the token portal and the uniswap portal, so if you used them as a template it is very likely that you will also have it. ## 0.82.0[​](#0820 "Direct link to 0.82.0") ### \[aztec.js] AztecNode.findLeavesIndexes returns indexes with block metadata[​](#aztecjs-aztecnodefindleavesindexes-returns-indexes-with-block-metadata "Direct link to \[aztec.js] AztecNode.findLeavesIndexes returns indexes with block metadata") It's common that we need block metadata of a block in which leaves were inserted when querying indexes of these tree leaves. For this reason we now return that information along with the indexes. This allows us to reduce the number of individual AztecNode queries. Along with this change, `findNullifiersIndexesWithBlock` and `findBlockNumbersForIndexes` functions were removed as all their uses can now be replaced with the newly modified `findLeavesIndexes` function. ### \[aztec.js] AztecNode.getPublicDataTreeWitness renamed as AztecNode.getPublicDataWitness[​](#aztecjs-aztecnodegetpublicdatatreewitness-renamed-as-aztecnodegetpublicdatawitness "Direct link to \[aztec.js] AztecNode.getPublicDataTreeWitness renamed as AztecNode.getPublicDataWitness") This change was done to have consistent naming across codebase. ### \[aztec.js] Wallet interface and Authwit management[​](#aztecjs-wallet-interface-and-authwit-management "Direct link to \[aztec.js] Wallet interface and Authwit management") The `Wallet` interface in `aztec.js` is undergoing transformations, trying to be friendlier to wallet builders and reducing the surface of its API. This means `Wallet` no longer extends `PXE`, and instead just implements a subset of the methods of the former. This is NOT going to be its final form, but paves the way towards better interfaces and starts to clarify what the responsibilities of the wallet are: ``` /** * The wallet interface. */ export type Wallet = AccountInterface & Pick< PXE, // Simulation | "simulateTx" | "simulateUnconstrained" | "profileTx" // Sending | "sendTx" // Contract management (will probably be collapsed in the future to avoid instance and class versions) | "getContractClassMetadata" | "getContractMetadata" | "registerContract" | "registerContractClass" // Likely to be removed | "proveTx" // Will probably be collapsed | "getNodeInfo" | "getPXEInfo" // Fee info | "getCurrentMinFees" // Still undecided, kept for the time being | "updateContract" // Sender management | "registerSender" | "getSenders" | "removeSender" // Tx status | "getTxReceipt" // Events. Kept since events are going to be reworked and changes will come when that's done | "getPrivateEvents" | "getPublicEvents" > & { createAuthWit(intent: IntentInnerHash | IntentAction): Promise; }; ``` As a side effect, a few debug only features have been removed ``` // Obtain tx effects const { txHash, debugInfo } = await contract.methods .set_constant(value) .send() -- .wait({ interval: 0.1, debug: true }); ++ .wait({ interval: 0.1 }) -- // check that 1 note hash was created -- expect(debugInfo!.noteHashes.length).toBe(1); ++ const txEffect = await aztecNode.getTxEffect(txHash); ++ const noteHashes = txEffect?.data.noteHashes; ++ // check that 1 note hash was created ++ expect(noteHashes?.length).toBe(1); // Wait for a tx to be proven -- tx.wait({ timeout: 300, interval: 10, proven: true, provenTimeout: 3000 }))); ++ const receipt = await tx.wait({ timeout: 300, interval: 10 }); ++ await waitForProven(aztecNode, receipt, { provenTimeout: 3000 }); ``` Authwit management has changed, and PXE no longer stores them. This is unnecessary because now they can be externally provided to simulations and transactions, making sure no stale authorizations are kept inside PXE's db. ``` const witness = await wallet.createAuthWit({ caller, action }); --await callerWallet.addAuthWitness(witness); --await action.send().wait(); ++await action.send({ authWitnesses: [witness] }).wait(); ``` Another side effect of this is that the interface of the `lookupValidity` method has changed, and now the authwitness has to be provided: ``` const witness = await wallet.createAuthWit({ caller, action }); --await callerWallet.addAuthWitness(witness); --await wallet.lookupValidity(wallet.getAddress(), { caller, action }); ++await wallet.lookupValidity(wallet.getAddress(), { caller, action }, witness); ``` ## 0.80.0[​](#0800 "Direct link to 0.80.0") ### \[PXE] Concurrent contract function simulation disabled[​](#pxe-concurrent-contract-function-simulation-disabled "Direct link to \[PXE] Concurrent contract function simulation disabled") PXE is no longer be able to execute contract functions concurrently (e.g. by collecting calls to `simulateTx` and then using `await Promise.all`). They will instead be put in a job queue and executed sequentially in order of arrival. ## 0.79.0[​](#0790 "Direct link to 0.79.0") ### \[aztec.js] Changes to `BatchCall` and `BaseContractInteraction`[​](#aztecjs-changes-to-batchcall-and-basecontractinteraction "Direct link to aztecjs-changes-to-batchcall-and-basecontractinteraction") The constructor arguments of `BatchCall` have been updated to improve usability. Previously, it accepted an array of `FunctionCall`, requiring users to manually set additional data such as `authwit` and `capsules`. Now, `BatchCall` takes an array of `BaseContractInteraction`, which encapsulates all necessary information. ``` class BatchCall extends BaseContractInteraction { - constructor(wallet: Wallet, protected calls: FunctionCall[]) { + constructor(wallet: Wallet, protected calls: BaseContractInteraction[]) { ... } ``` The `request` method of `BaseContractInteraction` now returns `ExecutionPayload`. This object includes all the necessary data to execute one or more functions. `BatchCall` invokes this method on all interactions to aggregate the required information. It is also used internally in simulations for fee estimation. Declaring a `BatchCall`: ``` new BatchCall(wallet, [ - await token.methods.transfer(alice, amount).request(), - await token.methods.transfer_to_private(bob, amount).request(), + token.methods.transfer(alice, amount), + token.methods.transfer_to_private(bob, amount), ]) ``` ## 0.77.0[​](#0770 "Direct link to 0.77.0") ### \[aztec-nr] `TestEnvironment::block_number()` refactored[​](#aztec-nr-testenvironmentblock_number-refactored "Direct link to aztec-nr-testenvironmentblock_number-refactored") The `block_number` function from `TestEnvironment` has been expanded upon with two extra functions, the first being `pending_block_number`, and the second being `committed_block_number`. `pending_block_number` now returns what `block_number` does. In other words, it returns the block number of the block we are currently building. `committed_block_number` returns the block number of the last committed block, i.e. the block number that gets used to execute the private part of transactions when your PXE is successfully synced to the tip of the chain. ``` + `TestEnvironment::pending_block_number()` + `TestEnvironment::committed_block_number()` ``` ### \[aztec-nr] `compute_nullifier_without_context` renamed[​](#aztec-nr-compute_nullifier_without_context-renamed "Direct link to aztec-nr-compute_nullifier_without_context-renamed") The `compute_nullifier_without_context` function from `NoteHash` (ex `NoteInterface`) is now called `compute_nullifier_unconstrained`, and instead of taking storage slot, contract address and nonce it takes a note hash for nullification (same as `compute_note_hash`). This makes writing this function simpler: ``` - unconstrained fn compute_nullifier_without_context(self, storage_slot: Field, contract_address: AztecAddress, nonce: Field) -> Field { - let note_hash_for_nullify = ...; + unconstrained fn compute_nullifier_unconstrained(self, note_hash_for_nullify: Field) -> Field { ... } ``` ### `U128` type replaced with native `u128`[​](#u128-type-replaced-with-native-u128 "Direct link to u128-type-replaced-with-native-u128") The `U128` type has been replaced with the native `u128` type. This means that you can no longer use the `U128` type in your code. Instead, you should use the `u128` type. Doing the changes is as straightforward as: ``` #[public] #[view] - fn balance_of_public(owner: AztecAddress) -> U128 { + fn balance_of_public(owner: AztecAddress) -> u128 { storage.public_balances.at(owner).read() } ``` `UintNote` has also been updated to use the native `u128` type. ### \[aztec-nr] Removed `compute_note_hash_and_optionally_a_nullifier`[​](#aztec-nr-removed-compute_note_hash_and_optionally_a_nullifier "Direct link to aztec-nr-removed-compute_note_hash_and_optionally_a_nullifier") This function is no longer mandatory for contracts, and the `#[aztec]` macro no longer injects it. ### \[PXE] Removed `addNote` and `addNullifiedNote`[​](#pxe-removed-addnote-and-addnullifiednote "Direct link to pxe-removed-addnote-and-addnullifiednote") These functions have been removed from PXE and the base `Wallet` interface. If you need to deliver a note manually because its creation is not being broadcast in an encrypted log, then create an unconstrained contract function to process it and simulate execution of it. The `aztec::discovery::private_logs::do_process_log` function can be used to perform note discovery and add to it to PXE. See an example of how to handle a `TransparentNote`: ``` unconstrained fn deliver_transparent_note( contract_address: AztecAddress, amount: Field, secret_hash: Field, tx_hash: Field, unique_note_hashes_in_tx: BoundedVec, first_nullifier_in_tx: Field, recipient: AztecAddress, ) { // do_process_log expects a standard aztec-nr encoded note, which has the following shape: // [ storage_slot, note_type_id, ...packed_note ] let note = TransparentNote::new(amount, secret_hash); let log_plaintext = BoundedVec::from_array(array_concat( [ MyContract::storage_layout().my_state_variable.slot, TransparentNote::get_note_type_id(), ], note.pack(), )); do_process_log( contract_address, log_plaintext, tx_hash, unique_note_hashes_in_tx, first_nullifier_in_tx, recipient, _compute_note_hash_and_nullifier, ); } ``` The note is then processed by calling this function: ``` const txEffects = await wallet.getTxEffect(txHash); await contract.methods .deliver_transparent_note( contract.address, new Fr(amount), secretHash, txHash.hash, toBoundedVec(txEffects!.data.noteHashes, MAX_NOTE_HASHES_PER_TX), txEffects!.data.nullifiers[0], wallet.getAddress(), ) .simulate(); ``` ### Fee is mandatory[​](#fee-is-mandatory "Direct link to Fee is mandatory") All transactions must now pay fees. Previously, the default payment method was `NoFeePaymentMethod`; It has been changed to `FeeJuicePaymentMethod`, with the wallet owner as the fee payer. For example, the following code will still work: ``` await TokenContract.at(address, wallet).methods.transfer(recipient, 100n).send().wait(); ``` However, the wallet owner must have enough fee juice to cover the transaction fee. Otherwise, the transaction will be rejected. The 3 test accounts deployed in the sandbox are pre-funded with 10 ^ 22 fee juice, allowing them to send transactions right away. In addition to the native fee juice, users can pay the transaction fees using tokens that have a corresponding FPC contract. The sandbox now includes `BananaCoin` and `BananaFPC`. Users can use a funded test account to mint banana coin for a new account. The new account can then start sending transactions and pay fees with banana coin. ``` import { getDeployedTestAccountsWallets } from "@aztec/accounts/testing"; import { getDeployedBananaCoinAddress, getDeployedBananaFPCAddress, } from "@aztec/aztec"; // Fetch the funded test accounts. const [fundedWallet] = await getDeployedTestAccountsWallets(pxe); // Create a new account. const secret = Fr.random(); const signingKey = GrumpkinScalar.random(); const alice = await getSchnorrAccount(pxe, secret, signingKey); const aliceWallet = await alice.getWallet(); const aliceAddress = alice.getAddress(); // Deploy the new account using the pre-funded test account. await alice.deploy({ deployWallet: fundedWallet }).wait(); // Mint banana coin for the new account. const bananaCoinAddress = await getDeployedBananaCoinAddress(pxe); const bananaCoin = await TokenContract.at(bananaCoinAddress, fundedWallet); const mintAmount = 10n ** 20n; await bananaCoin.methods .mint_to_private(fundedWallet.getAddress(), aliceAddress, mintAmount) .send() .wait(); // Use the new account to send a tx and pay with banana coin. const transferAmount = 100n; const bananaFPCAddress = await getDeployedBananaFPCAddress(pxe); const paymentMethod = new PrivateFeePaymentMethod( bananaFPCAddress, aliceWallet, ); const receipt = await bananaCoin .withWallet(aliceWallet) .methods.transfer(recipient, transferAmount) .send({ fee: { paymentMethod } }) .wait(); const transactionFee = receipt.transactionFee!; // Check the new account's balance. const aliceBalance = await bananaCoin.methods .balance_of_private(aliceAddress) .simulate(); expect(aliceBalance).toEqual(mintAmount - transferAmount - transactionFee); ``` ### The tree of protocol contract addresses is now an indexed tree[​](#the-tree-of-protocol-contract-addresses-is-now-an-indexed-tree "Direct link to The tree of protocol contract addresses is now an indexed tree") This is to allow for non-membership proofs for non-protocol contract addresses. As before, the canonical protocol contract addresses point to the index of the leaf of the 'real' computed protocol address. For example, the canonical `DEPLOYER_CONTRACT_ADDRESS` is a constant `= 2`. This is used in the kernels as the `contract_address`. We calculate the `computed_address` (currently `0x1665c5fbc1e58ba19c82f64c0402d29e8bbf94b1fde1a056280d081c15b0dac1`) and check that this value exists in the indexed tree at index `2`. This check already existed and ensures that the call cannot do 'special' protocol contract things unless it is a real protocol contract. The new check an indexed tree allows is non-membership of addresses of non protocol contracts. This ensures that if a call is from a protocol contract, it must use the canonical address. For example, before this check a call could be from the deployer contract and use `0x1665c5fbc1e58ba19c82f64c0402d29e8bbf94b1fde1a056280d081c15b0dac1` as the `contract_address`, but be incorrectly treated as a 'normal' call. ``` - let computed_protocol_contract_tree_root = if is_protocol_contract { - 0 - } else { - root_from_sibling_path( - computed_address.to_field(), - protocol_contract_index, - private_call_data.protocol_contract_sibling_path, - ) - }; + conditionally_assert_check_membership( + computed_address.to_field(), + is_protocol_contract, + private_call_data.protocol_contract_leaf, + private_call_data.protocol_contract_membership_witness, + protocol_contract_tree_root, + ); ``` ### \[Aztec.nr] Changes to note interfaces and note macros[​](#aztecnr-changes-to-note-interfaces-and-note-macros "Direct link to \[Aztec.nr] Changes to note interfaces and note macros") In this releases we decided to do a large refactor of notes which resulted in the following changes: 1. We removed `NoteHeader` and we've introduced a `HintedNote` struct that contains a note and the information originally stored in the `NoteHeader`. 2. We removed the `pack_content` and `unpack_content` functions from the `NoteInterface`and made notes implement the standard `Packable` trait. 3. We renamed the `NullifiableNote` trait to `NoteHash` and we've moved the `compute_note_hash` function to this trait from the `NoteInterface` trait. 4. We renamed `NoteInterface` trait as `NoteType` and `get_note_type_id` function as `get_id`. 5. The `#[note]` and `#[partial_note]` macros now generate both the `NoteType` and `NoteHash` traits. 6. `#[custom_note_interface]` macro has been renamed to `#[custom_note]` and it now implements the `NoteInterface` trait. This led us to do the following changes to the interfaces: ``` -pub trait NoteInterface { +pub trait NoteType { fn get_id() -> Field; - fn pack_content(self) -> [Field; N]; - fn unpack_content(fields: [Field; N]) -> Self; - fn get_header(self) -> NoteHeader; - fn set_header(&mut self, header: NoteHeader) -> (); - fn compute_note_hash(self) -> Field; } pub trait NoteHash { + fn compute_note_hash(self, storage_slot: Field) -> Field; fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field; - unconstrained fn compute_nullifier_without_context(self) -> Field; + unconstrained fn fn compute_nullifier_without_context(self, storage_slot: Field, contract_address: AztecAddress, note_nonce: Field) -> Field; } ``` If you are using `#[note]` or `#[partial_note(...)]` macros you will need to delete the implementations of the `NullifiableNote` (now `NoteHash`) trait as it now gets auto-generated. Your note will also need to have an `owner` (a note struct field called owner) as its used in the auto-generated nullifier functions. If you need a custom implementation of the `NoteHash` interface use the `#[custom_note]` macro. If you used `#[note_custom_interface]` macro before you will need to update your notes by using the `#[custom_note]` macro and implementing the `compute_note_hash` function. If you have no need for a custom implementation of the `compute_note_hash` function copy the default one: ``` fn compute_note_hash(self, storage_slot: Field) -> Field { let inputs = aztec::protocol::utils::arrays::array_concat(self.pack(), [storage_slot]); aztec::protocol::hash::poseidon2_hash_with_separator(inputs, aztec::protocol::constants::DOM_SEP__NOTE_HASH) } ``` If you need to keep the custom implementation of the packing functionality, manually implement the `Packable` trait: ``` + use dep::aztec::protocol::traits::Packable; +impl Packable for YourNote { + fn pack(self) -> [Field; N] { + ... + } + + fn unpack(fields: [Field; N]) -> Self { + ... + } +} ``` If you don't provide a custom implementation of the `Packable` trait, a default one will be generated. ### \[Aztec.nr] Changes to state variables[​](#aztecnr-changes-to-state-variables "Direct link to \[Aztec.nr] Changes to state variables") Since we've removed `NoteHeader` from notes we no longer need to modify the header in the notes when working with state variables. This means that we no longer need to be passing a mutable note reference which led to the following changes in the API. #### PrivateImmutable[​](#privateimmutable "Direct link to PrivateImmutable") For `PrivateImmutable` the changes are fairly straightforward. Instead of passing in a mutable reference `&mut note` just pass in `note`. ``` impl PrivateImmutable { - pub fn initialize(self, note: &mut Note) -> NoteEmission + pub fn initialize(self, note: Note) -> NoteEmission where Note: NoteInterface + NullifiableNote, { ... } } ``` #### PrivateSet[​](#privateset "Direct link to PrivateSet") For `PrivateSet` the changes are a bit more involved than the changes in `PrivateImmutable`. Instead of passing in a mutable reference `&mut note` to the `insert` function just pass in `note`. The `remove` function now takes in a `HintedNote` instead of a `Note` and the `get_notes` function now returns a vector `HintedNote`s instead of a vector `Note`s. Note getters now generally return `HintedNote`s so getting a hold of the `HintedNote` for removal should be straightforward. ``` impl PrivateSet where Note: NoteInterface + NullifiableNote + Eq, { - pub fn insert(self, note: &mut Note) -> NoteEmission { + pub fn insert(self, note: Note) -> NoteEmission { ... } - pub fn remove(self, note: Note) { + pub fn remove(self, hinted_note: HintedNote) { ... } pub fn get_notes( self, options: NoteGetterOptions, - ) -> BoundedVec { + ) -> BoundedVec, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL> { ... } } - impl PrivateSet - where - Note: NoteInterface + NullifiableNote, - { - pub fn insert_from_public(self, note: &mut Note) { - create_note_hash_from_public(self.context, self.storage_slot, note); - } - } ``` #### PrivateMutable[​](#privatemutable "Direct link to PrivateMutable") For `PrivateMutable` the changes are similar to the changes in `PrivateImmutable`. ``` impl PrivateMutable where Note: NoteInterface + NullifiableNote, { - pub fn initialize(self, note: &mut Note) -> NoteEmission { + pub fn initialize(self, note: Note) -> NoteEmission { ... } - pub fn replace(self, new_note: &mut Note) -> NoteEmission { + pub fn replace(self, new_note: Note) -> NoteEmission { ... } - pub fn initialize_or_replace(self, note: &mut Note) -> NoteEmission { + pub fn initialize_or_replace(self, note: Note) -> NoteEmission { ... } } ``` ## 0.75.0[​](#0750 "Direct link to 0.75.0") ### Changes to `TokenBridge` interface[​](#changes-to-tokenbridge-interface "Direct link to changes-to-tokenbridge-interface") `get_token` and `get_portal_address` functions got merged into a single `get_config` function that returns a struct containing both the token and portal addresses. ### \[Aztec.nr] `SharedMutable` can store size of packed length larger than 1[​](#aztecnr-sharedmutable-can-store-size-of-packed-length-larger-than-1 "Direct link to aztecnr-sharedmutable-can-store-size-of-packed-length-larger-than-1") `SharedMutable` has been modified such that now it can store type `T` which packs to a length larger than 1. This is a breaking change because now `SharedMutable` requires `T` to implement `Packable` trait instead of `ToField` and `FromField` traits. To implement the `Packable` trait for your type you can use the derive macro: ``` + use std::meta::derive; + #[derive(Packable)] pub struct YourType { ... } ``` ### \[Aztec.nr] Introduction of `WithHash`[​](#aztecnr-introduction-of-withhasht "Direct link to aztecnr-introduction-of-withhasht") `WithHash` is a struct that allows for efficient reading of value `T` from public storage in private. This is achieved by storing the value with its hash, then obtaining the values via an oracle and verifying them against the hash. This results in in a fewer tree inclusion proofs for values `T` that are packed into more than a single field. `WithHash` is leveraged by state variables like `PublicImmutable`. This is a breaking change because now we require values stored in `PublicImmutable` and `SharedMutable` to implement the `Eq` trait. To implement the `Eq` trait you can use the `#[derive(Eq)]` macro: ``` + use std::meta::derive; + #[derive(Eq)] pub struct YourType { ... } ``` ## 0.73.0[​](#0730 "Direct link to 0.73.0") ### \[Token, FPC] Moving fee-related complexity from the Token to the FPC[​](#token-fpc-moving-fee-related-complexity-from-the-token-to-the-fpc "Direct link to \[Token, FPC] Moving fee-related complexity from the Token to the FPC") There was a complexity leak of fee-related functionality in the token contract. We've came up with a way how to achieve the same objective with the general functionality of the Token contract. This lead to the removal of `setup_refund` and `complete_refund` functions from the Token contract and addition of `complete_refund` function to the FPC. ### \[Aztec.nr] Improved storage slot allocation[​](#aztecnr-improved-storage-slot-allocation "Direct link to \[Aztec.nr] Improved storage slot allocation") State variables are no longer assumed to be generic over a type that implements the `Serialize` trait: instead, they must implement the `Storage` trait with an `N` value equal to the number of slots they need to reserve. For the vast majority of state variables, this simply means binding the serialization length to this trait: ``` + impl Storage for MyStateVar where T: Serialize { }; ``` ### \[Aztec.nr] Introduction of `Packable` trait[​](#aztecnr-introduction-of-packable-trait "Direct link to aztecnr-introduction-of-packable-trait") We have introduced a `Packable` trait that allows types to be serialized and deserialized with a focus on minimizing the size of the resulting Field array. This is in contrast to the `Serialize` and `Deserialize` traits, which follows Noir's intrinsic serialization format. This is a breaking change because we now require `Packable` trait implementation for any type that is to be stored in contract storage. Example implementation of Packable trait for `U128` type from `noir::std`: ``` use crate::traits::{Packable, ToField}; let U128_PACKED_LEN: u32 = 1; impl Packable for U128 { fn pack(self) -> [Field; U128_PACKED_LEN] { [self.to_field()] } fn unpack(fields: [Field; U128_PACKED_LEN]) -> Self { U128::from_integer(fields[0]) } } ``` ### Logs for notes, partial notes, and events have been refactored.[​](#logs-for-notes-partial-notes-and-events-have-been-refactored "Direct link to Logs for notes, partial notes, and events have been refactored.") We're preparing to make log assembly more customisable. These paths have changed. ``` - use dep::aztec::encrypted_logs::encrypted_note_emission::encode_and_encrypt_note, + use dep::aztec::messages::logs::note::encode_and_encrypt_note, ``` And similar paths for `encode_and_encrypt_note_unconstrained`, and for events and partial notes. The way in which logs are assembled in this "default\_aes128" strategy is has also changed. I repeat: **Encrypted log layouts have changed**. The corresponding typescript for note discovery has also been changed, but if you've rolled your own functions for parsing and decrypting logs, those will be broken by this change. ### `NoteInferface` and `EventInterface` no-longer have a `to_be_bytes` method.[​](#noteinferface-and-eventinterface-no-longer-have-a-to_be_bytes-method "Direct link to noteinferface-and-eventinterface-no-longer-have-a-to_be_bytes-method") You can remove this method from any custom notes or events that you've implemented. ### \[Aztec.nr] Packing notes resulting in changes in `NoteInterface`[​](#aztecnr-packing-notes-resulting-in-changes-in-noteinterface "Direct link to aztecnr-packing-notes-resulting-in-changes-in-noteinterface") Note interface implementation generated by our macros now packs note content instead of serializing it With this change notes are being less costly DA-wise to emit when some of the note struct members implements the `Packable` trait (this is typically the `UintNote` which represents `value` as `U128` that gets serialized as 2 fields but packed as 1). This results in the following changes in the `NoteInterface`: ``` pub trait NoteInterface { - fn serialize_content(self) -> [Field; N]; + fn pack_content(self) -> [Field; N]; - fn deserialize_content(fields: [Field; N]) -> Self; + fn unpack_content(fields: [Field; N]) -> Self; fn get_header(self) -> NoteHeader; fn set_header(&mut self, header: NoteHeader) -> (); fn get_note_type_id() -> Field; fn compute_note_hash(self) -> Field; } ``` ### \[PXE] Cleanup of Contract and ContractClass information getters[​](#pxe-cleanup-of-contract-and-contractclass-information-getters "Direct link to \[PXE] Cleanup of Contract and ContractClass information getters") ``` - pxe.isContractInitialized - pxe.getContractInstance - pxe.isContractPubliclyDeployed + pxe.getContractMetadata ``` have been merged into getContractMetadata ``` - pxe.getContractClass - pxe.isContractClassPubliclyRegistered - pxe.getContractArtifact + pxe.getContractClassMetadata ``` These functions have been merged into `pxe.getContractMetadata` and `pxe.getContractClassMetadata`. ## 0.72.0[​](#0720 "Direct link to 0.72.0") ### Some functions in `aztec.js` and `@aztec/accounts` are now async[​](#some-functions-in-aztecjs-and-aztecaccounts-are-now-async "Direct link to some-functions-in-aztecjs-and-aztecaccounts-are-now-async") In our efforts to make libraries more browser-friendly and providing with more bundling options for `bb.js` (like a non top-level-await version), some functions are being made async, in particular those that access our cryptographic functions. ``` - AztecAddress.random(); + await AztecAddress.random(); - getSchnorrAccount(); + await getSchnorrAccount(); ``` ### Public logs replace unencrypted logs[​](#public-logs-replace-unencrypted-logs "Direct link to Public logs replace unencrypted logs") Any log emitted from public is now known as a public log, rather than an unencrypted log. This means methods relating to these logs have been renamed e.g. in the pxe, archiver, txe: ``` - getUnencryptedLogs(filter: LogFilter): Promise - getUnencryptedEvents(eventMetadata: EventMetadataDefinition, from: number, limit: number): Promise + getPublicLogs(filter: LogFilter): Promise + getPublicEvents(eventMetadata: EventMetadataDefinition, from: number, limit: number): Promise ``` The context method in aztec.nr is now: ``` - context.emit_unencrypted_log(log) + context.emit_public_log(log) ``` These logs were treated as bytes in the node and as hashes in the protocol circuits. Now, public logs are treated as fields everywhere: ``` - unencryptedLogs: UnencryptedTxL2Logs - unencrypted_logs_hashes: [ScopedLogHash; MAX_UNENCRYPTED_LOGS_PER_TX] + publicLogs: PublicLog[] + public_logs: [PublicLog; MAX_PUBLIC_LOGS_PER_TX] ``` A `PublicLog` contains the log (as an array of fields) and the app address. This PR also renamed encrypted events to private events: ``` - getEncryptedEvents(eventMetadata: EventMetadataDefinition, from: number, limit: number, vpks: Point[]): Promise + getPrivateEvents(eventMetadata: EventMetadataDefinition, from: number, limit: number, vpks: Point[]): Promise ``` ## 0.70.0[​](#0700 "Direct link to 0.70.0") ### \[Aztec.nr] Removal of `getSiblingPath` oracle[​](#aztecnr-removal-of-getsiblingpath-oracle "Direct link to aztecnr-removal-of-getsiblingpath-oracle") Use `getMembershipWitness` oracle instead that returns both the sibling path and index. ## 0.68.0[​](#0680 "Direct link to 0.68.0") ### \[archiver, node, pxe] Remove contract artifacts in node and archiver and store function names instead[​](#archiver-node-pxe-remove-contract-artifacts-in-node-and-archiver-and-store-function-names-instead "Direct link to \[archiver, node, pxe] Remove contract artifacts in node and archiver and store function names instead") Contract artifacts were only in the archiver for debugging purposes. Instead function names are now (optionally) emitted when registering contract classes Function changes in the Node interface and Contract Data source interface: ``` - addContractArtifact(address: AztecAddress, artifact: ContractArtifact): Promise; + registerContractFunctionNames(address: AztecAddress, names: Record): Promise; ``` So now the PXE registers this when calling `registerContract()` ``` await this.node.registerContractFunctionNames(instance.address, functionNames); ``` Function changes in the Archiver ``` - addContractArtifact(address: AztecAddress, artifact: ContractArtifact) - getContractArtifact(address: AztecAddress) + registerContractFunctionNames(address: AztecAddress, names: Record): Promise ``` ### \[fees, fpc] Changes in setting up FPC as fee payer on AztecJS and method names in FPC[​](#fees-fpc-changes-in-setting-up-fpc-as-fee-payer-on-aztecjs-and-method-names-in-fpc "Direct link to \[fees, fpc] Changes in setting up FPC as fee payer on AztecJS and method names in FPC") On AztecJS, setting up `PrivateFeePaymentMethod` and `PublicFeePaymentMethod` are now the same. The don't need to specify a sequencer address or which coin to pay in. The coins are set up in the FPC contract! ``` - paymentMethod: new PrivateFeePaymentMethod(bananaCoin.address,bananaFPC.address,aliceWallet,sequencerAddress), + paymentMethod: new PrivateFeePaymentMethod(bananaFPC.address, aliceWallet), - paymentMethod: new PublicFeePaymentMethod(bananaCoin.address, bananaFPC.address, aliceWallet), + paymentMethod: new PublicFeePaymentMethod(bananaFPC.address, aliceWallet), ``` Changes in `FeePaymentMethod` class in AztecJS ``` - getAsset(): AztecAddress; + getAsset(): Promise; ``` Changes in the token contract: FPC specific methods, `setup_refund()` and `complete_refund()` have minor args rename. Changes in FPC contract: Rename of args in all of FPC functions as FPC now stores the accepted token address and admin and making it clearer the amounts are corresponding to the accepted token and not fee juice. Also created a public function `pull_funds()` for admin to clawback any money in the FPC Expect more changes in FPC in the coming releases! ### Name change from `contact` to `sender` in PXE API[​](#name-change-from-contact-to-sender-in-pxe-api "Direct link to name-change-from-contact-to-sender-in-pxe-api") `contact` has been deemed confusing because the name is too similar to `contract`. For this reason we've decided to rename it: ``` - await pxe.registerContact(address); + await pxe.registerSender(address); - await pxe.getContacts(); + await pxe.getSenders(); - await pxe.removeContact(address); + await pxe.removeSender(address); ``` ## 0.67.1[​](#0671 "Direct link to 0.67.1") ### Noir contracts package no longer exposes artifacts as default export[​](#noir-contracts-package-no-longer-exposes-artifacts-as-default-export "Direct link to Noir contracts package no longer exposes artifacts as default export") To reduce loading times, the package `@aztec/noir-contracts.js` no longer exposes all artifacts as its default export. Instead, it exposes a `ContractNames` variable with the list of all contract names available. To import a given artifact, use the corresponding export, such as `@aztec/noir-contracts.js/FPC`. ### Blobs[​](#blobs "Direct link to Blobs") We now publish the majority of DA in L1 blobs rather than calldata, with only contract class logs remaining as calldata. This replaces all code that touched the `txsEffectsHash`. In the rollup circuits, instead of hashing each child circuit's `txsEffectsHash` to form a tree, we track tx effects by absorbing them into a sponge for blob data (hence the name: `spongeBlob`). This sponge is treated like the state trees in that we check each rollup circuit 'follows' the next: ``` - let txs_effects_hash = sha256_to_field(left.txs_effects_hash, right.txs_effects_hash); + assert(left.end_sponge_blob.eq(right.start_sponge_blob)); + let start_sponge_blob = left.start_sponge_blob; + let end_sponge_blob = right.end_sponge_blob; ``` This sponge is used in the block root circuit to confirm that an injected array of all `txEffects` does match those rolled up so far in the `spongeBlob`. Then, the `txEffects` array is used to construct and prove opening of the polynomial representing the blob commitment on L1 (this is done efficiently thanks to the Barycentric formula). On L1, we publish the array as a blob and verify the above proof of opening. This confirms that the tx effects in the rollup circuit match the data in the blob: ``` - bytes32 txsEffectsHash = TxsDecoder.decode(_body); + bytes32 blobHash = _validateBlob(blobInput); ``` Where `blobInput` contains the proof of opening and evaluation calculated in the block root rollup circuit. It is then stored and used as a public input to verifying the epoch proof. ## 0.67.0[​](#0670 "Direct link to 0.67.0") ### L2 Gas limit of 6M enforced for public portion of TX[​](#l2-gas-limit-of-6m-enforced-for-public-portion-of-tx "Direct link to L2 Gas limit of 6M enforced for public portion of TX") A 12M limit was previously enforced per-enqueued-public-call. The protocol now enforces a stricter limit that the entire public portion of a transaction consumes at most 6,000,000 L2 gas. ### \[aztec.nr] Renamed `Header` and associated helpers[​](#aztecnr-renamed-header-and-associated-helpers "Direct link to aztecnr-renamed-header-and-associated-helpers") The `Header` struct has been renamed to `BlockHeader`, and the `get_header()` family of functions have been similarly renamed to `get_block_header()`. ``` - let header = context.get_header_at(block_number); + let header = context.get_block_header_at(block_number); ``` ### Outgoing Events removed[​](#outgoing-events-removed "Direct link to Outgoing Events removed") Previously, every event which was emitted included: * Incoming Header (to convey the app contract address to the recipient) * Incoming Ciphertext (to convey the note contents to the recipient) * Outgoing Header (served as a backup, to convey the app contract address to the "outgoing viewer" - most likely the sender) * Outgoing Ciphertext (served as a backup, encrypting the symmetric key of the incoming ciphertext to the "outgoing viewer" - most likely the sender) The latter two have been removed from the `.emit()` functions, so now only an Incoming Header and Incoming Ciphertext will be emitted. The interface for emitting a note has therefore changed, slightly. No more ovpk's need to be derived and passed into `.emit()` functions. ``` - nfts.at(to).insert(&mut new_note).emit(encode_and_encrypt_note(&mut context, from_ovpk_m, to, from)); + nfts.at(to).insert(&mut new_note).emit(encode_and_encrypt_note(&mut context, to, from)); ``` The `getOutgoingNotes` function is removed from the PXE interface. Some aztec.nr library methods' arguments are simplified to remove an `outgoing_viewer` parameter. E.g. `ValueNote::increment`, `ValueNote::decrement`, `ValueNote::decrement_by_at_most`, `EasyPrivateUint::add`, `EasyPrivateUint::sub`. Further changes are planned, so that: * Outgoing ciphertexts (or any kind of abstract ciphertext) can be emitted by a contract, and on the other side discovered and then processed by the contract. * Headers will be removed, due to the new tagging scheme. ## 0.66[​](#066 "Direct link to 0.66") ### DEBUG env var is removed[​](#debug-env-var-is-removed "Direct link to DEBUG env var is removed") The `DEBUG` variable is no longer used. Use `LOG_LEVEL` with one of `silent`, `fatal`, `error`, `warn`, `info`, `verbose`, `debug`, or `trace`. To tweak log levels per module, add a list of module prefixes with their overridden level. For example, LOG\_LEVEL="info; verbose: aztec:sequencer, aztec:archiver; debug: aztec:kv-store" sets `info` as the default log level, `verbose` for the sequencer and archiver, and `debug` for the kv-store. Module name match is done by prefix. ### `tty` resolve fallback required for browser bundling[​](#tty-resolve-fallback-required-for-browser-bundling "Direct link to tty-resolve-fallback-required-for-browser-bundling") When bundling `aztec.js` for web, the `tty` package now needs to be specified as an empty fallback: ``` resolve: { plugins: [new ResolveTypeScriptPlugin()], alias: { './node/index.js': false }, fallback: { crypto: false, os: false, fs: false, path: false, url: false, + tty: false, worker_threads: false, buffer: require.resolve('buffer/'), util: require.resolve('util/'), stream: require.resolve('stream-browserify'), }, }, ``` ## 0.65[​](#065 "Direct link to 0.65") ### \[aztec.nr] Removed SharedImmutable[​](#aztecnr-removed-sharedimmutable "Direct link to \[aztec.nr] Removed SharedImmutable") The `SharedImmutable` state variable has been removed, since it was essentially the exact same as `PublicImmutable`, which now contains functions for reading from private: ``` - foo: SharedImmutable. + foo: PublicImmutable. ``` ### \[aztec.nr] SharedImmutable renamings[​](#aztecnr-sharedimmutable-renamings "Direct link to \[aztec.nr] SharedImmutable renamings") `SharedImmutable::read_private` and `SharedImmutable::read_public` were renamed to simply `read`, since only one of these versions is ever available depending on the current context. ``` // In private - let value = storage.my_var.read_private(); + let value = storage.my_var.read(); // In public - let value = storage.my_var.read_public(); + let value = storage.my_var.read(); ``` ### \[aztec.nr] SharedMutable renamings[​](#aztecnr-sharedmutable-renamings "Direct link to \[aztec.nr] SharedMutable renamings") `SharedMutable` getters (`get_current_value_in_public`, etc.) were renamed by dropping the `_in` suffix, since only one of these versions is ever available depending on the current context. ``` // In private - let value = storage.my_var.get_current_value_in_private(); + let value = storage.my_var.get_current_value(); // In public - let value = storage.my_var.get_current_value_in_public(); + let value = storage.my_var.get_current_value(); ``` ### \[aztec.js] Random addresses are now valid[​](#aztecjs-random-addresses-are-now-valid "Direct link to \[aztec.js] Random addresses are now valid") The `AztecAddress.random()` function now returns valid addresses, i.e. addresses that can receive encrypted messages and therefore have notes be sent to them. `AztecAddress.isValid()` was also added to check for validity of an address. ## 0.63.0[​](#0630 "Direct link to 0.63.0") ### \[PXE] Note tagging and discovery[​](#pxe-note-tagging-and-discovery "Direct link to \[PXE] Note tagging and discovery") PXE's trial decryption of notes has been replaced in favor of a tagging and discovery approach. It is much more efficient and should scale a lot better as the network size increases, since notes can now be discovered on-demand. For the time being, this means that accounts residing *on different PXE instances* should add senders to their contact list, so notes can be discovered (accounts created on the same PXE instance will be added as senders for each other by default) ``` +pxe.registerContact(senderAddress) ``` The note discovery process is triggered automatically whenever a contract invokes the `get_notes` oracle, meaning no contract changes are expected. Just in case, every contract has now a utility method `sync_notes` that can trigger the process manually if necessary. This can be useful since now the `DebugInfo` object that can be obtained when sending a tx with the `debug` flag set to true no longer contains the notes that were generated in the transaction: ``` const receipt = await inclusionsProofsContract.methods.create_note(owner, 5n).send().wait({ debug: true }); -const { visibleIncomingNotes } = receipt.debugInfo!; -expect(visibleIncomingNotes.length).toEqual(1); +await inclusionsProofsContract.methods.sync_notes().simulate(); +const incomingNotes = await wallet.getIncomingNotes({ txHash: receipt.txHash }); +expect(incomingNotes.length).toEqual(1); ``` ### \[Token contract] Partial notes related refactor[​](#token-contract-partial-notes-related-refactor "Direct link to \[Token contract] Partial notes related refactor") We've decided to replace the old "shield" flow with one leveraging partial notes. This led to a removal of `shield` and `redeem_shield` functions and an introduction of `transfer_to_private`. An advantage of the new approach is that only 1 tx is required and the API of partial notes is generally nicer. For more information on partial notes refer to docs. ### \[Token contract] Function naming changes[​](#token-contract-function-naming-changes "Direct link to \[Token contract] Function naming changes") There have been a few naming changes done for improved consistency. These are the renamings: `transfer_public` --> `transfer_in_public` `transfer_from` --> `transfer_in_private` `mint_public` --> `mint_to_public` `burn` --> `burn_private` ## 0.62.0[​](#0620 "Direct link to 0.62.0") ### \[TXE] Single execution environment[​](#txe-single-execution-environment "Direct link to \[TXE] Single execution environment") Thanks to recent advancements in Brillig TXE performs every single call as if it was a nested call, spawning a new ACVM or AVM simulator without performance loss. This ensures every single test runs in a consistent environment and allows for clearer test syntax: ``` -let my_call_interface = MyContract::at(address).my_function(args); -env.call_private(my_contract_interface) +MyContract::at(address).my_function(args).call(&mut env.private()); ``` This implies every contract has to be deployed before it can be tested (via `env.deploy` or `env.deploy_self`) and of course it has to be recompiled if its code was changed before TXE can use the modified bytecode. ### Uniqueness of L1 to L2 messages[​](#uniqueness-of-l1-to-l2-messages "Direct link to Uniqueness of L1 to L2 messages") L1 to L2 messages have been updated to guarantee their uniqueness. This means that the hash of an L1 to L2 message cannot be precomputed, and must be obtained from the `MessageSent` event emitted by the `Inbox` contract, found in the L1 transaction receipt that inserted the message: ``` event MessageSent(uint256 indexed l2BlockNumber, uint256 index, bytes32 indexed hash); ``` This event now also includes an `index`. This index was previously required to consume an L1 to L2 message in a public function, and now it is also required for doing so in a private function, since it is part of the message hash preimage. The `PrivateContext` in aztec-nr has been updated to reflect this: ``` pub fn consume_l1_to_l2_message( &mut self, content: Field, secret: Field, sender: EthAddress, + leaf_index: Field, ) { ``` This change has also modified the internal structure of the archiver database, making it incompatible with previous ones. Last, the API for obtaining an L1 to L2 message membership witness has been simplified to leverage message uniqueness: ``` getL1ToL2MessageMembershipWitness( blockNumber: L2BlockNumber, l1ToL2Message: Fr, - startIndex: bigint, ): Promise<[bigint, SiblingPath] | undefined>; ``` ### Address is now a point[​](#address-is-now-a-point "Direct link to Address is now a point") The address now serves as someone's public key to encrypt incoming notes. An address point has a corresponding address secret, which is used to decrypt the notes encrypted with the address point. ### Notes no longer store a hash of the nullifier public keys, and now store addresses[​](#notes-no-longer-store-a-hash-of-the-nullifier-public-keys-and-now-store-addresses "Direct link to Notes no longer store a hash of the nullifier public keys, and now store addresses") Because of removing key rotation, we can now store addresses as the owner of a note. Because of this and the above change, we can and have removed the process of registering a recipient, because now we do not need any keys of the recipient. example\_note.nr ``` -npk_m_hash: Field +owner: AztecAddress ``` PXE Interface ``` -registerRecipient(completeAddress: CompleteAddress) ``` ## 0.58.0[​](#0580 "Direct link to 0.58.0") ### \[l1-contracts] Inbox's MessageSent event emits global tree index[​](#l1-contracts-inboxs-messagesent-event-emits-global-tree-index "Direct link to \[l1-contracts] Inbox's MessageSent event emits global tree index") Earlier `MessageSent` event in Inbox emitted a subtree index (index of the message in the subtree of the l2Block). But the nodes and Aztec.nr expects the index in the global L1\_TO\_L2\_MESSAGES\_TREE. So to make it easier to parse this, Inbox now emits this global index. ## 0.57.0[​](#0570 "Direct link to 0.57.0") ### Changes to PXE API and \`ContractFunctionInteraction\`\`[​](#changes-to-pxe-api-and-contractfunctioninteraction "Direct link to Changes to PXE API and `ContractFunctionInteraction``") PXE APIs have been refactored to better reflect the lifecycle of a Tx (`execute private -> simulate kernels -> simulate public (estimate gas) -> prove -> send`) * `.simulateTx`: Now returns a `TxSimulationResult`, containing the output of private execution, kernel simulation and public simulation (optional). * `.proveTx`: Now accepts the result of executing the private part of a transaction, so simulation doesn't have to happen again. Thanks to this refactor, `ContractFunctionInteraction` has been updated to remove its internal cache and avoid bugs due to its mutable nature. As a result our type-safe interfaces now have to be used as follows: ``` -const action = MyContract.at(address).method(args); -await action.prove(); -await action.send().wait(); +const action = MyContract.at(address).method(args); +const provenTx = await action.prove(); +await provenTx.send().wait(); ``` It's still possible to use `.send()` as before, which will perform proving under the hood. More changes are coming to these APIs to better support gas estimation mechanisms and advanced features. ### Changes to public calling convention[​](#changes-to-public-calling-convention "Direct link to Changes to public calling convention") Contracts that include public functions (that is, marked with `#[public]`), are required to have a function `public_dispatch(selector: Field)` which acts as an entry point. This will be soon the only public function registered/deployed in contracts. The calling convention is updated so that external calls are made to this function. If you are writing your contracts using Aztec-nr, there is nothing you need to change. The `public_dispatch` function is automatically generated by the `#[aztec]` macro. ### \[Aztec.nr] Renamed `unsafe_rand` to `random`[​](#aztecnr-renamed-unsafe_rand-to-random "Direct link to aztecnr-renamed-unsafe_rand-to-random") Since this is an `unconstrained` function, callers are already supposed to include an `unsafe` block, so this function has been renamed for reduced verbosity. ``` -use aztec::oracle::unsafe_rand::unsafe_rand; +use aztec::oracle::random::random; -let random_value = unsafe { unsafe_rand() }; +let random_value = unsafe { random() }; ``` ### \[Aztec.js] Removed `L2Block.fromFields`[​](#aztecjs-removed-l2blockfromfields "Direct link to aztecjs-removed-l2blockfromfields") `L2Block.fromFields` was a syntactic sugar which is causing [issues](https://github.com/AztecProtocol/aztec-packages/issues/8340) so we've removed it. ``` -const l2Block = L2Block.fromFields({ header, archive, body }); +const l2Block = new L2Block(archive, header, body); ``` ### \[Aztec.nr] Removed `SharedMutablePrivateGetter`[​](#aztecnr-removed-sharedmutableprivategetter "Direct link to aztecnr-removed-sharedmutableprivategetter") This state variable was deleted due to it being difficult to use safely. ### \[Aztec.nr] Changes to `NullifiableNote`[​](#aztecnr-changes-to-nullifiablenote "Direct link to aztecnr-changes-to-nullifiablenote") The `compute_nullifier_without_context` function is now `unconstrained`. It had always been meant to be called in unconstrained contexts (which is why it did not receive the `context` object), but now that Noir supports trait functions being `unconstrained` this can be implemented properly. Users must add the `unconstrained` keyword to their implementations of the trait: ``` impl NullifiableNote for MyCustomNote { - fn compute_nullifier_without_context(self) -> Field { + unconstrained fn compute_nullifier_without_context(self) -> Field { ``` ### \[Aztec.nr] Make `TestEnvironment` unconstrained[​](#aztecnr-make-testenvironment-unconstrained "Direct link to aztecnr-make-testenvironment-unconstrained") All of `TestEnvironment`'s functions are now `unconstrained`, preventing accidentally calling them in a constrained circuit, among other kinds of user error. Becuase they work with mutable references, and these are not allowed to cross the constrained/unconstrained barrier, tests that use `TestEnvironment` must also become `unconstrained`. The recommended practice is to make *all* Noir tests and test helper functions be \`unconstrained: ``` #[test] -fn test_my_function() { +unconstrained fn test_my_function() { let env = TestEnvironment::new(); ``` ### \[Aztec.nr] removed `encode_and_encrypt_note` and renamed `encode_and_encrypt_note_with_keys` to `encode_and_encrypt_note`[​](#aztecnr-removed-encode_and_encrypt_note-and-renamed-encode_and_encrypt_note_with_keys-to-encode_and_encrypt_note "Direct link to aztecnr-removed-encode_and_encrypt_note-and-renamed-encode_and_encrypt_note_with_keys-to-encode_and_encrypt_note") ``` contract XYZ { - use dep::aztec::encrypted_logs::encrypted_note_emission::encode_and_encrypt_note_with_keys; + use dep::aztec::encrypted_logs::encrypted_note_emission::encode_and_encrypt_note; ... - numbers.at(owner).initialize(&mut new_number).emit(encode_and_encrypt_note_with_keys(&mut context, owner_ovpk_m, owner_ivpk_m, owner)); + numbers.at(owner).initialize(&mut new_number).emit(encode_and_encrypt_note(&mut context, owner_ovpk_m, owner_ivpk_m, owner)); } ``` ## 0.56.0[​](#0560 "Direct link to 0.56.0") ### \[Aztec.nr] Changes to contract definition[​](#aztecnr-changes-to-contract-definition "Direct link to \[Aztec.nr] Changes to contract definition") We've migrated the Aztec macros to use the newly introduce meta programming Noir feature. Due to being Noir-based, the new macros are less obscure and can be more easily modified. As part of this transition, some changes need to be applied to Aztec contracts: * The top level `contract` block needs to have the `#[aztec]` macro applied to it. * All `#[aztec(name)]` macros are renamed to `#[name]`. * The storage struct (the one that gets the `#[storage]` macro applied) but be generic over a `Context` type, and all state variables receive this type as their last generic type parameter. ``` + use dep::aztec::macros::aztec; #[aztec] contract Token { + use dep::aztec::macros::{storage::storage, events::event, functions::{initializer, private, view, public}}; - #[aztec(storage)] - struct Storage { + #[storage] + struct Storage { - admin: PublicMutable, + admin: PublicMutable, - minters: Map>, + minters: Map, Context>, } - #[aztec(public)] - #[aztec(initializer)] + #[public] + #[initializer] fn constructor(admin: AztecAddress, name: str<31>, symbol: str<31>, decimals: u8) { ... } - #[aztec(public)] - #[aztec(view)] - fn public_get_name() -> FieldCompressedString { + #[public] + #[view] fn public_get_name() -> FieldCompressedString { ... } ``` ### \[Aztec.nr] Changes to `NoteInterface`[​](#aztecnr-changes-to-noteinterface "Direct link to aztecnr-changes-to-noteinterface") The new macro model prevents partial trait auto-implementation: they either implement the entire trait or none of it. This means users can no longer implement part of `NoteInterface` and have the rest be auto-implemented. For this reason we've separated the methods which are auto-implemented and those which needs to be implemented manually into two separate traits: the auto-implemented ones stay in the `NoteInterface` trace and the manually implemented ones were moved to `NullifiableNote` (name likely to change): ``` -#[aztec(note)] +#[note] struct AddressNote { ... } -impl NoteInterface for AddressNote { +impl NullifiableNote for AddressNote { fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { ... } fn compute_nullifier_without_context(self) -> Field { ... } } ``` ### \[Aztec.nr] Changes to contract interface[​](#aztecnr-changes-to-contract-interface "Direct link to \[Aztec.nr] Changes to contract interface") The `Contract::storage()` static method has been renamed to `Contract::storage_layout()`. ``` - let fee_payer_balances_slot = derive_storage_slot_in_map(Token::storage().balances.slot, fee_payer); - let user_balances_slot = derive_storage_slot_in_map(Token::storage().balances.slot, user); + let fee_payer_balances_slot = derive_storage_slot_in_map(Token::storage_layout().balances.slot, fee_payer); + let user_balances_slot = derive_storage_slot_in_map(Token::storage_layout().balances.slot, user); ``` ### Key rotation removed[​](#key-rotation-removed "Direct link to Key rotation removed") The ability to rotate incoming, outgoing, nullifying and tagging keys has been removed - this feature was easy to misuse and not worth the complexity and gate count cost. As part of this, the Key Registry contract has also been deleted. The API for fetching public keys has been adjusted accordingly: ``` - let keys = get_current_public_keys(&mut context, account); + let keys = get_public_keys(account); ``` ### \[Aztec.nr] Rework `NoteGetterOptions::select`[​](#aztecnr-rework-notegetteroptionsselect "Direct link to aztecnr-rework-notegetteroptionsselect") The `select` function in both `NoteGetterOptions` and `NoteViewerOptions` no longer takes an `Option` of a comparator, but instead requires an explicit comparator to be passed. Additionally, the order of the parameters has been changed so that they are `(lhs, operator, rhs)`. These two changes should make invocations of the function easier to read: ``` - options.select(ValueNote::properties().value, amount, Option::none()) + options.select(ValueNote::properties().value, Comparator.EQ, amount) ``` ## 0.53.0[​](#0530 "Direct link to 0.53.0") ### \[Aztec.nr] Remove `OwnedNote` and create `UintNote`[​](#aztecnr-remove-ownednote-and-create-uintnote "Direct link to aztecnr-remove-ownednote-and-create-uintnote") `OwnedNote` allowed having a U128 `value` in the custom note while `ValueNote` restricted to just a Field. We have removed `OwnedNote` but are introducing a more genric `UintNote` within aztec.nr ``` #[aztec(note)] struct UintNote { // The integer stored by the note value: U128, // The nullifying public key hash is used with the nsk_app to ensure that the note can be privately spent. npk_m_hash: Field, // Randomness of the note to hide its contents randomness: Field, } ``` ### \[TXE] logging[​](#txe-logging "Direct link to \[TXE] logging") You can now use `debug_log()` within your contract to print logs when using the TXE Remember to set the following environment variables to activate debug logging: ``` export DEBUG="aztec:*" export LOG_LEVEL="debug" ``` ### \[Account] no assert in is\_valid\_impl[​](#account-no-assert-in-is_valid_impl "Direct link to \[Account] no assert in is_valid_impl") `is_valid_impl` method in account contract asserted if signature was true. Instead now we will return the verification to give flexibility to developers to handle it as they please. ``` - let verification = std::ecdsa_secp256k1::verify_signature(public_key.x, public_key.y, signature, hashed_message); - assert(verification == true); - true + std::ecdsa_secp256k1::verify_signature(public_key.x, public_key.y, signature, hashed_message) ``` ## 0.49.0[​](#0490 "Direct link to 0.49.0") ### Key Rotation API overhaul[​](#key-rotation-api-overhaul "Direct link to Key Rotation API overhaul") Public keys (ivpk, ovpk, npk, tpk) should no longer be fetched using the old `get_[x]pk_m` methods on the `Header` struct, but rather by calling `get_current_public_keys`, which returns a `PublicKeys` struct with all four keys at once: ``` +use dep::aztec::keys::getters::get_current_public_keys; -let header = context.header(); -let owner_ivpk_m = header.get_ivpk_m(&mut context, owner); -let owner_ovpk_m = header.get_ovpk_m(&mut context, owner); +let owner_keys = get_current_public_keys(&mut context, owner); +let owner_ivpk_m = owner_keys.ivpk_m; +let owner_ovpk_m = owner_keys.ovpk_m; ``` If using more than one key per account, this will result in very large circuit gate count reductions. Additionally, `get_historical_public_keys` was added to support reading historical keys using a historical header: ``` +use dep::aztec::keys::getters::get_historical_public_keys; let historical_header = context.header_at(some_block_number); -let owner_ivpk_m = header.get_ivpk_m(&mut context, owner); -let owner_ovpk_m = header.get_ovpk_m(&mut context, owner); +let owner_keys = get_historical_public_keys(historical_header, owner); +let owner_ivpk_m = owner_keys.ivpk_m; +let owner_ovpk_m = owner_keys.ovpk_m; ``` ## 0.48.0[​](#0480 "Direct link to 0.48.0") ### NoteInterface changes[​](#noteinterface-changes "Direct link to NoteInterface changes") `compute_note_hash_and_nullifier*` functions were renamed as `compute_nullifier*` and the `compute_nullifier` function now takes `note_hash_for_nullify` as an argument (this allowed us to reduce gate counts and the hash was typically computed before). Also `compute_note_hash_for_consumption` function was renamed as `compute_note_hash_for_nullification`. ``` impl NoteInterface for ValueNote { - fn compute_note_hash_and_nullifier(self, context: &mut PrivateContext) -> (Field, Field) { - let note_hash_for_nullify = compute_note_hash_for_consumption(self); - let secret = context.request_nsk_app(self.npk_m_hash); - let nullifier = poseidon2_hash_with_separator([ - note_hash_for_nullify, - secret, - ], - DOM_SEP__NOTE_NULLIFIER as Field, - ); - (note_hash_for_nullify, nullifier) - } - fn compute_note_hash_and_nullifier_without_context(self) -> (Field, Field) { - let note_hash_for_nullify = compute_note_hash_for_consumption(self); - let secret = get_nsk_app(self.npk_m_hash); - let nullifier = poseidon2_hash_with_separator([ - note_hash_for_nullify, - secret, - ], - DOM_SEP__NOTE_NULLIFIER as Field, - ); - (note_hash_for_nullify, nullifier) - } + fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { + let secret = context.request_nsk_app(self.npk_m_hash); + poseidon2_hash_with_separator([ + note_hash_for_nullify, + secret + ], + DOM_SEP__NOTE_NULLIFIER as Field, + ) + } + fn compute_nullifier_without_context(self) -> Field { + let note_hash_for_nullify = compute_note_hash_for_nullification(self); + let secret = get_nsk_app(self.npk_m_hash); + poseidon2_hash_with_separator([ + note_hash_for_nullify, + secret, + ], + DOM_SEP__NOTE_NULLIFIER as Field, + ) + } } ``` ### Fee Juice rename[​](#fee-juice-rename "Direct link to Fee Juice rename") The name of the canonical Gas contract has changed to Fee Juice. Update noir code: ``` -GasToken::at(contract_address) +FeeJuice::at(contract_address) ``` Additionally, `NativePaymentMethod` and `NativePaymentMethodWithClaim` have been renamed to `FeeJuicePaymentMethod` and `FeeJuicePaymentMethodWithClaim`. ### PrivateSet::pop\_notes(...)[​](#privatesetpop_notes "Direct link to PrivateSet::pop_notes(...)") The most common flow when working with notes is obtaining them from a `PrivateSet` via `get_notes(...)` and then removing them via `PrivateSet::remove(...)`. This is cumbersome and it results in unnecessary constraints due to a redundant note read request checks in the remove function. For this reason we've implemented `pop_notes(...)` which gets the notes, removes them from the set and returns them. This tight coupling of getting notes and removing them allowed us to safely remove the redundant read request check. Token contract diff: ``` -let options = NoteGetterOptions::with_filter(filter_notes_min_sum, target_amount).set_limit(max_notes); -let notes = self.map.at(owner).get_notes(options); -let mut subtracted = U128::from_integer(0); -for i in 0..options.limit { - if i < notes.len() { - let note = notes.get_unchecked(i); - self.map.at(owner).remove(note); - subtracted = subtracted + note.get_amount(); - } -} -assert(minuend >= subtrahend, "Balance too low"); +let options = NoteGetterOptions::with_filter(filter_notes_min_sum, target_amount).set_limit(max_notes); +let notes = self.map.at(owner).pop_notes(options); +let mut subtracted = U128::from_integer(0); +for i in 0..options.limit { + if i < notes.len() { + let note = notes.get_unchecked(i); + subtracted = subtracted + note.get_amount(); + } +} +assert(minuend >= subtrahend, "Balance too low"); ``` Note that `pop_notes` may not have obtained and removed any notes! The caller must place checks on the returned notes, e.g. in the example above by checking a sum of balances, or by checking the number of returned notes (`assert_eq(notes.len(), expected_num_notes)`). ## 0.47.0[​](#0470 "Direct link to 0.47.0") # \[Aztec sandbox] TXE deployment changes The way simulated deployments are done in TXE tests has changed to avoid relying on TS interfaces. It is now possible to do it by directly pointing to a Noir standalone contract or workspace: ``` -let deployer = env.deploy("path_to_contract_ts_interface"); +let deployer = env.deploy("path_to_contract_root_folder_where_nargo_toml_is", "ContractName"); ``` Extended syntax for more use cases: ``` // The contract we're testing env.deploy_self("ContractName"); // We have to provide ContractName since nargo isn't ready to support multi-contract files // A contract in a workspace env.deploy("../path/to/workspace@package_name", "ContractName"); // This format allows locating the artifact in the root workspace target folder, regardless of internal code organization ``` The deploy function returns a `Deployer`, which requires performing a subsequent call to `without_initializer()`, `with_private_initializer()` or `with_public_initializer()` just like before in order to **actually** deploy the contract. ### \[CLI] Command refactor and unification + `aztec test`[​](#cli-command-refactor-and-unification--aztec-test "Direct link to cli-command-refactor-and-unification--aztec-test") Sandbox commands have been cleaned up and simplified. Doing `aztec-up` now gets you the following top-level commands: `aztec`: All the previous commands + all the CLI ones without having to prefix them with cli. Run `aztec` for help! `aztec-nargo`: No changes **REMOVED/RENAMED**: * `aztec-sandbox` and `aztec sandbox`: now `aztec start --sandbox` * `aztec-builder`: now `aztec codegen` and `aztec update` **ADDED**: * `aztec test [options]`: runs `aztec start --txe && aztec-nargo test --oracle-resolver http://aztec:8081 --silence-warnings [options]` via docker-compose allowing users to easily run contract tests using TXE ## 0.45.0[​](#0450 "Direct link to 0.45.0") ### \[Aztec.nr] Remove unencrypted logs from private[​](#aztecnr-remove-unencrypted-logs-from-private "Direct link to \[Aztec.nr] Remove unencrypted logs from private") They leak privacy so is a footgun! ## 0.44.0[​](#0440 "Direct link to 0.44.0") ### \[Aztec.nr] Autogenerate Serialize methods for events[​](#aztecnr-autogenerate-serialize-methods-for-events "Direct link to \[Aztec.nr] Autogenerate Serialize methods for events") ``` #[aztec(event)] struct WithdrawalProcessed { who: Field, amount: Field, } -impl Serialize<2> for WithdrawalProcessed { - fn serialize(self: Self) -> [Field; 2] { - [self.who.to_field(), self.amount as Field] - } } ``` ### \[Aztec.nr] rename `encode_and_encrypt_with_keys` to `encode_and_encrypt_note_with_keys`[​](#aztecnr-rename-encode_and_encrypt_with_keys-to-encode_and_encrypt_note_with_keys "Direct link to aztecnr-rename-encode_and_encrypt_with_keys-to-encode_and_encrypt_note_with_keys") ``` contract XYZ { - use dep::aztec::encrypted_logs::encrypted_note_emission::encode_and_encrypt_with_keys; + use dep::aztec::encrypted_logs::encrypted_note_emission::encode_and_encrypt_note_with_keys; .... - numbers.at(owner).initialize(&mut new_number).emit(encode_and_encrypt_with_keys(&mut context, owner_ovpk_m, owner_ivpk_m)); + numbers.at(owner).initialize(&mut new_number).emit(encode_and_encrypt_note_with_keys(&mut context, owner_ovpk_m, owner_ivpk_m)); } ``` ### \[Aztec.nr] changes to `NoteInterface`[​](#aztecnr-changes-to-noteinterface-1 "Direct link to aztecnr-changes-to-noteinterface-1") `compute_nullifier` function was renamed to `compute_note_hash_and_nullifier` and now the function has to return not only the nullifier but also the note hash used to compute the nullifier. The same change was done to `compute_nullifier_without_context` function. These changes were done because having the note hash exposed allowed us to not having to re-compute it again in `destroy_note` function of Aztec.nr which led to significant decrease in gate counts (see the [optimization PR](https://github.com/AztecProtocol/aztec-packages/pull/7103) for more details). ``` - impl NoteInterface for ValueNote { - fn compute_nullifier(self, context: &mut PrivateContext) -> Field { - let note_hash_for_nullify = compute_note_hash_for_consumption(self); - let secret = context.request_nsk_app(self.npk_m_hash); - poseidon2_hash([ - note_hash_for_nullify, - secret, - DOM_SEP__NOTE_NULLIFIER as Field, - ]) - } - - fn compute_nullifier_without_context(self) -> Field { - let note_hash_for_nullify = compute_note_hash_for_consumption(self); - let secret = get_nsk_app(self.npk_m_hash); - poseidon2_hash([ - note_hash_for_nullify, - secret, - DOM_SEP__NOTE_NULLIFIER as Field, - ]) - } - } + impl NoteInterface for ValueNote { + fn compute_note_hash_and_nullifier(self, context: &mut PrivateContext) -> (Field, Field) { + let note_hash_for_nullify = compute_note_hash_for_consumption(self); + let secret = context.request_nsk_app(self.npk_m_hash); + let nullifier = poseidon2_hash([ + note_hash_for_nullify, + secret, + DOM_SEP__NOTE_NULLIFIER as Field, + ]); + (note_hash_for_nullify, nullifier) + } + + fn compute_note_hash_and_nullifier_without_context(self) -> (Field, Field) { + let note_hash_for_nullify = compute_note_hash_for_consumption(self); + let secret = get_nsk_app(self.npk_m_hash); + let nullifier = poseidon2_hash([ + note_hash_for_nullify, + secret, + DOM_SEP__NOTE_NULLIFIER as Field, + ]); + (note_hash_for_nullify, nullifier) + } + } ``` ### \[Aztec.nr] `note_getter` returns `BoundedVec`[​](#aztecnr-note_getter-returns-boundedvec "Direct link to aztecnr-note_getter-returns-boundedvec") The `get_notes` and `view_notes` function no longer return an array of options (i.e. `[Option, N_NOTES]`) but instead a `BoundedVec`. This better conveys the useful property the old array had of having all notes collapsed at the beginning of the array, which allows for powerful optimizations and gate count reduction when setting the `options.limit` value. A `BoundedVec` has a `max_len()`, which equals the number of elements it can hold, and a `len()`, which equals the number of elements it currently holds. Since `len()` is typically not knwon at compile time, iterating over a `BoundedVec` looks slightly different than iterating over an array of options: ``` - let option_notes = get_notes(options); - for i in 0..option_notes.len() { - if option_notes[i].is_some() { - let note = option_notes[i].unwrap_unchecked(); - } - } + let notes = get_notes(options); + for i in 0..notes.max_len() { + if i < notes.len() { + let note = notes.get_unchecked(i); + } + } ``` To further reduce gate count, you can iterate over `options.limit` instead of `max_len()`, since `options.limit` is guaranteed to be larger or equal to `len()`, and smaller or equal to `max_len()`: ``` - for i in 0..notes.max_len() { + for i in 0..options.limit { ``` ### \[Aztec.nr] static private authwit[​](#aztecnr-static-private-authwit "Direct link to \[Aztec.nr] static private authwit") The private authwit validation is now making a static call to the account contract instead of passing over control flow. This is to ensure that it cannot be used for re-entry. To make this change however, we cannot allow emitting a nullifier from the account contract, since that would break the static call. Instead, we will be changing the `spend_private_authwit` to a `verify_private_authwit` and in the `auth` library emit the nullifier. This means that the "calling" contract will now be emitting the nullifier, and not the account. For example, for a token contract, the nullifier is now emitted by the token contract. However, as this is done inside the `auth` library, the token contract doesn't need to change much. The biggest difference is related to "cancelling" an authwit. Since it is no longer in the account contract, you cannot just emit a nullifier from it anymore. Instead it must rely on the token contract providing functionality for cancelling. There are also a few general changes to how authwits are generated, namely to more easily support the data required for a validity lookup now. Previously we could lookup the `message_hash` directly at the account contract, now we instead need to use the `inner_hash` and the contract of the consumer to figure out if it have already been emitted. A minor extension have been made to the authwit creations to make it easier to sign a specific a hash with a specific caller, e.g., the `inner_hash` can be provided as `{consumer, inner_hash}` to the `createAuthWit` where it previously needed to do a couple of manual steps to compute the outer hash. The `computeOuterAuthWitHash` have been made internal and the `computeAuthWitMessageHash` can instead be used to compute the values similarly to other authwit computations. ``` const innerHash = computeInnerAuthWitHash([Fr.ZERO, functionSelector.toField(), entrypointPackedArgs.hash]); -const outerHash = computeOuterAuthWitHash( - this.dappEntrypointAddress, - new Fr(this.chainId), - new Fr(this.version), - innerHash, -); +const messageHash = computeAuthWitMessageHash( + { consumer: this.dappEntrypointAddress, innerHash }, + { chainId: new Fr(this.chainId), version: new Fr(this.version) }, +); ``` If the wallet is used to compute the authwit, it will populate the chain id and version instead of requiring it to be provided by tha actor. ``` const innerHash = computeInnerAuthWitHash([Fr.fromString('0xdead')]); -const outerHash = computeOuterAuthWitHash(wallets[1].getAddress(), chainId, version, innerHash); -const witness = await wallets[0].createAuthWit(outerHash); + const witness = await wallets[0].createAuthWit({ comsumer: accounts[1].address, inner_hash }); ``` ## 0.43.0[​](#0430 "Direct link to 0.43.0") ### \[Aztec.nr] break `token.transfer()` into `transfer` and `transferFrom`[​](#aztecnr-break-tokentransfer-into-transfer-and-transferfrom "Direct link to aztecnr-break-tokentransfer-into-transfer-and-transferfrom") Earlier we had just one function - `transfer()` which used authwits to handle the case where a contract/user wants to transfer funds on behalf of another user. To reduce circuit sizes and proof times, we are breaking up `transfer` and introducing a dedicated `transferFrom()` function like in the ERC20 standard. ### \[Aztec.nr] `options.limit` has to be constant[​](#aztecnr-optionslimit-has-to-be-constant "Direct link to aztecnr-optionslimit-has-to-be-constant") The `limit` parameter in `NoteGetterOptions` and `NoteViewerOptions` is now required to be a compile-time constant. This allows performing loops over this value, which leads to reduced circuit gate counts when setting a `limit` value. ### \[Aztec.nr] canonical public authwit registry[​](#aztecnr-canonical-public-authwit-registry "Direct link to \[Aztec.nr] canonical public authwit registry") The public authwits are moved into a shared registry (auth registry) to make it easier for sequencers to approve for their non-revertible (setup phase) whitelist. Previously, it was possible to DOS a sequencer by having a very expensive authwit validation that fails at the end, now the whitelist simply need the registry. Notable, this means that consuming a public authwit will no longer emit a nullifier in the account contract but instead update STORAGE in the public domain. This means that there is a larger difference between private and public again. However, it also means that if contracts need to approve, and use the approval in the same tx, it is transient and don't need to go to DA (saving 96 bytes). For the typescript wallets this is handled so the APIs don't change, but account contracts should get rid of their current setup with `approved_actions`. ``` - let actions = AccountActions::init(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); + let actions = AccountActions::init(&mut context, is_valid_impl); ``` For contracts we have added a `set_authorized` function in the auth library that can be used to set values in the registry. ``` - storage.approved_action.at(message_hash).write(true); + set_authorized(&mut context, message_hash, true); ``` ### \[Aztec.nr] emit encrypted logs[​](#aztecnr-emit-encrypted-logs "Direct link to \[Aztec.nr] emit encrypted logs") Emitting or broadcasting encrypted notes are no longer done as part of the note creation, but must explicitly be either emitted or discarded instead. ``` + use dep::aztec::encrypted_logs::encrypted_note_emission::{encode_and_encrypt, encode_and_encrypt_with_keys}; - storage.balances.sub(from, amount); + storage.balances.sub(from, amount).emit(encode_and_encrypt_with_keys(&mut context, from, from)); + storage.balances.sub(from, amount).emit(encode_and_encrypt_with_keys(&mut context, from_ovpk, from_ivpk)); + storage.balances.sub(from, amount).discard(); ``` ## 0.42.0[​](#0420 "Direct link to 0.42.0") ### \[Aztec.nr] Unconstrained Context[​](#aztecnr-unconstrained-context "Direct link to \[Aztec.nr] Unconstrained Context") Top-level unconstrained execution is now marked by the new `UnconstrainedContext`, which provides access to the block number and contract address being used in the simulation. Any custom state variables that provided unconstrained functions should update their specialization parameter: ``` + use dep::aztec::context::UnconstrainedContext; - impl MyStateVariable<()> { + impl MyStateVariable { ``` ### \[Aztec.nr] Filtering is now constrained[​](#aztecnr-filtering-is-now-constrained "Direct link to \[Aztec.nr] Filtering is now constrained") The `filter` argument of `NoteGetterOptions` (typically passed via the `with_filter()` function) is now applied in a constraining environment, meaning any assertions made during the filtering are guaranteed to hold. This mirrors the behavior of the `select()` function. ### \[Aztec.nr] Emitting encrypted notes and logs[​](#aztecnr-emitting-encrypted-notes-and-logs "Direct link to \[Aztec.nr] Emitting encrypted notes and logs") The `emit_encrypted_log` context function is now `encrypt_and_emit_log` or `encrypt_and_emit_note`. ``` - context.emit_encrypted_log(log1); + context.encrypt_and_emit_log(log1); + context.encrypt_and_emit_note(note1); ``` Broadcasting a note will call `encrypt_and_emit_note` in the background. To broadcast a generic event, use `encrypt_and_emit_log` with the same encryption parameters as notes require. Currently, only fields and arrays of fields are supported as events. By default, logs emitted via `encrypt_and_emit_log` will be siloed with a *masked* contract address. To force the contract address to be revealed, so everyone can check it rather than just the log recipient, provide `randomness = 0`. ## Public execution migrated to the Aztec Virtual Machine[​](#public-execution-migrated-to-the-aztec-virtual-machine "Direct link to Public execution migrated to the Aztec Virtual Machine") **What does this mean for me?** It should be mostly transparent, with a few caveats: * Not all Noir blackbox functions are supported by the AVM. Only `Sha256`, `PedersenHash`, `Poseidon2Permutation`, `Keccak256`, and `ToRadix` are supported. * For public functions, `context.nullifier_exists(...)` will now also consider pending nullifiers. * The following methods of `PublicContext` are not supported anymore: `fee_recipient`, `fee_per_da_gas`, `fee_per_l2_gas`, `call_public_function_no_args`, `static_call_public_function_no_args`, `delegate_call_public_function_no_args`, `call_public_function_with_packed_args`, `set_return_hash`, `finish`. However, in terms of functionality, the new context's interface should be equivalent (unless otherwise specified in this list). * Delegate calls are not yet supported in the AVM. * If you have types with custom serialization that you use across external contracts calls, you might need to modify its serialization to match how Noir would serialize it. This is a known problem unrelated to the AVM, but triggered more often when using it. * A few error messages might change format, so you might need to change your test assertions. **Internal details** Before this change, public bytecode was executed using the same simulator as in private: the ACIR simulator (and internally, the Brillig VM). On the Aztec.nr side, public functions accessed the context through `PublicContext`. After this change, public bytecode will be run using the AVM simulator (the simulator for our upcoming zkVM). This bytecode is generated from Noir contracts in two steps: First, `nargo compile` produces an artifact which has Brillig bytecode for public functions, just as it did before. Second: the `avm-transpiler` takes that artifact, and it transpiles Brillig bytecode to AVM bytecode. This final artifact can now be deployed and used with the new public runtime. On the Aztec.nr side, public functions keep accessing the context using `PublicContext` but the underlying implementation is switch with what formerly was the `AvmContext`. ## 0.41.0[​](#0410 "Direct link to 0.41.0") ### \[Aztec.nr] State variable rework[​](#aztecnr-state-variable-rework "Direct link to \[Aztec.nr] State variable rework") Aztec.nr state variables have been reworked so that calling private functions in public and vice versa is detected as an error during compilation instead of at runtime. This affects users in a number of ways: #### New compile time errors[​](#new-compile-time-errors "Direct link to New compile time errors") It used to be that calling a state variable method only available in public from a private function resulted in obscure runtime errors in the form of a failed `_is_some` assertion. Incorrect usage of the state variable methods now results in compile time errors. For example, given the following function: ``` #[aztec(public)] fn get_decimals() -> pub u8 { storage.decimals.read_private() } ``` The compiler will now error out with ``` Expected type SharedImmutable<_, &mut PrivateContext>, found type SharedImmutable ``` The key component is the second generic parameter: the compiler expects a `PrivateContext` (becuse `read_private` is only available during private execution), but a `PublicContext` is being used instead (because of the `#[aztec(public)]` attribute). #### Generic parameters in `Storage`[​](#generic-parameters-in-storage "Direct link to generic-parameters-in-storage") The `Storage` struct (the one marked with `#[aztec(storage)]`) should now be generic over a `Context` type, which matches the new generic parameter of all Aztec.nr libraries. This parameter is always the last generic parameter. This means that, without any additional features, we'd end up with some extra boilerplate when declaring this struct: ``` #[aztec(storage)] - struct Storage { + struct Storage { - nonce_for_burn_approval: PublicMutable, + nonce_for_burn_approval: PublicMutable, - portal_address: SharedImmutable, + portal_address: SharedImmutable, - approved_action: Map>, + approved_action: Map, Context>, } ``` Because of this, the `#[aztec(storage)]` macro has been updated to **automatically inject** this `Context` generic parameter. The storage declaration does not require any changes. #### Removal of `Context`[​](#removal-of-context "Direct link to removal-of-context") The `Context` type no longer exists. End users typically didn't use it, but if imported it needs to be deleted. ### \[Aztec.nr] View functions and interface navigation[​](#aztecnr-view-functions-and-interface-navigation "Direct link to \[Aztec.nr] View functions and interface navigation") It is now possible to explicitly state a function doesn't perform any state alterations (including storage, logs, nullifiers and/or messages from L2 to L1) with the `#[aztec(view)]` attribute, similarly to solidity's `view` function modifier. ``` #[aztec(public)] + #[aztec(view)] fn get_price(asset_id: Field) -> Asset { storage.assets.at(asset_id).read() } ``` View functions only generate a `StaticCallInterface` that doesn't include `.call` or `.enqueue` methods. Also, the denomination `static` has been completely removed from the interfaces, in favor of the more familiar `view` ``` - let price = PriceFeed::at(asset.oracle).get_price(0).static_call(&mut context).price; + let price = PriceFeed::at(asset.oracle).get_price(0).view(&mut context).price; ``` ``` #[aztec(private)] fn enqueue_public_get_value_from_child(target_contract: AztecAddress, value: Field) { - StaticChild::at(target_contract).pub_get_value(value).static_enqueue(&mut context); + StaticChild::at(target_contract).pub_get_value(value).enqueue_view(&mut context); } ``` Additionally, the Noir LSP will now honor "go to definitions" requests for contract interfaces (Ctrl+click), taking the user to the original function implementation. ### \[Aztec.js] Simulate changes[​](#aztecjs-simulate-changes "Direct link to \[Aztec.js] Simulate changes") * `.simulate()` now tracks closer the process performed by `.send().wait()`, specifically going through the account contract entrypoint instead of directly calling the intended function. * `wallet.viewTx(...)` has been renamed to `wallet.simulateUnconstrained(...)` to better clarify what it does. ### \[Aztec.nr] Keys: Token note now stores an owner master nullifying public key hash instead of an owner address[​](#aztecnr-keys-token-note-now-stores-an-owner-master-nullifying-public-key-hash-instead-of-an-owner-address "Direct link to \[Aztec.nr] Keys: Token note now stores an owner master nullifying public key hash instead of an owner address") i.e. ``` struct TokenNote { amount: U128, - owner: AztecAddress, + npk_m_hash: Field, randomness: Field, } ``` Creating a token note and adding it to storage now looks like this: ``` - let mut note = ValueNote::new(new_value, owner); - storage.a_private_value.insert(&mut note, true); + let owner_npk_m_hash = get_npk_m_hash(&mut context, owner); + let owner_ivpk_m = get_ivpk_m(&mut context, owner); + let mut note = ValueNote::new(new_value, owner_npk_m_hash); + storage.a_private_value.insert(&mut note, true, owner_ivpk_m); ``` Computing the nullifier similarly changes to use this master nullifying public key hash. ## 0.40.0[​](#0400 "Direct link to 0.40.0") ### \[Aztec.nr] Debug logging[​](#aztecnr-debug-logging "Direct link to \[Aztec.nr] Debug logging") The function `debug_log_array_with_prefix` has been removed. Use `debug_log_format` with `{}` instead. The special sequence `{}` will be replaced with the whole array. You can also use `{0}`, `{1}`, ... as usual with `debug_log_format`. ``` - debug_log_array_with_prefix("Prefix", my_array); + debug_log_format("Prefix {}", my_array); ``` ## 0.39.0[​](#0390 "Direct link to 0.39.0") ### \[Aztec.nr] Mutable delays in `SharedMutable`[​](#aztecnr-mutable-delays-in-sharedmutable "Direct link to aztecnr-mutable-delays-in-sharedmutable") The type signature for `SharedMutable` changed from `SharedMutable` to `SharedMutable`. The behavior is the same as before, except the delay can now be changed after deployment by calling `schedule_delay_change`. ### \[Aztec.nr] get\_public\_key oracle replaced with get\_ivpk\_m[​](#aztecnr-get_public_key-oracle-replaced-with-get_ivpk_m "Direct link to \[Aztec.nr] get_public_key oracle replaced with get_ivpk_m") When implementing changes according to a new key scheme we had to change oracles. What used to be called encryption public key is now master incoming viewing public key. ``` - use dep::aztec::oracles::get_public_key::get_public_key; + use dep::aztec::keys::getters::get_ivpk_m; - let encryption_pub_key = get_public_key(self.owner); + let ivpk_m = get_ivpk_m(context, self.owner); ``` ## 0.38.0[​](#0380 "Direct link to 0.38.0") ### \[Aztec.nr] Emitting encrypted logs[​](#aztecnr-emitting-encrypted-logs "Direct link to \[Aztec.nr] Emitting encrypted logs") The `emit_encrypted_log` function is now a context method. ``` - use dep::aztec::log::emit_encrypted_log; - use dep::aztec::logs::emit_encrypted_log; - emit_encrypted_log(context, log1); + context.emit_encrypted_log(log1); ``` ## 0.36.0[​](#0360 "Direct link to 0.36.0") ### `FieldNote` removed[​](#fieldnote-removed "Direct link to fieldnote-removed") `FieldNote` only existed for testing purposes, and was not a note type that should be used in any real application. Its name unfortunately led users to think that it was a note type suitable to store a `Field` value, which it wasn't. If using `FieldNote`, you most likely want to use `ValueNote` instead, which has both randomness for privacy and an owner for proper nullification. ### `SlowUpdatesTree` replaced for `SharedMutable`[​](#slowupdatestree-replaced-for-sharedmutable "Direct link to slowupdatestree-replaced-for-sharedmutable") The old `SlowUpdatesTree` contract and libraries have been removed from the codebase, use the new `SharedMutable` library instead. This will require that you add a global variable specifying a delay in blocks for updates, and replace the slow updates tree state variable with `SharedMutable` variables. ``` + global CHANGE_ROLES_DELAY_BLOCKS = 5; struct Storage { - slow_update: SharedImmutable, + roles: Map>, } ``` Reading from `SharedMutable` is much simpler, all that's required is to call `get_current_value_in_public` or `get_current_value_in_private`, depending on the domain. ``` - let caller_roles = UserFlags::new(U128::from_integer(slow.read_at_pub(context.msg_sender().to_field()).call(&mut context))); + let caller_roles = storage.roles.at(context.msg_sender()).get_current_value_in_public(); ``` Finally, you can remove all capsule usage on the client code or tests, since those are no longer required when working with `SharedMutable`. ### \[Aztec.nr & js] Portal addresses[​](#aztecnr--js-portal-addresses "Direct link to \[Aztec.nr & js] Portal addresses") Deployments have been modified. No longer are portal addresses treated as a special class, being immutably set on creation of a contract. They are no longer passed in differently compared to the other variables and instead should be implemented using usual storage by those who require it. One should use the storage that matches the usecase - likely shared storage to support private and public. This means that you will likely add the portal as a constructor argument ``` - fn constructor(token: AztecAddress) { - storage.token.write(token); - } + struct Storage { ... + portal_address: SharedImmutable, + } + fn constructor(token: AztecAddress, portal_address: EthAddress) { + storage.token.write(token); + storage.portal_address.initialize(portal_address); + } ``` And read it from storage whenever needed instead of from the context. ``` - context.this_portal_address(), + storage.portal_address.read_public(), ``` ### \[Aztec.nr] Oracles[​](#aztecnr-oracles "Direct link to \[Aztec.nr] Oracles") Oracle `get_nullifier_secret_key` was renamed to `get_app_nullifier_secret_key` and `request_nullifier_secret_key` function on PrivateContext was renamed as `request_app_nullifier_secret_key`. ``` - let secret = get_nullifier_secret_key(self.owner); + let secret = get_app_nullifier_secret_key(self.owner); ``` ``` - let secret = context.request_nullifier_secret_key(self.owner); + let secret = context.request_app_nullifier_secret_key(self.owner); ``` ### \[Aztec.nr] Contract interfaces[​](#aztecnr-contract-interfaces "Direct link to \[Aztec.nr] Contract interfaces") It is now possible to import contracts on another contracts and use their automatic interfaces to perform calls. The interfaces have the same name as the contract, and are automatically exported. Parameters are automatically serialized (using the `Serialize` trait) and return values are automatically deserialized (using the `Deserialize` trait). Serialize and Deserialize methods have to conform to the standard ACVM serialization schema for the interface to work! 1. Only fixed length types are supported 2. All numeric types become Fields 3. Strings become arrays of Fields, one per char 4. Arrays become arrays of Fields following rules 2 and 3 5. Structs become arrays of Fields, with every item defined in the same order as they are in Noir code, following rules 2, 3, 4 and 5 (recursive) ``` - context.call_public_function( - storage.gas_token_address.read_private(), - FunctionSelector::from_signature("pay_fee(Field)"), - [42] - ); - - context.call_public_function( - storage.gas_token_address.read_private(), - FunctionSelector::from_signature("pay_fee(Field)"), - [42] - ); - - let _ = context.call_private_function( - storage.subscription_token_address.read_private(), - FunctionSelector::from_signature("transfer((Field),(Field),Field,Field)"), - [ - context.msg_sender().to_field(), - storage.subscription_recipient_address.read_private().to_field(), - storage.subscription_price.read_private(), - nonce - ] - ); + use dep::gas_token::GasToken; + use dep::token::Token; + + ... + // Public call from public land + GasToken::at(storage.gas_token_address.read_private()).pay_fee(42).call(&mut context); + // Public call from private land + GasToken::at(storage.gas_token_address.read_private()).pay_fee(42).enqueue(&mut context); + // Private call from private land + Token::at(asset).transfer(context.msg_sender(), storage.subscription_recipient_address.read_private(), amount, nonce).call(&mut context); ``` It is also possible to use these automatic interfaces from the local contract, and thus enqueue public calls from private without having to rely on low level `context` calls. ### \[Aztec.nr] Rename max block number setter[​](#aztecnr-rename-max-block-number-setter "Direct link to \[Aztec.nr] Rename max block number setter") The `request_max_block_number` function has been renamed to `set_tx_max_block_number` to better reflect that it is not a getter, and that the setting is transaction-wide. ``` - context.request_max_block_number(value); + context.set_tx_max_block_number(value); ``` ### \[Aztec.nr] Get portal address[​](#aztecnr-get-portal-address "Direct link to \[Aztec.nr] Get portal address") The `get_portal_address` oracle was removed. If you need to get the portal address of SomeContract, add the following methods to it ``` #[aztec(private)] fn get_portal_address() -> EthAddress { context.this_portal_address() } #[aztec(public)] fn get_portal_address_public() -> EthAddress { context.this_portal_address() } ``` and change the call to `get_portal_address` ``` - let portal_address = get_portal_address(contract_address); + let portal_address = SomeContract::at(contract_address).get_portal_address().call(&mut context); ``` ### \[Aztec.nr] Required gas limits for public-to-public calls[​](#aztecnr-required-gas-limits-for-public-to-public-calls "Direct link to \[Aztec.nr] Required gas limits for public-to-public calls") When calling a public function from another public function using the `call_public_function` method, you must now specify how much gas you're allocating to the nested call. This will later allow you to limit the amount of gas consumed by the nested call, and handle any out of gas errors. Note that gas limits are not yet enforced. For now, it is suggested you use `dep::aztec::context::gas::GasOpts::default()` which will forward all available gas. ``` + use dep::aztec::context::gas::GasOpts; - context.call_public_function(target_contract, target_selector, args); + context.call_public_function(target_contract, target_selector, args, GasOpts::default()); ``` Note that this is not required when enqueuing a public function from a private one, since top-level enqueued public functions will always consume all gas available for the transaction, as it is not possible to handle any out-of-gas errors. ### \[Aztec.nr] Emitting unencrypted logs[​](#aztecnr-emitting-unencrypted-logs "Direct link to \[Aztec.nr] Emitting unencrypted logs") The `emit_unencrypted_logs` function is now a context method. ``` - use dep::aztec::log::emit_unencrypted_log; - use dep::aztec::log::emit_unencrypted_log_from_private; - emit_unencrypted_log(context, log1); - emit_unencrypted_log_from_private(context, log2); + context.emit_unencrypted_log(log1); + context.emit_unencrypted_log(log2); ``` ## 0.33[​](#033 "Direct link to 0.33") ### \[Aztec.nr] Storage struct annotation[​](#aztecnr-storage-struct-annotation "Direct link to \[Aztec.nr] Storage struct annotation") The storage struct now identified by the annotation `#[aztec(storage)]`, instead of having to rely on it being called `Storage`. ``` - struct Storage { - ... - } + #[aztec(storage)] + struct MyStorageStruct { + ... + } ``` ### \[Aztec.js] Storage layout and note info[​](#aztecjs-storage-layout-and-note-info "Direct link to \[Aztec.js] Storage layout and note info") Storage layout and note information are now exposed in the TS contract artifact ``` - const note = new Note([new Fr(mintAmount), secretHash]); - const pendingShieldStorageSlot = new Fr(5n); // storage slot for pending_shields - const noteTypeId = new Fr(84114971101151129711410111011678111116101n); // note type id for TransparentNote - const extendedNote = new ExtendedNote( - note, - admin.address, - token.address, - pendingShieldStorageSlot, - noteTypeId, - receipt.txHash, - ); - await pxe.addNote(extendedNote); + const note = new Note([new Fr(mintAmount), secretHash]); + const extendedNote = new ExtendedNote( + note, + admin.address, + token.address, + TokenContract.storage.pending_shields.slot, + TokenContract.notes.TransparentNote.id, + receipt.txHash, + ); + await pxe.addNote(extendedNote); ``` ### \[Aztec.nr] rand oracle is now called unsafe\_rand[​](#aztecnr-rand-oracle-is-now-called-unsafe_rand "Direct link to \[Aztec.nr] rand oracle is now called unsafe_rand") `oracle::rand::rand` has been renamed to `oracle::unsafe_rand::unsafe_rand`. This change was made to communicate that we do not constrain the value in circuit and instead we just trust our PXE. ``` - let random_value = rand(); + let random_value = unsafe_rand(); ``` ### \[AztecJS] Simulate and get return values for ANY call and introducing `prove()`[​](#aztecjs-simulate-and-get-return-values-for-any-call-and-introducing-prove "Direct link to aztecjs-simulate-and-get-return-values-for-any-call-and-introducing-prove") Historically it have been possible to "view" `unconstrained` functions to simulate them and get the return values, but not for `public` nor `private` functions. This has lead to a lot of bad code where we have the same function implemented thrice, once in `private`, once in `public` and once in `unconstrained`. It is not possible to call `simulate` on any call to get the return values! However, beware that it currently always returns a Field array of size 4 for private and public. This will change to become similar to the return values of the `unconstrained` functions with proper return types. ``` - #[aztec(private)] - fn get_shared_immutable_constrained_private() -> pub Leader { - storage.shared_immutable.read_private() - } - - unconstrained fn get_shared_immutable() -> pub Leader { - storage.shared_immutable.read_public() - } + #[aztec(private)] + fn get_shared_immutable_private() -> pub Leader { + storage.shared_immutable.read_private() + } - const returnValues = await contract.methods.get_shared_immutable().view(); + const returnValues = await contract.methods.get_shared_immutable_private().simulate(); ``` ``` await expect( - asset.withWallet(wallets[1]).methods.update_admin(newAdminAddress).simulate()).rejects.toThrow( + asset.withWallet(wallets[1]).methods.update_admin(newAdminAddress).prove()).rejects.toThrow( "Assertion failed: caller is not admin 'caller_roles.is_admin'", ); ``` ## 0.31.0[​](#0310 "Direct link to 0.31.0") ### \[Aztec.nr] Public storage historical read API improvement[​](#aztecnr-public-storage-historical-read-api-improvement "Direct link to \[Aztec.nr] Public storage historical read API improvement") `history::public_value_inclusion::prove_public_value_inclusion` has been renamed to `history::storage::public_storage_historical_read`, and its API changed slightly. Instead of receiving a `value` parameter it now returns the historical value stored at that slot. If you were using an oracle to get the value to pass to `prove_public_value_inclusion`, drop the oracle and use the return value from `public_storage_historical_read` instead: ``` - let value = read_storage(); - prove_public_value_inclusion(value, storage_slot, contract_address, context); + let value = public_storage_historical_read(storage_slot, contract_address, context); ``` If you were proving historical existence of a value you got via some other constrained means, perform an assertion against the return value of `public_storage_historical_read` instead: ``` - prove_public_value_inclusion(value, storage_slot, contract_address, context); + assert(public_storage_historical_read(storage_slot, contract_address, context) == value); ``` ## 0.30.0[​](#0300 "Direct link to 0.30.0") ### \[AztecJS] Simplify authwit syntax[​](#aztecjs-simplify-authwit-syntax "Direct link to \[AztecJS] Simplify authwit syntax") ``` - const messageHash = computeAuthWitMessageHash(accounts[1].address, action.request()); - await wallets[0].setPublicAuth(messageHash, true).send().wait(); + await wallets[0].setPublicAuthWit({ caller: accounts[1].address, action }, true).send().wait(); ``` ``` const action = asset .withWallet(wallets[1]) .methods.unshield(accounts[0].address, accounts[1].address, amount, nonce); -const messageHash = computeAuthWitMessageHash(accounts[1].address, action.request()); -const witness = await wallets[0].createAuthWitness(messageHash); +const witness = await wallets[0].createAuthWit({ caller: accounts[1].address, action }); await wallets[1].addAuthWitness(witness); ``` Also note some of the naming changes: `setPublicAuth` -> `setPublicAuthWit` `createAuthWitness` -> `createAuthWit` ### \[Aztec.nr] Automatic NoteInterface implementation and selector changes[​](#aztecnr-automatic-noteinterface-implementation-and-selector-changes "Direct link to \[Aztec.nr] Automatic NoteInterface implementation and selector changes") Implementing a note required a fair amount of boilerplate code, which has been substituted by the `#[aztec(note)]` attribute. ``` + #[aztec(note)] struct AddressNote { address: AztecAddress, owner: AztecAddress, randomness: Field, header: NoteHeader } impl NoteInterface for AddressNote { - fn serialize_content(self) -> [Field; ADDRESS_NOTE_LEN]{ - [self.address.to_field(), self.owner.to_field(), self.randomness] - } - - fn deserialize_content(serialized_note: [Field; ADDRESS_NOTE_LEN]) -> Self { - AddressNote { - address: AztecAddress::from_field(serialized_note[0]), - owner: AztecAddress::from_field(serialized_note[1]), - randomness: serialized_note[2], - header: NoteHeader::empty(), - } - } - - fn compute_note_content_hash(self) -> Field { - pedersen_hash(self.serialize_content(), 0) - } - fn compute_nullifier(self, context: &mut PrivateContext) -> Field { let note_hash_for_nullify = compute_note_hash_for_consumption(self); let secret = context.request_nullifier_secret_key(self.owner); pedersen_hash([ note_hash_for_nullify, secret.low, secret.high, ],0) } fn compute_nullifier_without_context(self) -> Field { let note_hash_for_nullify = compute_note_hash_for_consumption(self); let secret = get_nullifier_secret_key(self.owner); pedersen_hash([ note_hash_for_nullify, secret.low, secret.high, ],0) } - fn set_header(&mut self, header: NoteHeader) { - self.header = header; - } - - fn get_header(note: Self) -> NoteHeader { - note.header - } fn broadcast(self, context: &mut PrivateContext, slot: Field) { let encryption_pub_key = get_public_key(self.owner); emit_encrypted_log( context, (*context).this_address(), slot, Self::get_note_type_id(), encryption_pub_key, self.serialize_content(), ); } - fn get_note_type_id() -> Field { - 6510010011410111511578111116101 - } } ``` Automatic note (de)serialization implementation also means it is now easier to filter notes using `NoteGetterOptions.select` via the `::properties()` helper: Before: ``` let options = NoteGetterOptions::new().select(0, amount, Option::none()).select(1, owner.to_field(), Option::none()).set_limit(1); ``` After: ``` let options = NoteGetterOptions::new().select(ValueNote::properties().value, amount, Option::none()).select(ValueNote::properties().owner, owner.to_field(), Option::none()).set_limit(1); ``` The helper returns a metadata struct that looks like this (if autogenerated) ``` ValueNoteProperties { value: PropertySelector { index: 0, offset: 0, length: 32 }, owner: PropertySelector { index: 1, offset: 0, length: 32 }, randomness: PropertySelector { index: 2, offset: 0, length: 32 }, } ``` It can also be used for the `.sort` method. ## 0.27.0[​](#0270 "Direct link to 0.27.0") ### `initializer` macro replaces `constructor`[​](#initializer-macro-replaces-constructor "Direct link to initializer-macro-replaces-constructor") Before this version, every contract was required to have exactly one `constructor` private function, that was used for deployment. We have now removed this requirement, and made `constructor` a function like any other. To signal that a function can be used to **initialize** a contract, you must now decorate it with the `#[aztec(initializer)]` attribute. Initializers are regular functions that set an "initialized" flag (a nullifier) for the contract. A contract can only be initialized once, and contract functions can only be called after the contract has been initialized, much like a constructor. However, if a contract defines no initializers, it can be called at any time. Additionally, you can define as many initializer functions in a contract as you want, both private and public. To migrate from current code, simply add an initializer attribute to your constructor functions. ``` + #[aztec(initializer)] #[aztec(private)] fn constructor() { ... } ``` If your private constructor was used to just call a public internal initializer, then remove the private constructor and flag the public function as initializer. And if your private constructor was an empty one, just remove it. ## 0.25.0[​](#0250 "Direct link to 0.25.0") ### \[Aztec.nr] Static calls[​](#aztecnr-static-calls "Direct link to \[Aztec.nr] Static calls") It is now possible to perform static calls from both public and private functions. Static calls forbid any modification to the state, including L2->L1 messages or log generation. Once a static context is set through a static all, every subsequent call will also be treated as static via context propagation. ``` context.static_call_private_function(targetContractAddress, targetSelector, args); context.static_call_public_function(targetContractAddress, targetSelector, args); ``` ### \[Aztec.nr] Introduction to `prelude`[​](#aztecnr-introduction-to-prelude "Direct link to aztecnr-introduction-to-prelude") A new `prelude` module to include common Aztec modules and types. This simplifies dependency syntax. For example: ``` use dep::aztec::protocol::address::AztecAddress; use dep::aztec::{ context::{PrivateContext, Context}, note::{note_header::NoteHeader, utils as note_utils}, state_vars::Map }; ``` Becomes: ``` use dep::aztec::prelude::{AztecAddress, NoteHeader, PrivateContext, Map}; use dep::aztec::context::Context; use dep::aztec::notes::utils as note_utils; ``` This will be further simplified in future versions (See [4496](https://github.com/AztecProtocol/aztec-packages/pull/4496) for further details). The prelude consists of \[Edit: removed because the prelude no-longer exists] ### `internal` is now a macro[​](#internal-is-now-a-macro "Direct link to internal-is-now-a-macro") The `internal` keyword is now removed from Noir, and is replaced by an `aztec(internal)` attribute in the function. The resulting behavior is exactly the same: these functions will only be callable from within the same contract. Before: ``` #[aztec(private)] internal fn double(input: Field) -> Field { input * 2 } ``` After: ``` #[aztec(private)] #[aztec(internal)] fn double(input: Field) -> Field { input * 2 } ``` ### \[Aztec.nr] No SafeU120 anymore\![​](#aztecnr-no-safeu120-anymore "Direct link to \[Aztec.nr] No SafeU120 anymore!") Noir now have overflow checks by default. So we don't need SafeU120 like libraries anymore. You can replace it with `U128` instead Before: ``` SafeU120::new(0) ``` Now: ``` U128::from_integer(0) ``` ### \[Aztec.nr] `compute_note_hash_and_nullifier` is now autogenerated[​](#aztecnr-compute_note_hash_and_nullifier-is-now-autogenerated "Direct link to aztecnr-compute_note_hash_and_nullifier-is-now-autogenerated") Historically developers have been required to include a `compute_note_hash_and_nullifier` function in each of their contracts. This function is now automatically generated, and all instances of it in contract code can be safely removed. It is possible to provide a user-defined implementation, in which case auto-generation will be skipped (though there are no known use cases for this). ### \[Aztec.nr] Updated naming of state variable wrappers[​](#aztecnr-updated-naming-of-state-variable-wrappers "Direct link to \[Aztec.nr] Updated naming of state variable wrappers") We have decided to change the naming of our state variable wrappers because the naming was not clear. The changes are as follows: 1. `Singleton` -> `PrivateMutable` 2. `ImmutableSingleton` -> `PrivateImmutable` 3. `StablePublicState` -> `SharedImmutable` 4. `PublicState` -> `PublicMutable` This is the meaning of "private", "public" and "shared": Private: read (R) and write (W) from private, not accessible from public Public: not accessible from private, R/W from public Shared: R from private, R/W from public Note: `SlowUpdates` will be renamed to `SharedMutable` once the implementation is ready. ### \[Aztec.nr] Authwit updates[​](#aztecnr-authwit-updates "Direct link to \[Aztec.nr] Authwit updates") Authentication Witnesses have been updates such that they are now cancellable and scoped to a specific consumer. This means that the `authwit` nullifier must be emitted from the account contract, which require changes to the interface. Namely, the `assert_current_call_valid_authwit_public` and `assert_current_call_valid_authwit` in `auth.nr` will **NO LONGER** emit a nullifier. Instead it will call a `spend_*_authwit` function in the account contract - which will emit the nullifier and perform a few checks. This means that the `is_valid` functions have been removed to not confuse it for a non-mutating function (static). Furthermore, the `caller` parameter of the "authwits" have been moved "further out" such that the account contract can use it in validation, allowing scoped approvals from the account POV. For most contracts, this won't be changing much, but for the account contract, it will require a few changes. Before: ``` #[aztec(public)] fn is_valid_public(message_hash: Field) -> Field { let actions = AccountActions::public(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); actions.is_valid_public(message_hash) } #[aztec(private)] fn is_valid(message_hash: Field) -> Field { let actions = AccountActions::private(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); actions.is_valid(message_hash) } ``` After: ``` #[aztec(private)] fn verify_private_authwit(inner_hash: Field) -> Field { let actions = AccountActions::private(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); actions.verify_private_authwit(inner_hash) } #[aztec(public)] fn spend_public_authwit(inner_hash: Field) -> Field { let actions = AccountActions::public(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); actions.spend_public_authwit(inner_hash) } ``` ## 0.24.0[​](#0240 "Direct link to 0.24.0") ### Introduce Note Type IDs[​](#introduce-note-type-ids "Direct link to Introduce Note Type IDs") Note Type IDs are a new feature which enable contracts to have multiple `Map`s with different underlying note types, something that was not possible before. This is done almost without any user intervention, though some minor changes are required. The mandatory `compute_note_hash_and_nullifier` now has a fifth parameter `note_type_id`. Use this instead of `storage_slot` to determine which deserialization function to use. Before: ``` unconstrained fn compute_note_hash_and_nullifier( contract_address: AztecAddress, nonce: Field, storage_slot: Field, preimage: [Field; TOKEN_NOTE_LEN] ) -> pub [Field; 4] { let note_header = NoteHeader::new(contract_address, nonce, storage_slot); if (storage_slot == storage.pending_shields.get_storage_slot()) { note_utils::compute_note_hash_and_nullifier(TransparentNote::deserialize_content, note_header, preimage) } else if (note_type_id == storage.slow_update.get_storage_slot()) { note_utils::compute_note_hash_and_nullifier(FieldNote::deserialize_content, note_header, preimage) } else { note_utils::compute_note_hash_and_nullifier(TokenNote::deserialize_content, note_header, preimage) } ``` Now: ``` unconstrained fn compute_note_hash_and_nullifier( contract_address: AztecAddress, nonce: Field, storage_slot: Field, note_type_id: Field, preimage: [Field; TOKEN_NOTE_LEN] ) -> pub [Field; 4] { let note_header = NoteHeader::new(contract_address, nonce, storage_slot); if (note_type_id == TransparentNote::get_note_type_id()) { note_utils::compute_note_hash_and_nullifier(TransparentNote::deserialize_content, note_header, preimage) } else if (note_type_id == FieldNote::get_note_type_id()) { note_utils::compute_note_hash_and_nullifier(FieldNote::deserialize_content, note_header, preimage) } else { note_utils::compute_note_hash_and_nullifier(TokenNote::deserialize_content, note_header, preimage) } ``` The `NoteInterface` trait now has an additional `get_note_type_id()` function. This implementation will be autogenerated in the future, but for now providing any unique ID will suffice. The suggested way to do it is by running the Python command shown in the comment below: ``` impl NoteInterface for MyCustomNote { fn get_note_type_id() -> Field { // python -c "print(int(''.join(str(ord(c)) for c in 'MyCustomNote')))" 771216711711511611110978111116101 } } ``` ### \[js] Importing contracts in JS[​](#js-importing-contracts-in-js "Direct link to \[js] Importing contracts in JS") `@aztec/noir-contracts` is now `@aztec/noir-contracts.js`. You'll need to update your package.json & imports. Before: ``` import { TokenContract } from "@aztec/noir-contracts/Token"; ``` Now: ``` import { TokenContract } from "@aztec/noir-contracts.js/Token"; ``` ### \[Aztec.nr] Aztec.nr contracts location change in Nargo.toml[​](#aztecnr-aztecnr-contracts-location-change-in-nargotoml "Direct link to \[Aztec.nr] Aztec.nr contracts location change in Nargo.toml") Aztec contracts are now moved outside of the `yarn-project` folder and into `noir-projects`, so you need to update your imports. Before: ``` easy_private_token_contract = {git = "https://github.com/AztecProtocol/aztec-packages/", tag ="v0.23.0", directory = "yarn-project/noir-contracts/contracts/easy_private_token_contract"} ``` Now, update the `yarn-project` folder for `noir-projects`: ``` easy_private_token_contract = {git = "https://github.com/AztecProtocol/aztec-packages/", tag ="v0.24.0", directory = "noir-projects/noir-contracts/contracts/easy_private_token_contract"} ``` ## 0.22.0[​](#0220 "Direct link to 0.22.0") ### `Note::compute_note_hash` renamed to `Note::compute_note_content_hash`[​](#notecompute_note_hash-renamed-to-notecompute_note_content_hash "Direct link to notecompute_note_hash-renamed-to-notecompute_note_content_hash") The `compute_note_hash` function in of the `Note` trait has been renamed to `compute_note_content_hash` to avoid being confused with the actual note hash. Before: ``` impl NoteInterface for CardNote { fn compute_note_hash(self) -> Field { pedersen_hash([ self.owner.to_field(), ], 0) } ``` Now: ``` impl NoteInterface for CardNote { fn compute_note_content_hash(self) -> Field { pedersen_hash([ self.owner.to_field(), ], 0) } ``` ### Introduce `compute_note_hash_for_consumption` and `compute_note_hash_for_insertion`[​](#introduce-compute_note_hash_for_consumption-and-compute_note_hash_for_insertion "Direct link to introduce-compute_note_hash_for_consumption-and-compute_note_hash_for_insertion") Makes a split in logic for note hash computation for consumption and insertion. This is to avoid confusion between the two, and to make it clear that the note hash for consumption is different from the note hash for insertion (sometimes). `compute_note_hash_for_consumption` replaces `compute_note_hash_for_read_or_nullify`. `compute_note_hash_for_insertion` is new, and mainly used in \`lifecycle.nr\`\` ### `Note::serialize_content` and `Note::deserialize_content` added to \`NoteInterface[​](#noteserialize_content-and-notedeserialize_content-added-to-noteinterface "Direct link to noteserialize_content-and-notedeserialize_content-added-to-noteinterface") The `NoteInterface` have been extended to include `serialize_content` and `deserialize_content` functions. This is to convey the difference between serializing the full note, and just the content. This change allows you to also add a `serialize` function to support passing in a complete note to a function. Before: ``` impl Serialize for AddressNote { fn serialize(self) -> [Field; ADDRESS_NOTE_LEN]{ [self.address.to_field(), self.owner.to_field(), self.randomness] } } impl Deserialize for AddressNote { fn deserialize(serialized_note: [Field; ADDRESS_NOTE_LEN]) -> Self { AddressNote { address: AztecAddress::from_field(serialized_note[0]), owner: AztecAddress::from_field(serialized_note[1]), randomness: serialized_note[2], header: NoteHeader::empty(), } } ``` Now ``` impl NoteInterface for AddressNote { fn serialize_content(self) -> [Field; ADDRESS_NOTE_LEN]{ [self.address.to_field(), self.owner.to_field(), self.randomness] } fn deserialize_content(serialized_note: [Field; ADDRESS_NOTE_LEN]) -> Self { AddressNote { address: AztecAddress::from_field(serialized_note[0]), owner: AztecAddress::from_field(serialized_note[1]), randomness: serialized_note[2], header: NoteHeader::empty(), } } ... } ``` ### \[Aztec.nr] No storage.init() and `Serialize`, `Deserialize`, `NoteInterface` as Traits, removal of SerializationMethods and SERIALIZED\_LEN[​](#aztecnr-no-storageinit-and-serialize-deserialize-noteinterface-as-traits-removal-of-serializationmethods-and-serialized_len "Direct link to aztecnr-no-storageinit-and-serialize-deserialize-noteinterface-as-traits-removal-of-serializationmethods-and-serialized_len") Storage definition and initialization has been simplified. Previously: ``` struct Storage { leader: PublicState, legendary_card: Singleton, profiles: Map>, test: Set, imm_singleton: PrivateImmutable, } impl Storage { fn init(context: Context) -> Self { Storage { leader: PublicMutable::new( context, 1, LeaderSerializationMethods, ), legendary_card: PrivateMutable::new(context, 2, CardNoteMethods), profiles: Map::new( context, 3, |context, slot| { PrivateMutable::new(context, slot, CardNoteMethods) }, ), test: Set::new(context, 4, CardNoteMethods), imm_singleton: PrivateImmutable::new(context, 4, CardNoteMethods), } } } ``` Now: ``` struct Storage { leader: PublicMutable, legendary_card: Singleton, profiles: Map>, test: Set, imm_singleton: PrivateImmutable, } ``` For this to work, Notes must implement Serialize, Deserialize and NoteInterface Traits. Previously: ``` use dep::aztec::protocol::address::AztecAddress; use dep::aztec::{ note::{ note_header::NoteHeader, note_interface::NoteInterface, utils::compute_note_hash_for_read_or_nullify, }, oracle::{ nullifier_key::get_nullifier_secret_key, get_public_key::get_public_key, }, log::emit_encrypted_log, hash::pedersen_hash, context::PrivateContext, }; // Shows how to create a custom note global CARD_NOTE_LEN: Field = 1; impl CardNote { pub fn new(owner: AztecAddress) -> Self { CardNote { owner, } } pub fn serialize(self) -> [Field; CARD_NOTE_LEN] { [self.owner.to_field()] } pub fn deserialize(serialized_note: [Field; CARD_NOTE_LEN]) -> Self { CardNote { owner: AztecAddress::from_field(serialized_note[1]), } } pub fn compute_note_hash(self) -> Field { pedersen_hash([ self.owner.to_field(), ],0) } pub fn compute_nullifier(self, context: &mut PrivateContext) -> Field { let note_hash_for_nullify = compute_note_hash_for_read_or_nullify(CardNoteMethods, self); let secret = context.request_nullifier_secret_key(self.owner); pedersen_hash([ note_hash_for_nullify, secret.high, secret.low, ],0) } pub fn compute_nullifier_without_context(self) -> Field { let note_hash_for_nullify = compute_note_hash_for_read_or_nullify(CardNoteMethods, self); let secret = get_nullifier_secret_key(self.owner); pedersen_hash([ note_hash_for_nullify, secret.high, secret.low, ],0) } pub fn set_header(&mut self, header: NoteHeader) { self.header = header; } // Broadcasts the note as an encrypted log on L1. pub fn broadcast(self, context: &mut PrivateContext, slot: Field) { let encryption_pub_key = get_public_key(self.owner); emit_encrypted_log( context, (*context).this_address(), slot, encryption_pub_key, self.serialize(), ); } } fn deserialize(serialized_note: [Field; CARD_NOTE_LEN]) -> CardNote { CardNote::deserialize(serialized_note) } fn serialize(note: CardNote) -> [Field; CARD_NOTE_LEN] { note.serialize() } fn compute_note_hash(note: CardNote) -> Field { note.compute_note_hash() } fn compute_nullifier(note: CardNote, context: &mut PrivateContext) -> Field { note.compute_nullifier(context) } fn compute_nullifier_without_context(note: CardNote) -> Field { note.compute_nullifier_without_context() } fn get_header(note: CardNote) -> NoteHeader { note.header } fn set_header(note: &mut CardNote, header: NoteHeader) { note.set_header(header) } // Broadcasts the note as an encrypted log on L1. fn broadcast(context: &mut PrivateContext, slot: Field, note: CardNote) { note.broadcast(context, slot); } global CardNoteMethods = NoteInterface { deserialize, serialize, compute_note_hash, compute_nullifier, compute_nullifier_without_context, get_header, set_header, broadcast, }; ``` Now: ``` use dep::aztec::{ note::{ note_header::NoteHeader, note_interface::NoteInterface, utils::compute_note_hash_for_read_or_nullify, }, oracle::{ nullifier_key::get_nullifier_secret_key, get_public_key::get_public_key, }, log::emit_encrypted_log, hash::pedersen_hash, context::PrivateContext, protocol::{ address::AztecAddress, traits::{Serialize, Deserialize, Empty} } }; // Shows how to create a custom note global CARD_NOTE_LEN: Field = 1; impl CardNote { pub fn new(owner: AztecAddress) -> Self { CardNote { owner, } } } impl NoteInterface for CardNote { fn compute_note_content_hash(self) -> Field { pedersen_hash([ self.owner.to_field(), ],0) } fn compute_nullifier(self, context: &mut PrivateContext) -> Field { let note_hash_for_nullify = compute_note_hash_for_read_or_nullify(self); let secret = context.request_nullifier_secret_key(self.owner); pedersen_hash([ note_hash_for_nullify, secret.high, secret.low, ],0) } fn compute_nullifier_without_context(self) -> Field { let note_hash_for_nullify = compute_note_hash_for_read_or_nullify(self); let secret = get_nullifier_secret_key(self.owner); pedersen_hash([ note_hash_for_nullify, secret.high, secret.low, ],0) } fn set_header(&mut self, header: NoteHeader) { self.header = header; } fn get_header(note: CardNote) -> NoteHeader { note.header } fn serialize_content(self) -> [Field; CARD_NOTE_LEN]{ [self.owner.to_field()] } fn deserialize_content(serialized_note: [Field; CARD_NOTE_LEN]) -> Self { AddressNote { owner: AztecAddress::from_field(serialized_note[0]), header: NoteHeader::empty(), } } // Broadcasts the note as an encrypted log on L1. fn broadcast(self, context: &mut PrivateContext, slot: Field) { let encryption_pub_key = get_public_key(self.owner); emit_encrypted_log( context, (*context).this_address(), slot, encryption_pub_key, self.serialize(), ); } } ``` Public state must implement Serialize and Deserialize traits. It is still possible to manually implement the storage initialization (for custom storage wrappers or internal types that don't implement the required traits). For the above example, the `impl Storage` section would look like this: ``` impl Storage { fn init(context: Context) -> Self { Storage { leader: PublicMutable::new( context, 1 ), legendary_card: PrivateMutable::new(context, 2), profiles: Map::new( context, 3, |context, slot| { PrivateMutable::new(context, slot) }, ), test: Set::new(context, 4), imm_singleton: PrivateImmutable::new(context, 4), } } } ``` ## 0.20.0[​](#0200 "Direct link to 0.20.0") ### \[Aztec.nr] Changes to `NoteInterface`[​](#aztecnr-changes-to-noteinterface-2 "Direct link to aztecnr-changes-to-noteinterface-2") 1. Changing `compute_nullifier()` to `compute_nullifier(private_context: PrivateContext)` This API is invoked for nullifier generation within private functions. When using a secret key for nullifier creation, retrieve it through: `private_context.request_nullifier_secret_key(account_address)` The private context will generate a request for the kernel circuit to validate that the secret key does belong to the account. Before: ``` pub fn compute_nullifier(self) -> Field { let secret = oracle.get_secret_key(self.owner); pedersen_hash([ self.value, secret.low, secret.high, ]) } ``` Now: ``` pub fn compute_nullifier(self, context: &mut PrivateContext) -> Field { let secret = context.request_nullifier_secret_key(self.owner); pedersen_hash([ self.value, secret.low, secret.high, ]) } ``` 2. New API `compute_nullifier_without_context()`. This API is used within unconstrained functions where the private context is not available, and using an unverified nullifier key won't affect the network or other users. For example, it's used in `compute_note_hash_and_nullifier()` to compute values for the user's own notes. ``` pub fn compute_nullifier_without_context(self) -> Field { let secret = oracle.get_nullifier_secret_key(self.owner); pedersen_hash([ self.value, secret.low, secret.high, ]) } ``` > Note that the `get_secret_key` oracle API has been renamed to `get_nullifier_secret_key`. ## 0.18.0[​](#0180 "Direct link to 0.18.0") ### \[Aztec.nr] Remove `protocol` from Nargo.toml[​](#aztecnr-remove-protocol-from-nargotoml "Direct link to aztecnr-remove-protocol-from-nargotoml") The `protocol` package is now being reexported from `aztec`. It can be accessed through `dep::aztec::protocol`. ``` aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v4.3.1", directory="yarn-project/aztec-nr/aztec" } ``` ### \[Aztec.nr] key type definition in Map[​](#aztecnr-key-type-definition-in-map "Direct link to \[Aztec.nr] key type definition in Map") The `Map` class now requires defining the key type in its declaration which *must* implement the `ToField` trait. Before: ``` struct Storage { balances: Map> } let user_balance = balances.at(owner.to_field()) ``` Now: ``` struct Storage { balances: Map> } let user_balance = balances.at(owner) ``` ### \[js] Updated function names[​](#js-updated-function-names "Direct link to \[js] Updated function names") * `waitForSandbox` renamed to `waitForPXE` in `@aztec/aztec.js` * `getSandboxAccountsWallets` renamed to `getInitialTestAccountsWallets` in `@aztec/accounts/testing` ## 0.17.0[​](#0170 "Direct link to 0.17.0") ### \[js] New `@aztec/accounts` package[​](#js-new-aztecaccounts-package "Direct link to js-new-aztecaccounts-package") Before: ``` import { getSchnorrAccount } from "@aztec/aztec.js"; // previously you would get the default accounts from the `aztec.js` package: ``` Now, import them from the new package `@aztec/accounts` ``` import { getSchnorrAccount } from "@aztec/accounts"; ``` ### Typed Addresses[​](#typed-addresses "Direct link to Typed Addresses") Address fields in Aztec.nr now is of type `AztecAddress` as opposed to `Field` Before: ``` unconstrained fn compute_note_hash_and_nullifier(contract_address: Field, nonce: Field, storage_slot: Field, serialized_note: [Field; VALUE_NOTE_LEN]) -> [Field; 4] { let note_header = NoteHeader::new(_address, nonce, storage_slot); ... ``` Now: ``` unconstrained fn compute_note_hash_and_nullifier( contract_address: AztecAddress, nonce: Field, storage_slot: Field, serialized_note: [Field; VALUE_NOTE_LEN] ) -> pub [Field; 4] { let note_header = NoteHeader::new(contract_address, nonce, storage_slot); ``` Similarly, there are changes when using aztec.js to call functions. To parse a `AztecAddress` to BigInt, use `.inner` Before: ``` const tokenBigInt = await bridge.methods.token().simulate(); ``` Now: ``` const tokenBigInt = (await bridge.methods.token().simulate()).inner; ``` ### \[Aztec.nr] Add `protocol` to Nargo.toml[​](#aztecnr-add-protocol-to-nargotoml "Direct link to aztecnr-add-protocol-to-nargotoml") ``` aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v4.3.1", directory="yarn-project/aztec-nr/aztec" } protocol = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v4.3.1", directory="yarn-project/noir-protocol-circuits/crates/types"} ``` ### \[Aztec.nr] moving compute\_address func to AztecAddress[​](#aztecnr-moving-compute_address-func-to-aztecaddress "Direct link to \[Aztec.nr] moving compute_address func to AztecAddress") Before: ``` let calculated_address = compute_address(pub_key_x, pub_key_y, partial_address); ``` Now: ``` let calculated_address = AztecAddress::compute(pub_key_x, pub_key_y, partial_address); ``` ### \[Aztec.nr] moving `compute_selector` to FunctionSelector[​](#aztecnr-moving-compute_selector-to-functionselector "Direct link to aztecnr-moving-compute_selector-to-functionselector") Before: ``` let selector = compute_selector("_initialize((Field))"); ``` Now: ``` let selector = FunctionSelector::from_signature("_initialize((Field))"); ``` ### \[js] Importing contracts in JS[​](#js-importing-contracts-in-js-1 "Direct link to \[js] Importing contracts in JS") Contracts are now imported from a file with the type's name. Before: ``` import { TokenContract } from "@aztec/noir-contracts/types"; ``` Now: ``` import { TokenContract } from "@aztec/noir-contracts/Token"; ``` ### \[Aztec.nr] Aztec example contracts location change in Nargo.toml[​](#aztecnr-aztec-example-contracts-location-change-in-nargotoml "Direct link to \[Aztec.nr] Aztec example contracts location change in Nargo.toml") Aztec contracts are now moved outside of the `src` folder, so you need to update your imports. Before: ``` easy_private_token_contract = {git = "https://github.com/AztecProtocol/aztec-packages/", tag ="v0.16.9", directory = "noir-projects/noir-contracts/contracts/easy_private_token_contract"} ``` Now, just remove the `src` folder,: ``` easy_private_token_contract = {git = "https://github.com/AztecProtocol/aztec-packages/", tag ="v0.17.0", directory = "noir-projects/noir-contracts/contracts/easy_private_token_contract"} ``` --- # Video lessons Prefer watching to reading? These short explainers, presented by Ciara Nightingale from the Aztec team, each cover a core Aztec concept in just a few minutes. Written pages that go deeper are linked below each video. ## What is Aztec?[​](#what-is-aztec "Direct link to What is Aztec?") Aztec is a privacy-first Layer 2 on Ethereum: a zero-knowledge rollup where smart contracts can have both public and private state, and private execution happens locally on your own device. This video explains the core idea in under 90 seconds. [What is Aztec: Explained in Under 90 Seconds](https://www.youtube-nocookie.com/embed/urcBvo2QJp0) Related reading: [Aztec overview](/developers/overview.md), [foundational topics](/developers/docs/foundational-topics.md) ## Private and public state in one transaction[​](#private-and-public-state-in-one-transaction "Direct link to Private and public state in one transaction") A single Aztec transaction can span private and public execution. Using a private voting contract as the example, this video shows how private execution runs first on your device, producing a proof and side effects (nullifiers, note commitments, and enqueued public calls) that the sequencer then applies in public, keeping your vote private while the tally stays public. [One Transaction, Two Worlds: Private and Public State on Aztec](https://www.youtube-nocookie.com/embed/MayopgQ1FjI) Related reading: [transactions](/developers/docs/foundational-topics/transactions.md), [state management](/developers/docs/foundational-topics/state_management.md) ## What is private composability?[​](#what-is-private-composability "Direct link to What is private composability?") On Aztec, smart contracts can call each other privately. Because transactions execute and prove locally, not only the state but the call stack itself can stay private: nobody watching the chain learns which contract called which. This video explains how that lets you build on top of other apps permissionlessly, just like Ethereum, without leaking what you are doing. [What is Private Composability? An Aztec Explainer](https://www.youtube-nocookie.com/embed/idxRuGQnQKs) Related reading: [call types](/developers/docs/foundational-topics/call_types.md), [calling other contracts](/developers/docs/aztec-nr/framework-description/calling_contracts.md) ## How authorization works (authwits)[​](#how-authorization-works-authwits "Direct link to How authorization works (authwits)") Authentication witnesses (authwits) are Aztec's generalized alternative to Ethereum's approve and transferFrom pattern: they authorize a specific action for a specific caller, work in both private and public execution, and prevent replay. This lesson walks through the message hash structure, the private and public flows, and the `#[authorize_once]` macro. [How Authorization Works on Aztec](https://www.youtube-nocookie.com/embed/VRZVOCdjGZ4) Related reading: [authentication witness concepts](/developers/docs/foundational-topics/advanced/authwit.md), [using authwits in aztec.nr](/developers/docs/aztec-nr/framework-description/authentication_witnesses.md) ## Get started in under 60 seconds[​](#get-started-in-under-60-seconds "Direct link to Get started in under 60 seconds") Ready to build? This video walks through installing the Aztec tooling, creating a new contract project, compiling it, and deploying it to a local network, all in under a minute. [Get Started on Aztec in Under 60 Seconds](https://www.youtube-nocookie.com/embed/_jgHNdNgFOg) Related reading: [getting started on a local network](/developers/getting_started_on_local_network.md) ## More videos[​](#more-videos "Direct link to More videos") For a full-length course and more explainers, visit the [Aztec Network YouTube channel](https://www.youtube.com/@aztecnetwork). --- # Counter Contract In this guide, we will create our first Aztec.nr smart contract. We will build a simple private counter, where you can keep your own private counter - so no one knows what ID you are at or when you increment! This contract will get you started with the basic setup and syntax of Aztec.nr, but doesn't showcase all of the awesome stuff Aztec is capable of. This tutorial is compatible with the Aztec version `v4.3.1`. Install the correct version with `VERSION=4.3.1 bash -i <(curl -sL https://install.aztec.network/4.3.1)`. Or if you'd like to use a different version, you can find the relevant tutorial by clicking the version dropdown at the top of the page. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * You have followed the [quickstart](/developers/getting_started_on_local_network.md) * Running Aztec local network * Installed [Noir LSP](/developers/docs/aztec-nr/installation.md) (optional) ## Set up a project[​](#set-up-a-project "Direct link to Set up a project") Run this to create a new contract project: ``` aztec new counter ``` Your structure should look like this: ``` . |-counter | |-Nargo.toml <-- workspace root | |-counter_contract | | |-src | | | |-main.nr | | |-Nargo.toml <-- contract package config | |-counter_test | | |-src | | | |-lib.nr | | |-Nargo.toml <-- test package config ``` The `aztec new` command creates a workspace with two crates: a `counter_contract` crate for your smart contract code and a `counter_test` crate for Noir tests. The file `counter_contract/src/main.nr` will soon turn into our smart contract! Add the following dependency to `counter_contract/Nargo.toml` under the existing `aztec` dependency: ``` [dependencies] aztec = { git="https://github.com/AztecProtocol/aztec-nr/", tag="v4.3.1", directory="aztec" } balance_set = { git="https://github.com/AztecProtocol/aztec-nr/", tag="v4.3.1", directory="balance-set" } ``` ## Define the functions[​](#define-the-functions "Direct link to Define the functions") Go to `counter_contract/src/main.nr`, and replace the boilerplate code with this contract initialization: ``` use aztec::macros::aztec; #[aztec] pub contract Counter { } ``` This defines a contract called `Counter`. Clear the scaffold's placeholder test The scaffolded `counter_test/src/lib.nr` imports the default contract name (`Main`) we just replaced above, so it now fails to compile. Tests aren't used in this tutorial - replace its contents with a single-line stub so `aztec compile` stays clean: ``` // Tests are out of scope for this tutorial. See https://docs.aztec.network/developers/docs/aztec-nr/testing_contracts for examples. ``` ## Imports[​](#imports "Direct link to Imports") We need to define some imports. Write this inside your contract, ie inside these brackets: ``` pub contract Counter { // imports go here! } ``` imports ``` use aztec::{ macros::{functions::{external, initializer}, storage::storage}, messages::message_delivery::MessageDelivery, oracle::logging::debug_log_format, protocol::{address::AztecAddress, traits::ToField}, state_vars::Owned, }; use balance_set::BalanceSet; ``` > [Source code: docs/examples/contracts/counter\_contract/src/main.nr#L7-L16](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/counter_contract/src/main.nr#L7-L16) * `macros::{functions::{external, initializer}, storage::storage}` Imports the macros needed to define function types (`external`, `initializer`) and the `storage` macro for declaring contract storage structures. * `messages::message_delivery::MessageDelivery` Imports `MessageDelivery` for specifying how note delivery should be handled (e.g., constrained onchain delivery). * `oracle::logging::debug_log_format` Imports a debug logging utility for printing formatted messages during contract execution. * `protocol::{address::AztecAddress, traits::ToField}` Brings in `AztecAddress` (used to identify accounts/contracts) and traits for converting values to field elements, necessary for serialization and formatting inside Aztec. * `state_vars::Owned` Brings in `Owned`, a wrapper for state variables that have a single owner. * `use balance_set::BalanceSet` Imports `BalanceSet` from the `balance_set` dependency, which provides functionality for managing private balances (used for our counter). ## Declare storage[​](#declare-storage "Direct link to Declare storage") Add this below the imports. It declares the storage variables for our contract. We use an `Owned` state variable wrapping a `BalanceSet` to manage private balances for each owner. storage\_struct ``` #[storage] struct Storage { counters: Owned, Context>, } ``` > [Source code: docs/examples/contracts/counter\_contract/src/main.nr#L18-L23](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/counter_contract/src/main.nr#L18-L23) ## Keep the counter private[​](#keep-the-counter-private "Direct link to Keep the counter private") Now we’ve got a mechanism for storing our private state, we can start using it to ensure the privacy of balances. Let’s create a constructor method to run on deployment that assigns an initial count to a specified owner. This function is called `initialize`, but behaves like a constructor. It is the `#[initializer]` decorator that specifies that this function behaves like a constructor. Write this: constructor ``` #[initializer] #[external("private")] // We can name our initializer anything we want as long as it's marked as aztec(initializer) fn initialize(headstart: u128, owner: AztecAddress) { self.storage.counters.at(owner).add(headstart).deliver( MessageDelivery.ONCHAIN_CONSTRAINED, ); } ``` > [Source code: docs/examples/contracts/counter\_contract/src/main.nr#L25-L34](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/counter_contract/src/main.nr#L25-L34) This function accesses the counters from storage. It adds the `headstart` value to the `owner`'s counter using `at().add()`, then calls `.deliver(MessageDelivery.ONCHAIN_CONSTRAINED)` to ensure the note is delivered onchain. We have annotated this and other functions with `#[external("private")]` which are ABI macros so the compiler understands it will handle private inputs. ## Incrementing our counter[​](#incrementing-our-counter "Direct link to Incrementing our counter") Now let's implement an `increment` function to increase the counter. increment ``` #[external("private")] fn increment(owner: AztecAddress) { debug_log_format("Incrementing counter for owner {0}", [owner.to_field()]); self.storage.counters.at(owner).add(1).deliver(MessageDelivery.ONCHAIN_CONSTRAINED); } ``` > [Source code: docs/examples/contracts/counter\_contract/src/main.nr#L36-L42](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/counter_contract/src/main.nr#L36-L42) The `increment` function works similarly to the `initialize` function. It logs a debug message, then adds 1 to the owner's counter and delivers the note onchain. ## Getting a counter[​](#getting-a-counter "Direct link to Getting a counter") The last thing we need to implement is a function to retrieve a counter value. get\_counter ``` #[external("utility")] unconstrained fn get_counter(owner: AztecAddress) -> pub u128 { self.storage.counters.at(owner).balance_of() } ``` > [Source code: docs/examples/contracts/counter\_contract/src/main.nr#L44-L49](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/counter_contract/src/main.nr#L44-L49) This is a `utility` function used to obtain the counter value outside of a transaction. We access the `owner`'s balance from the `counters` storage variable using `at(owner)`, then call `balance_of()` to retrieve the current count. This yields a private counter that only the owner can decrypt. ## Compile[​](#compile "Direct link to Compile") Now we've written a simple Aztec.nr smart contract, we can compile it. ### Compile the smart contract[​](#compile-the-smart-contract "Direct link to Compile the smart contract") In the `./counter/` directory, run: ``` aztec compile ``` This command compiles your Noir contract and creates a `target` folder with a `.json` artifact inside. After compiling, you can generate a TypeScript class using the `aztec codegen` command. In the same directory, run this: ``` aztec codegen -o src/artifacts target ``` You can now use the artifact and/or the TS class in your Aztec.js! ## Next Steps[​](#next-steps "Direct link to Next Steps") ### Optional: Learn more about concepts mentioned here[​](#optional-learn-more-about-concepts-mentioned-here "Direct link to Optional: Learn more about concepts mentioned here") * [Functions and annotations like `#[external("private")]`](/developers/docs/aztec-nr/framework-description/functions/function_transforms.md#private-functions) --- # Verify Noir Proofs in Aztec Contracts ## Overview[​](#overview "Direct link to Overview") In this tutorial, you will build a system that generates zero-knowledge proofs offchain using a Noir circuit and verifies them onchain within an Aztec Protocol smart contract. You will create a simple circuit that proves two values are not equal, generate an UltraHonk proof, deploy an Aztec contract that stores a verification key hash, and submit the proof for onchain verification. This pattern enables trustless computation where anyone can verify that a computation was performed correctly without revealing the private inputs. Why "Recursive" Verification? This is called "recursive" verification because the proof is verified inside an Aztec private function, which itself gets compiled into a ZK circuit. The result is a proof being verified inside another proof. The Noir circuit you write is not recursive; the recursion happens at the Aztec protocol level when the private function execution (including the `verify_honk_proof` call) is proven. Full Working Example The complete code for this tutorial is available in the [docs/examples](https://github.com/AztecProtocol/aztec-packages/tree/v4.3.1/docs/examples) directory. Clone it to follow along or use it as a reference. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before starting, ensure you have the following installed and configured: * Node.js (v22 or later) * yarn package manager * Aztec CLI (version v4.3.1) * Nargo * Familiarity with [Noir syntax](https://noir-lang.org/docs) and [Aztec contract basics](/developers/docs/aztec-nr.md) Install the required tools: ``` # Install Aztec CLI VERSION=4.3.1 bash -i <(curl -sL https://install.aztec.network/4.3.1) ``` ## Part 1: Understanding the Architecture[​](#part-1-understanding-the-architecture "Direct link to Part 1: Understanding the Architecture") ### The Core Problem[​](#the-core-problem "Direct link to The Core Problem") Aztec contracts have inherent [limits on function inputs and transaction complexity](/developers/docs/resources/considerations/limitations.md#circuit-limitations). These constraints stem from the circuit-based nature of private execution. When your computation requires more inputs than these limits allow, or when the computation itself is too complex to fit in a single function, **recursive proof verification** provides an escape hatch. For example, consider a machine learning inference that needs 10,000 input features, or a Merkle tree verification with 1,000 leaves. These cannot fit within a single Aztec function's input constraints. Instead, you can: 1. Perform the computation offchain in a vanilla Noir circuit with no input limits 2. Generate a proof of correct execution 3. Verify only the proof onchain (115 fields for VK + 457-508 fields for proof + N public inputs) This pattern transforms arbitrarily large computations into fixed-size proof verification. ### Data Flow[​](#data-flow "Direct link to Data Flow") The recursive verification pattern follows this data flow: 1. **Circuit Definition**: Write a Noir circuit that defines the computation you want to prove 2. **Compilation**: Compile the circuit with `aztec-nargo compile` (or your own `nargo compile` install) to produce bytecode 3. **Proof Generation**: Execute the circuit offchain and generate an UltraHonk proof using [Barretenberg](https://github.com/AztecProtocol/barretenberg) 4. **Onchain Verification**: Submit the proof to an Aztec contract that verifies it using the stored [verification key](/developers/docs/resources/glossary.md#verification-key) hash **Why this separation matters**: The circuit defines *what* you're proving. The proof is *evidence* that you executed the circuit correctly with valid inputs. The onchain verifier checks the evidence without re-running the computation. This is what makes ZK proofs powerful: verification is orders of magnitude cheaper than computation. ### Why Verify Proofs in Aztec Contracts?[​](#why-verify-proofs-in-aztec-contracts "Direct link to Why Verify Proofs in Aztec Contracts?") Proof verification enables several patterns: * **Bypassing Input Limits**: Aztec private functions have strict input constraints. A proof verification call uses \~624 fields (115 VK + 508 proof + 1 public input), but can attest to computations with arbitrarily many inputs. For example, proving membership in a set of 10,000 elements becomes a fixed-size verification. * **Cross-System Verification**: Verify proofs generated by external Noir circuits within your Aztec application. This enables composability: your contract can trust computations performed by other systems without those systems needing to be Aztec-native. * **Batching Operations**: Aggregate multiple operations into a single proof. Instead of making N separate contract calls, prove all N operations were done correctly and verify once. ### Why Use Aztec for Proof Verification?[​](#why-use-aztec-for-proof-verification "Direct link to Why Use Aztec for Proof Verification?") Aztec provides a unique advantage: **private function execution**. When you verify a proof in an Aztec private function: 1. The proof verification happens inside a zero-knowledge circuit 2. The inputs to verification (the proof itself) can remain private 3. You can compose proof verification with other private operations This enables patterns impossible on transparent blockchains, like proving you have a valid credential without revealing which credential or when you obtained it. ### UX Considerations: Multiple Proof Generation[​](#ux-considerations-multiple-proof-generation "Direct link to UX Considerations: Multiple Proof Generation") When using [recursive verification](https://noir-lang.org/docs/noir/standard_library/recursion) in Aztec, users experience **two distinct proof generation phases**: 1. **Noir Proof Generation** (application-specific): * Happens before interacting with the Aztec contract * Proves the computation (e.g., "I know values x and y where x ≠ y") * Time depends on circuit complexity (seconds to minutes) * Produces the proof and verification key that will be verified 2. **Aztec Transaction Proof** (protocol-level): * Generated by the [PXE](/developers/docs/foundational-topics/pxe.md) when calling the private function * Proves correct execution of the Aztec contract (including the `verify_honk_proof` call) With this foundation in mind, let's build a complete example. You'll create a Noir circuit, generate a proof, and verify it inside an Aztec contract. ## Part 2: Writing the Noir Circuit[​](#part-2-writing-the-noir-circuit "Direct link to Part 2: Writing the Noir Circuit") Start by writing a simple circuit that proves two field values are not equal. This minimal example demonstrates the core pattern—you can extend it for more complex computations like Merkle proofs, credential verification, or something else entirely. ### Create the Circuit Project[​](#create-the-circuit-project "Direct link to Create the Circuit Project") Use `aztec-nargo new` to generate the project structure (the Aztec installer ships `nargo` as `aztec-nargo`; substitute your own `nargo` if its version matches `aztec-nargo --version`): ``` aztec-nargo new circuit ``` This creates the following structure: ``` circuit/ ├── src/ │ └── main.nr # Circuit code └── Nargo.toml # Circuit configuration ``` ### Circuit Code[​](#circuit-code "Direct link to Circuit Code") Replace the contents of `circuit/src/main.nr` with: circuit ``` fn main(x: u64, y: pub u64) { assert(x != y); } #[test] fn test_main() { main(1, 2); } ``` > [Source code: docs/examples/circuits/hello\_circuit/src/main.nr#L1-L10](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/circuits/hello_circuit/src/main.nr#L1-L10) This is intentionally minimal to focus on the verification pattern. In production, you would replace `assert(x != y)` with meaningful computations like: * Merkle tree membership proofs * Hash preimage verification * Range proofs (proving a value is within bounds) * Credential verification * Email verification (proving you received an email from a domain without revealing its contents, like [zkEmail](https://www.prove.email/)) ### Understanding Private vs Public Inputs[​](#understanding-private-vs-public-inputs "Direct link to Understanding Private vs Public Inputs") The circuit has two inputs with different visibility: * `x: Field` - A **private input** known only to the prover. This value is never revealed onchain or included in the proof data. The verifier cannot determine what value was used—only that *some* valid value exists. * `y: pub Field` - A **public input** that is visible to the verifier. This value is included in the proof data, but since proof verification happens within a private function, it isn't exposed onchain unless you explicitly reveal it. **Why this distinction matters**: The circuit asserts that `x != y`. The prover demonstrates they know a secret value `x` that differs from the public value `y`. Public inputs don't have to come from the caller. During verification, the Aztec contract can read values from its own storage and use them as public inputs. This pattern ties the proof to contract state—the prover must generate a proof against the *current* stored value and cannot substitute a different public input. To make the "public input" truly public, the contract developer can enqueue a public function call from the private function that verifies the proof, passing the public input to a public function to be logged or verified against public state. For example, you could create a zkpassport proof demonstrating that you are over a certain age. The proof is verified in a private function, then the age (the public input) is passed to a public function where it's compared against a mutable threshold in public storage. ### Circuit Configuration[​](#circuit-configuration "Direct link to Circuit Configuration") Update `circuit/Nargo.toml` (see [Noir crates and packages](https://noir-lang.org/docs/noir/modules_packages_crates/crates_and_packages) for more details): circuit\_nargo\_toml ``` [package] name = "hello_circuit" type = "bin" authors = [""] [dependencies] ``` > [Source code: docs/examples/circuits/hello\_circuit/Nargo.toml#L1-L8](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/circuits/hello_circuit/Nargo.toml#L1-L8) **Note**: This is a vanilla Noir circuit, not an Aztec contract. It has `type = "bin"` (binary) and no Aztec dependencies. The circuit is compiled with `nargo`, not `aztec compile`. This distinction is important—you can verify proofs from *any* Noir circuit inside Aztec contracts. ### Compile the Circuit[​](#compile-the-circuit "Direct link to Compile the Circuit") ``` cd circuit aztec-nargo compile ``` This generates `target/hello_circuit.json` containing: * **Bytecode**: The compiled circuit representation * **ABI (Application Binary Interface)**: Describes the circuit's inputs and outputs, including which are public The TypeScript code uses the ABI to correctly format inputs during witness generation. ### Test the Circuit[​](#test-the-circuit "Direct link to Test the Circuit") ``` aztec-nargo test ``` Expected output: ``` [hello_circuit] Running 1 test function [hello_circuit] Testing test_main ... ok [hello_circuit] 1 test passed ``` **Tip**: Circuit tests run without generating proofs, making them fast for development. Use them to verify your circuit logic before the more expensive proof generation step. ## Part 3: Writing the Aztec Contract[​](#part-3-writing-the-aztec-contract "Direct link to Part 3: Writing the Aztec Contract") The Aztec contract stores the verification key hash and verifies proofs submitted by users. When a valid proof is submitted, it increments a counter for the caller. ### Why This Contract Design?[​](#why-this-contract-design "Direct link to Why This Contract Design?") The contract demonstrates several important patterns: 1. **VK Hash Storage**: Instead of storing the full 115-field verification key onchain (expensive), we store only its hash (1 field). The prover submits the full VK with each proof, and the contract verifies it matches the stored hash. 2. **Private-to-Public Flow**: Proof verification happens in a [private function](/developers/docs/aztec-nr/framework-description/functions/visibility.md) (generating a ZK proof of the verification), but the counter update happens in a public function (visible state change). This separation is fundamental to Aztec's architecture. 3. **Self-Only Public Functions**: The `_increment_public` function can only be called by the contract itself, not external accounts (similar to `internal` functions in Solidity). This ensures the counter can only be modified after successful proof verification. ### Create the Contract Project[​](#create-the-contract-project "Direct link to Create the Contract Project") Use `aztec new` to generate the workspace: ``` aztec new ValueNotEqual ``` This creates a two-crate workspace under `ValueNotEqual/`: the contract crate is named `ValueNotEqual_contract` and the test crate is named `ValueNotEqual_test`, both derived from the positional argument. The Noir `contract` identifier declared inside `main.nr` is independent of the crate name and determines the compiled artifact filename. ``` ValueNotEqual/ ├── Nargo.toml # [workspace] members ├── ValueNotEqual_contract/ │ ├── src/ │ │ └── main.nr # Contract code │ └── Nargo.toml # Contract package (type = "contract") └── ValueNotEqual_test/ ├── src/ │ └── lib.nr # Noir tests └── Nargo.toml # Test package (type = "lib") ``` ### Contract Configuration[​](#contract-configuration "Direct link to Contract Configuration") Update `ValueNotEqual/ValueNotEqual_contract/Nargo.toml` with the required dependencies: ``` [package] name = "ValueNotEqual_contract" type = "contract" authors = ["[YOUR_NAME]"] [dependencies] aztec = { git = "https://github.com/AztecProtocol/aztec-nr/", tag = "v4.3.1", directory = "aztec" } bb_proof_verification = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v4.3.1", directory = "barretenberg/noir/bb_proof_verification" } ``` **Key differences from the circuit's Nargo.toml** (in `ValueNotEqual/ValueNotEqual_contract/Nargo.toml`): * `type = "contract"` (not `"bin"`) * Depends on `aztec` for Aztec-specific features * Depends on `bb_proof_verification` for `verify_honk_proof` ### Contract Structure[​](#contract-structure "Direct link to Contract Structure") Replace the contents of `ValueNotEqual/ValueNotEqual_contract/src/main.nr` with: full\_contract ``` use aztec::macros::aztec; #[aztec] pub contract ValueNotEqual { use aztec::{ macros::{functions::{external, initializer, only_self, view}, storage::storage}, oracle::logging::debug_log_format, protocol::{address::AztecAddress, traits::ToField}, state_vars::{Map, PublicImmutable, PublicMutable}, }; use bb_proof_verification::{UltraHonkVerificationKey, UltraHonkZKProof, verify_honk_proof}; #[storage] struct Storage { counters: Map, Context>, vk_hash: PublicImmutable, } #[initializer] #[external("public")] fn constructor(headstart: Field, owner: AztecAddress, vk_hash: Field) { self.storage.counters.at(owner).write(headstart); self.storage.vk_hash.initialize(vk_hash); } #[external("private")] fn increment( owner: AztecAddress, verification_key: UltraHonkVerificationKey, proof: UltraHonkZKProof, public_inputs: [Field; 1], ) { debug_log_format("Incrementing counter for owner {0}", [owner.to_field()]); // Read the stored VK hash - this is readable from private context // because PublicImmutable values are committed at deployment let vk_hash = self.storage.vk_hash.read(); // Verify the proof - this is the core operation // The function checks: // 1. The VK hashes to the stored vk_hash // 2. The proof is valid for the given VK and public inputs verify_honk_proof(verification_key, proof, public_inputs, vk_hash); // If we reach here, the proof is valid // Enqueue a public function call to update state self.enqueue_self._increment_public(owner); } #[only_self] #[external("public")] fn _increment_public(owner: AztecAddress) { let current = self.storage.counters.at(owner).read(); self.storage.counters.at(owner).write(current + 1); } #[view] #[external("public")] fn get_counter(owner: AztecAddress) -> Field { self.storage.counters.at(owner).read() } } ``` > [Source code: docs/examples/contracts/recursive\_verification\_contract/src/main.nr#L1-L64](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/recursive_verification_contract/src/main.nr#L1-L64) Clear the scaffold's placeholder test The scaffolded `ValueNotEqual/ValueNotEqual_test/src/lib.nr` imports the default contract name (`Main`) we just replaced above, so it now fails to compile. Tests aren't used in this tutorial — replace its contents with a single-line stub so `aztec compile` stays clean: ``` // Tests are out of scope for this tutorial. See https://docs.aztec.network/aztec-nr/testing_contracts for examples. ``` ### Storage Variables Explained[​](#storage-variables-explained "Direct link to Storage Variables Explained") The contract uses two [storage types](/developers/docs/aztec-nr/framework-description/state_variables.md) with different characteristics: **`vk_hash: PublicImmutable`** `PublicImmutable` is perfect for values that: * Are set once during contract initialization * Never change after deployment * Need to be readable from both public and private contexts The VK hash fits all these criteria. Once you deploy a contract to verify proofs from a specific circuit, the circuit (and thus its VK) shouldn't change. **Why store the hash instead of the full VK?** * Storage costs: 1 field vs 115 fields * The prover already has the full VK (needed to generate the proof) * Hash verification is cheap compared to storing/loading 115 fields **`counters: Map>`** `PublicMutable` is used for values that: * Change over time * Are updated by public functions * Need to be visible onchain The counter must be `PublicMutable` because it's modified by `_increment_public`, a public function. Private functions cannot directly write to public state; they can only enqueue public function calls. ### Function Breakdown[​](#function-breakdown "Direct link to Function Breakdown") **1. `constructor` (public initializer)** ``` #[initializer] #[external("public")] fn constructor(headstart: Field, owner: AztecAddress, vk_hash: Field) { self.storage.counters.at(owner).write(headstart); self.storage.vk_hash.initialize(vk_hash); } ``` * `#[initializer]`: Marks this as the constructor, called once during deployment * `#[external("public")]`: Executes publicly (visible onchain) * Sets the initial counter value for the owner * Stores the VK hash using `initialize()` (required for `PublicImmutable`) **2. `increment` (private function)** ``` #[external("private")] fn increment( owner: AztecAddress, verification_key: UltraHonkVerificationKey, proof: UltraHonkZKProof, public_inputs: [Field; 1], ) { let vk_hash = self.storage.vk_hash.read(); verify_honk_proof(verification_key, proof, public_inputs, vk_hash); self.enqueue_self._increment_public(owner); } ``` * `#[external("private")]`: Executes privately (generates a ZK proof of execution in the PXE) * Reads VK hash from storage (allowed because `PublicImmutable` is readable in private context) * Calls `verify_honk_proof()` which: * Computes the hash of the provided verification key * Checks it matches the stored `vk_hash` * Verifies the proof against the VK and public inputs * Fails (reverts) if any check fails * Uses `enqueue_self._increment_public(owner)` to schedule a public function call **Why `enqueue_self` instead of a direct call?** In Aztec, private functions cannot directly modify public state. Instead, they enqueue public function calls that execute after the private phase completes. This ensures: * Private execution remains private (no public state reads during private execution) * State updates are atomic (all enqueued calls execute or none do) * The execution order is deterministic **3. `_increment_public` (public, self-only)** ``` #[only_self] #[external("public")] fn _increment_public(owner: AztecAddress) { let current = self.storage.counters.at(owner).read(); self.storage.counters.at(owner).write(current + 1); } ``` * `#[only_self]`: Only callable by the contract itself (via `enqueue_self`) * `#[external("public")]`: Executes publicly * Reads the current counter and increments it **Why `#[only_self]`?** Without this modifier, anyone could call `_increment_public` directly, bypassing proof verification. The `#[only_self]` modifier ensures the function is only reachable through the private `increment` function, which requires a valid proof. **4. `get_counter` (public view)** ``` #[view] #[external("public")] fn get_counter(owner: AztecAddress) -> Field { self.storage.counters.at(owner).read() } ``` * `#[view]`: Read-only function, doesn't modify state * Returns the counter value for any address ## Part 4: TypeScript Setup and Proof Generation[​](#part-4-typescript-setup-and-proof-generation "Direct link to Part 4: TypeScript Setup and Proof Generation") Before compiling the contract or running any TypeScript scripts, set up the project with the necessary configuration files and dependencies. ### Project Setup[​](#project-setup "Direct link to Project Setup") Create the following files in your project root directory. **Create `package.json`:** ``` { "name": "recursive-verification-tutorial", "type": "module", "scripts": { "ccc": "cd ValueNotEqual && aztec compile && aztec codegen target -o ../artifacts", "data": "tsx scripts/generate_data.ts", "recursion": "tsx index.ts" }, "dependencies": { "@aztec/accounts": "4.3.1", "@aztec/aztec.js": "4.3.1", "@aztec/bb.js": "4.3.1", "@aztec/kv-store": "4.3.1", "@aztec/noir-contracts.js": "4.3.1", "@aztec/noir-noir_js": "4.3.1", "@aztec/pxe": "4.3.1", "@aztec/wallets": "4.3.1", "tsx": "^4.20.6" }, "devDependencies": { "@types/node": "^22.0.0" }, "peerDependencies": { "typescript": "^5.0.0" } } ``` **Create `tsconfig.json`:** ``` { "compilerOptions": { "lib": ["ESNext"], "target": "ESNext", "module": "ESNext", "moduleDetection": "force", "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "verbatimModuleSyntax": true, "noEmit": true, "strict": true, "skipLibCheck": true, "noFallthroughCasesInSwitch": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true } } ``` **Install dependencies:** ``` yarn install ``` This installs all the Aztec packages needed for proof generation and contract interaction. The installation may take a few minutes due to the size of the cryptographic libraries. ### Compile the Contract[​](#compile-the-contract "Direct link to Compile the Contract") Now compile the Aztec contract and generate TypeScript bindings: ``` yarn ccc ``` **What this command does** (see [How to Compile a Contract](/developers/docs/aztec-nr/compiling_contracts.md) for details): 1. `aztec compile`: Compiles the Noir contract and post-processes it for Aztec (different from `nargo compile`) 2. `aztec codegen`: Generates TypeScript bindings from the contract artifact, enabling type-safe contract interaction This generates: * `ValueNotEqual/target/ValueNotEqual_contract-ValueNotEqual.json` - Contract artifact (bytecode, ABI, etc.) * `artifacts/ValueNotEqual.ts` - TypeScript class for deploying and interacting with the contract ### Proof Generation Script[​](#proof-generation-script "Direct link to Proof Generation Script") The proof generation script executes the circuit offchain and produces the proof data needed for onchain verification. Create `scripts/generate_data.ts`: ``` import circuitJson from "../circuit/target/hello_circuit.json" with { type: "json" }; import { Noir } from "@aztec/noir-noir_js"; import { Barretenberg, UltraHonkBackend, deflattenFields } from "@aztec/bb.js"; import fs from "fs"; import { exit } from "process"; // Step 1: Initialize Barretenberg API (the proving system backend) // Barretenberg is the C++ library that implements UltraHonk // threads: 1 uses single-threaded mode (increase for faster proofs on multi-core machines) const barretenbergAPI = await Barretenberg.new({ threads: 1 }); // Step 2: Create Noir circuit instance from compiled bytecode // This loads the circuit definition so we can execute it const helloWorld = new Noir(circuitJson as any); // Step 3: Execute circuit with inputs to generate witness // The witness is all intermediate values computed during circuit execution // x=1 (private), y=2 (public) - proves that 1 != 2 const { witness: mainWitness } = await helloWorld.execute({ x: 1, y: 2 }); // Step 4: Create UltraHonk backend with circuit bytecode // The backend handles proof generation and verification const mainBackend = new UltraHonkBackend(circuitJson.bytecode, barretenbergAPI); // Step 5: Generate proof targeting the noir-recursive verifier // verifierTarget: 'noir-recursive' creates a proof format suitable for // verification inside another Noir circuit (which is what Aztec contracts are) const mainProofData = await mainBackend.generateProof(mainWitness, { verifierTarget: "noir-recursive", }); // Step 6: Verify proof locally before saving // This catches errors early - if verification fails here, it will fail onchain too const isValid = await mainBackend.verifyProof(mainProofData, { verifierTarget: "noir-recursive", }); console.log(`Proof verification: ${isValid ? "SUCCESS" : "FAILED"}`); // Step 7: Generate recursive artifacts for onchain use // This converts the proof and VK into field element arrays that can be // passed to the Aztec contract const recursiveArtifacts = await mainBackend.generateRecursiveProofArtifacts( mainProofData.proof, mainProofData.publicInputs.length, ); // Step 8: Convert proof to field elements if needed // Some versions return empty proofAsFields, requiring manual conversion let proofAsFields = recursiveArtifacts.proofAsFields; if (proofAsFields.length === 0) { console.log("Using deflattenFields to convert proof..."); proofAsFields = deflattenFields(mainProofData.proof).map((f) => f.toString()); } const vkAsFields = recursiveArtifacts.vkAsFields; console.log(`VK size: ${vkAsFields.length}`); // Should be 115 console.log(`Proof size: ${proofAsFields.length}`); // Should be ~500 console.log(`Public inputs: ${mainProofData.publicInputs.length}`); // Should be 1 // Step 9: Save all data to JSON for contract interaction const data = { vkAsFields: vkAsFields, // 115 field elements - the verification key vkHash: recursiveArtifacts.vkHash, // Hash of VK - stored in contract proofAsFields: proofAsFields, // ~500 field elements - the proof publicInputs: mainProofData.publicInputs.map((p: string) => p.toString()), }; fs.writeFileSync("data.json", JSON.stringify(data, null, 2)); await barretenbergAPI.destroy(); console.log("Done"); exit(); ``` ### Understanding the Proof Generation Pipeline[​](#understanding-the-proof-generation-pipeline "Direct link to Understanding the Proof Generation Pipeline") #### Setup[​](#setup "Direct link to Setup") * Initialize Barretenberg (the cryptographic backend) * Load the compiled circuit #### Witness Generation[​](#witness-generation "Direct link to Witness Generation") ``` const { witness: mainWitness } = await helloWorld.execute({ x: 1, y: 2 }); ``` The witness contains all values computed during circuit execution, not just inputs and outputs, but every intermediate value. The prover needs the witness to construct the proof. The verifier never sees the witness (that's the point of ZK proofs). #### Proof Generation[​](#proof-generation "Direct link to Proof Generation") ``` const mainProofData = await mainBackend.generateProof(mainWitness, { verifierTarget: "noir-recursive", }); ``` **Why `verifierTarget: 'noir-recursive'`?** There are different proof formats optimized for different verifiers: * Native verifiers (standalone programs) * Smart contract verifiers (Solidity) * Recursive verifiers (inside other ZK circuits) Aztec contracts are compiled to ZK circuits, so `verify_honk_proof` runs inside a circuit. We need the recursive-friendly proof format. #### Local Verification[​](#local-verification "Direct link to Local Verification") ``` const isValid = await mainBackend.verifyProof(mainProofData, { verifierTarget: "noir-recursive", }); ``` Always verify locally before submitting onchain. Onchain verification costs gas/fees and takes time. Local verification is free and instant. #### Field Element Conversion[​](#field-element-conversion "Direct link to Field Element Conversion") ZK proofs are arrays of bytes, but Aztec contracts work with field elements. We convert the proof and VK to arrays of 115 and 508 field elements respectively. ``` let proofAsFields = recursiveArtifacts.proofAsFields; if (proofAsFields.length === 0) { console.log("Using deflattenFields to convert proof..."); proofAsFields = deflattenFields(mainProofData.proof).map((f) => f.toString()); } const vkAsFields = recursiveArtifacts.vkAsFields; ``` Some versions of the library return an empty `proofAsFields` array, requiring manual conversion via `deflattenFields`. #### Saving Data for Contract Interaction[​](#saving-data-for-contract-interaction "Direct link to Saving Data for Contract Interaction") ``` const data = { vkAsFields: vkAsFields, vkHash: recursiveArtifacts.vkHash, proofAsFields: proofAsFields, publicInputs: mainProofData.publicInputs.map((p: string) => p.toString()), }; fs.writeFileSync("data.json", JSON.stringify(data, null, 2)); await barretenbergAPI.destroy(); ``` The data is saved as JSON so the deployment script can load it. We call `barretenbergAPI.destroy()` to clean up the WebAssembly resources used by Barretenberg. This is important because Barretenberg allocates significant memory for cryptographic operations, and not destroying it can cause memory leaks in long-running processes. ### Run Proof Generation[​](#run-proof-generation "Direct link to Run Proof Generation") ``` yarn data ``` Expected output: ``` Proof verification: SUCCESS Using deflattenFields to convert proof... VK size: 115 Proof size: 500 Public inputs: 1 Done ``` ### Output Format[​](#output-format "Direct link to Output Format") The generated `data.json` contains: ``` { "vkAsFields": ["0x...", "0x...", ...], // 115 field elements "vkHash": "0x...", // Single field element "proofAsFields": ["0x...", "0x...", ...], // 508 field elements "publicInputs": ["2"] // The public input y=2 } ``` **What each field is used for**: * `vkHash`: Passed to the contract constructor, stored permanently * `vkAsFields`: Passed to `increment()`, verified against stored hash * `proofAsFields`: Passed to `increment()`, verified by `verify_honk_proof` * `publicInputs`: Passed to `increment()`, must match what was used during proof generation ## Part 5: Deploying and Verifying[​](#part-5-deploying-and-verifying "Direct link to Part 5: Deploying and Verifying") The deployment script connects to the Aztec network, creates an account, deploys the contract, and submits a proof for verification. ### Deployment Script[​](#deployment-script "Direct link to Deployment Script") Create `index.ts`: run\_recursion ``` import { SponsoredFeePaymentMethod } from "@aztec/aztec.js/fee"; import type { FieldLike } from "@aztec/aztec.js/abi"; import { getSponsoredFPCInstance } from "./scripts/sponsored_fpc.js"; import { SponsoredFPCContract } from "@aztec/noir-contracts.js/SponsoredFPC"; import { ValueNotEqualContract } from "./artifacts/ValueNotEqual.js"; import { EmbeddedWallet } from "@aztec/wallets/embedded"; import { NO_FROM } from "@aztec/aztec.js/account"; import { Fr } from "@aztec/aztec.js/fields"; import fs from "node:fs"; import assert from "node:assert"; if (!fs.existsSync("data.json")) { console.error( "data.json not found. Run 'yarn data' first to generate proof data.", ); process.exit(1); } const data = JSON.parse(fs.readFileSync("data.json", "utf-8")); export const NODE_URL = process.env.AZTEC_NODE_URL ?? "http://localhost:8080"; // Setup sponsored fee payment - the FPC pays transaction fees for us const sponsoredFPC = await getSponsoredFPCInstance(); const sponsoredPaymentMethod = new SponsoredFeePaymentMethod( sponsoredFPC.address, ); // Initialize wallet and connect to local network // The wallet manages accounts and sends transactions through the PXE export const setupWallet = async (): Promise => { try { // Create wallet with embedded PXE // The wallet manages accounts and connects to the node let wallet = await EmbeddedWallet.create(NODE_URL); // Register the sponsored FPC so the wallet knows about it await wallet.registerContract(sponsoredFPC, SponsoredFPCContract.artifact); return wallet; } catch (error) { console.error("Failed to setup local network:", error); throw error; } }; async function main() { // Step 1: Setup wallet and create account // Accounts in Aztec are smart contracts (account abstraction) const wallet = await setupWallet(); const manager = await wallet.createSchnorrAccount(Fr.random(), Fr.random()); // Deploy the account contract const deployMethod = await manager.getDeployMethod(); await deployMethod.send({ from: NO_FROM, fee: { paymentMethod: sponsoredPaymentMethod }, }); const accounts = await wallet.getAccounts(); // Step 2: Deploy ValueNotEqual contract // Constructor args: initial counter (10), owner, VK hash const { contract: valueNotEqual } = await ValueNotEqualContract.deploy( wallet, 10, // Initial counter value accounts[0].item, // Owner address data.vkHash as unknown as FieldLike, // VK hash for verification ).send({ from: accounts[0].item, fee: { paymentMethod: sponsoredPaymentMethod }, }); console.log(`Contract deployed at: ${valueNotEqual.address}`); const opts = { from: accounts[0].item, fee: { paymentMethod: sponsoredPaymentMethod }, }; // Step 3: Read initial counter value // simulate() executes without submitting a transaction let counterValue = ( await valueNotEqual.methods .get_counter(accounts[0].item) .simulate({ from: accounts[0].item }) ).result; console.log(`Counter value: ${counterValue}`); // Should be 10 // Step 4: Call increment() with proof data // This creates a transaction that: // 1. Executes the private increment() function (client-side) // 2. Generates a ZK proof of correct execution // 3. Submits the proof to the network // 4. Network verifies the proof // 5. Executes enqueued _increment_public() const interaction = await valueNotEqual.methods.increment( accounts[0].item, data.vkAsFields as unknown as FieldLike[], // 115 field VK data.proofAsFields as unknown as FieldLike[], // 508 field proof data.publicInputs as unknown as FieldLike[], // Public inputs ); // Step 5: Send transaction and wait for inclusion await interaction.send(opts); // Step 6: Read updated counter counterValue = ( await valueNotEqual.methods .get_counter(accounts[0].item) .simulate({ from: accounts[0].item }) ).result; console.log(`Counter value: ${counterValue}`); // Should be 11 assert(counterValue === 11n, "Counter should be 11 after verification"); } main().catch((error) => { console.error(error); process.exit(1); }); ``` > [Source code: docs/examples/ts/recursive\_verification/index.ts#L1-L121](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/recursive_verification/index.ts#L1-L121) ### Understanding the Deployment Script[​](#understanding-the-deployment-script "Direct link to Understanding the Deployment Script") #### Sponsored Fee Payment[​](#sponsored-fee-payment "Direct link to Sponsored Fee Payment") Aztec transactions require fees. For testing, we use a Sponsored Fee Payment Contract (FPC) that pays fees on behalf of users: ``` const sponsoredFPC = await getSponsoredFPCInstance(); const sponsoredPaymentMethod = new SponsoredFeePaymentMethod( sponsoredFPC.address, ); ``` In production, you would use real [fee payment methods](/developers/docs/aztec-js/how_to_pay_fees.md) (native tokens, ERC20, etc.). #### What Happens During `increment().send().wait()`[​](#what-happens-during-incrementsendwait "Direct link to what-happens-during-incrementsendwait") This single line triggers a complex flow: 1. **Private Execution** (client-side, in PXE): * Execute `increment()` with provided arguments * Read `vk_hash` from contract storage * Execute `verify_honk_proof()` inside the private function * Generate the `enqueue_self._increment_public(owner)` call 2. **Proof Generation** (client-side, in PXE): * Generate a ZK proof that the private execution was correct * This proof doesn't reveal inputs (including the 508-field proof!) 3. **Transaction Submission**: * Send the proof + encrypted logs + public function calls to the network 4. **Verification & Public Execution** (onchain): * Network verifies the private execution proof * Execute `_increment_public(owner)` publicly * Update the counter in storage ### Supporting Utility[​](#supporting-utility "Direct link to Supporting Utility") Create `scripts/sponsored_fpc.ts`: sponsored\_fpc ``` import { getContractInstanceFromInstantiationParams } from "@aztec/aztec.js/contracts"; import { Fr } from "@aztec/aztec.js/fields"; import { SponsoredFPCContract } from "@aztec/noir-contracts.js/SponsoredFPC"; const SPONSORED_FPC_SALT = new Fr(BigInt(0)); export async function getSponsoredFPCInstance() { return await getContractInstanceFromInstantiationParams( SponsoredFPCContract.artifact, { salt: SPONSORED_FPC_SALT, }, ); } ``` > [Source code: docs/examples/ts/recursive\_verification/scripts/sponsored\_fpc.ts#L1-L16](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/recursive_verification/scripts/sponsored_fpc.ts#L1-L16) This utility computes the address of the pre-deployed sponsored FPC contract. The salt ensures we get the same address every time. For more information about fee payment options, see [Paying Fees](/developers/docs/aztec-js/how_to_pay_fees.md). ### Start the Local Network[​](#start-the-local-network "Direct link to Start the Local Network") In a separate terminal, start the [Aztec local network](/developers/getting_started_on_local_network.md): ``` aztec start --local-network ``` **What this starts**: * **Anvil**: A local Ethereum node (L1) * **Aztec Node**: The L2 rollup node * **PXE**: Private eXecution Environment (embedded in node for local development) Wait for the network to fully initialize. You should see logs indicating readiness. The PXE will be available at `http://localhost:8080`. ### Deploy and Verify[​](#deploy-and-verify "Direct link to Deploy and Verify") Run the deployment script: ``` yarn recursion ``` Expected output: ``` Contract deployed at: 0x... Counter value: 10 Counter value: 11 ``` The counter starts at 10 (set during deployment), and after successful proof verification, it increments to 11. This confirms that the Noir proof was verified inside the Aztec contract. ## Quick Reference[​](#quick-reference "Direct link to Quick Reference") If you want to run all commands at once, or if you're starting fresh, here's the complete workflow. You can also reference the [full working example](https://github.com/AztecProtocol/aztec-packages/tree/v4.3.1/docs/examples) in the main repository. ``` # Install dependencies (after creating package.json and tsconfig.json) yarn install # Compile the Noir circuit cd circuit && aztec-nargo compile && cd .. # Compile the Aztec contract and generate TypeScript bindings yarn ccc # Generate proof data yarn data # Start the local network (in a separate terminal) aztec start --local-network # Deploy and verify yarn recursion ``` ## Next Steps[​](#next-steps "Direct link to Next Steps") Now that you understand the basics of proof verification in Aztec contracts, explore these topics: * **Simpler Contract Examples**: If you're new to Aztec contracts, the [Counter Tutorial](/developers/docs/tutorials/contract_tutorials/counter_contract.md) provides a gentler introduction to contract development patterns. * **Multiple Public Inputs**: Extend the circuit to have multiple public inputs. Update `public_inputs: [Field; 1]` in the contract to match. * **Noir Language Reference**: Explore advanced Noir features like loops, arrays, and standard library functions at [noir-lang.org](https://noir-lang.org/docs). --- # Private Token Contract ## The Privacy Challenge: Mental Health Benefits at Giggle[​](#the-privacy-challenge-mental-health-benefits-at-giggle "Direct link to The Privacy Challenge: Mental Health Benefits at Giggle") Giggle (a fictional tech company) wants to support their employees' mental health by providing BOB tokens that can be spent at Bob's Psychology Clinic. However, employees have a crucial requirement: **complete privacy**. They don't want Giggle to know: * How many BOB tokens they've actually used * When they're using mental health services * Their therapy patterns or frequency In this tutorial, we'll build a token contract that allows Giggle to mint BOB tokens for employees while ensuring complete privacy in how those tokens are spent. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") This is an intermediate tutorial that assumes you have: * Completed the [Counter Contract tutorial](/developers/docs/tutorials/contract_tutorials/counter_contract.md) * A Running Aztec local network (see the Counter tutorial for setup) * Basic understanding of Aztec.nr syntax and structure * Aztec toolchain installed (`VERSION=4.3.1 bash -i <(curl -sL https://install.aztec.network/4.3.1)`) If you haven't completed the Counter Contract tutorial, please do so first as we'll skip the basic setup steps covered there. ## What We're Building[​](#what-were-building "Direct link to What We're Building") We'll create BOB tokens with: * **Public and Private minting**: Giggle can mint tokens in private or public * **Public and Private transfers**: Employees can spend tokens at Bob's clinic with full privacy ### Project Setup[​](#project-setup "Direct link to Project Setup") Let's create a simple yarn + aztec.nr project: ``` aztec new bob_token cd bob_token yarn init -y # This is to ensure yarn uses node_modules instead of pnp for dependency installation yarn config set nodeLinker node-modules yarn add @aztec/aztec.js@v4.3.1 @aztec/accounts@v4.3.1 @aztec/kv-store@v4.3.1 @aztec/wallets@v4.3.1 ``` ## Contract structure[​](#contract-structure "Direct link to Contract structure") The `aztec new` command created a workspace with two crates: a `bob_token_contract` crate for your smart contract code and a `bob_token_test` crate for Noir tests. In `bob_token_contract/src/main.nr` we even have a proto-contract. Let's replace it with a simple starting point: ``` use aztec::macros::aztec; #[aztec] pub contract BobToken { // We'll build the mental health token here } ``` Clear the scaffold's placeholder test The scaffolded `bob_token_test/src/lib.nr` imports the default contract name (`Main`) we just replaced above, so it now fails to compile. Tests aren't used in this tutorial — replace its contents with a single-line stub so `aztec compile` stays clean: ``` // Tests are out of scope for this tutorial. See https://docs.aztec.network/aztec-nr/testing_contracts for examples. ``` The `#[aztec]` macro transforms our contract code to work with Aztec's privacy protocol. Let's make sure the Aztec.nr library is listed in our dependencies in `bob_token_contract/Nargo.toml`: ``` [package] name = "bob_token_contract" type = "contract" [dependencies] aztec = { git = "https://github.com/AztecProtocol/aztec-nr/", tag = "v4.3.1", directory = "aztec" } ``` Since we're here, let's import more specific stuff from this library: ``` #[aztec] pub contract BobToken { use aztec::{ macros::{functions::{external, initializer, only_self}, storage::storage}, messages::message_delivery::MessageDelivery, protocol::address::AztecAddress, state_vars::{Map, Owned, PublicMutable}, }; } ``` These are the different macros we need to define the visibility of functions, and some handy types and functions. note You may see "unused import" warnings from your IDE or compiler for `only_self`, `MessageDelivery`, and `Owned`. That's expected at this stage — we'll start using them in Part 2 when we add the private half of the contract. ## Building the Mental Health Token System[​](#building-the-mental-health-token-system "Direct link to Building the Mental Health Token System") ### The Privacy Architecture[​](#the-privacy-architecture "Direct link to The Privacy Architecture") Before we start coding, let's understand how privacy works in our mental health token system: 1. **Public Layer**: Giggle mints tokens publicly - transparent and auditable 2. **Private Layer**: Employees transfer and spend tokens privately - completely confidential 3. **Cross-layer Transfer**: Employees can move tokens between public and private domains as needed This architecture ensures that while the initial allocation is transparent (important for corporate governance), the actual usage remains completely private. Privacy Note In Aztec, private state uses a UTXO model with "notes" - think of them as encrypted receipts that only the owner can decrypt and spend. When an employee receives BOB tokens privately, they get encrypted notes that only they can see and use. Let's start building! Remember to import types as needed - your IDE's Noir extension can help with auto-imports. ## Part 1: Public Minting for Transparency[​](#part-1-public-minting-for-transparency "Direct link to Part 1: Public Minting for Transparency") Let's start with the public components that Giggle will use to mint and track initial token allocations. ### Setting Up Storage[​](#setting-up-storage "Direct link to Setting Up Storage") First, define the storage for our BOB tokens: ``` #[storage] struct Storage { // Giggle's admin address owner: PublicMutable, // Public balances - visible for transparency public_balances: Map, Context>, } ``` This storage structure allows: * `owner`: Stores Giggle's admin address (who can mint tokens) * `public_balances`: Tracks public token balances (employees can verify their allocations) Why Public Balances? While employees want privacy when spending, having public balances during minting allows: 1. Employees to verify they received their mental health benefits 2. Auditors to confirm fair distribution 3. Transparency in the allocation process ### Initializing Giggle as Owner[​](#initializing-giggle-as-owner "Direct link to Initializing Giggle as Owner") When deploying the contract, we need to set Giggle as the owner: setup ``` #[initializer] #[external("public")] fn setup() { // Giggle becomes the owner who can mint mental health tokens self.storage.owner.write(self.msg_sender()); } ``` > [Source code: docs/examples/contracts/bob\_token\_contract/src/main.nr#L32-L39](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/bob_token_contract/src/main.nr#L32-L39) The `#[initializer]` decorator ensures this runs once during deployment. Only Giggle's address will have the power to mint new BOB tokens for employees. ### Minting BOB Tokens for Employees[​](#minting-bob-tokens-for-employees "Direct link to Minting BOB Tokens for Employees") Giggle needs a way to allocate mental health tokens to employees: mint\_public ``` #[external("public")] fn mint_public(employee: AztecAddress, amount: u64) { // Only Giggle can mint tokens assert_eq(self.msg_sender(), self.storage.owner.read(), "Only Giggle can mint BOB tokens"); // Add tokens to employee's public balance let current_balance = self.storage.public_balances.at(employee).read(); self.storage.public_balances.at(employee).write(current_balance + amount); } ``` > [Source code: docs/examples/contracts/bob\_token\_contract/src/main.nr#L41-L51](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/bob_token_contract/src/main.nr#L41-L51) This public minting function: 1. Verifies that only Giggle (the owner) is calling 2. Transparently adds tokens to the employee's public balance 3. Creates an auditable record of the allocation Real-World Scenario Imagine Giggle allocating 100 BOB tokens to each employee at the start of the year. This public minting ensures employees can verify they received their benefits, while their actual usage remains private. ### Public Transfers (Optional Transparency)[​](#public-transfers-optional-transparency "Direct link to Public Transfers (Optional Transparency)") While most transfers will be private, we'll add public transfers for cases where transparency is desired: transfer\_public ``` #[external("public")] fn transfer_public(to: AztecAddress, amount: u64) { let sender = self.msg_sender(); let sender_balance = self.storage.public_balances.at(sender).read(); assert(sender_balance >= amount, "Insufficient BOB tokens"); // Deduct from sender self.storage.public_balances.at(sender).write(sender_balance - amount); // Add to recipient let recipient_balance = self.storage.public_balances.at(to).read(); self.storage.public_balances.at(to).write(recipient_balance + amount); } ``` > [Source code: docs/examples/contracts/bob\_token\_contract/src/main.nr#L53-L67](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/bob_token_contract/src/main.nr#L53-L67) This might be used when: * An employee transfers tokens to a colleague who's comfortable with transparency * Bob's clinic makes a public refund * Any scenario where privacy isn't required ### Admin Transfer (Future-Proofing)[​](#admin-transfer-future-proofing "Direct link to Admin Transfer (Future-Proofing)") In case Giggle's mental health program administration changes: transfer\_ownership ``` #[external("public")] fn transfer_ownership(new_owner: AztecAddress) { assert_eq( self.msg_sender(), self.storage.owner.read(), "Only current admin can transfer ownership", ); self.storage.owner.write(new_owner); } ``` > [Source code: docs/examples/contracts/bob\_token\_contract/src/main.nr#L69-L79](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/bob_token_contract/src/main.nr#L69-L79) ## Your First Deployment - Let's See It Work[​](#your-first-deployment---lets-see-it-work "Direct link to Your First Deployment - Let's See It Work") ### Compile Your Contract[​](#compile-your-contract "Direct link to Compile Your Contract") You've written enough code to have a working token! Let's compile and test it: ``` aztec compile ``` ### Generate TypeScript Interface[​](#generate-typescript-interface "Direct link to Generate TypeScript Interface") ``` aztec codegen target --outdir artifacts ``` You should now have a nice typescript interface in a new `artifacts` folder. Pretty useful! ### Deploy and Test[​](#deploy-and-test "Direct link to Deploy and Test") Create `index.ts`. We will connect to our running local network and its wallet, then deploy the test accounts and get three wallets out of it. Ensure that your local network is running: ``` aztec start --local-network ``` Then we will use the `giggleWallet` to deploy our contract, mint 100 BOB to Alice, then transfer 10 of those to Bob's Clinic publicly... for now. Let's go: ``` import { BobTokenContract } from "./artifacts/BobToken.js"; import { AztecAddress } from "@aztec/aztec.js/addresses"; import { createAztecNodeClient } from "@aztec/aztec.js/node"; import { getInitialTestAccountsData } from "@aztec/accounts/testing"; import { EmbeddedWallet } from "@aztec/wallets/embedded"; async function main() { // Connect to local network const node = createAztecNodeClient("http://localhost:8080"); // `ephemeral: true` keeps PXE state in memory, so restarting the local // network won't leave this script pointing at stale block hashes. const wallet = await EmbeddedWallet.create(node, { ephemeral: true }); const [giggleWalletData, aliceWalletData, bobClinicWalletData] = await getInitialTestAccountsData(); const giggleAccountManager = await wallet.createSchnorrAccount( giggleWalletData.secret, giggleWalletData.salt, ); const aliceAccountManager = await wallet.createSchnorrAccount( aliceWalletData.secret, aliceWalletData.salt, ); const bobClinicAccountManager = await wallet.createSchnorrAccount( bobClinicWalletData.secret, bobClinicWalletData.salt, ); const giggleAddress = giggleAccountManager.address; const aliceAddress = aliceAccountManager.address; const bobClinicAddress = bobClinicAccountManager.address; const { contract: bobToken } = await BobTokenContract.deploy(wallet).send({ from: giggleAddress, }); await bobToken.methods .mint_public(aliceAddress, 100n) .send({ from: giggleAddress }); await bobToken.methods .transfer_public(bobClinicAddress, 10n) .send({ from: aliceAddress }); } main().catch(console.error); ``` Run your test: ``` npx tsx index.ts ``` tip What's this `tsx` dark magic? `tsx` is a tool that compiles and runs TypeScript using reasonable defaults. `npx` will auto-install it if you don't have it. If you'd prefer to install it explicitly, run `yarn add -D tsx` first. Ephemeral PXE state We pass `{ ephemeral: true }` to `EmbeddedWallet.create`. This tells the PXE to keep its state in memory instead of writing it to `pxe_data_*` / `wallet_data_*` folders on disk. If you ever stop and restart your local network (or wipe its state), the next run starts clean instead of failing with errors like `No local block hash for block number …` because on-disk PXE state no longer matches the chain. For real applications you typically want persistent state, but for tutorials that spin up a fresh network each run, ephemeral is the safer default. ### 🎉 Celebrate[​](#-celebrate "Direct link to 🎉 Celebrate") Congratulations! You've just deployed a working token contract on Aztec! You can: * ✅ Mint BOB tokens as Giggle * ✅ Transfer tokens between employees * ✅ Track balances publicly But there's a problem... **Giggle can see everything!** They know: * Who's transferring tokens * How much is being spent * When mental health services are being used This defeats the whole purpose of our mental health privacy initiative. Let's fix this by adding private functionality! ## Part 2: Adding Privacy - The Real Magic Begins[​](#part-2-adding-privacy---the-real-magic-begins "Direct link to Part 2: Adding Privacy - The Real Magic Begins") Now let's add the privacy features that make our mental health benefits truly confidential. ### Understanding Private Notes[​](#understanding-private-notes "Direct link to Understanding Private Notes") Here's where Aztec's privacy magic happens. Unlike public balances (a single number), private balances are collections of encrypted "notes". Think of it this way: * **Public balance**: "Alice has 100 BOB tokens" (visible to everyone) * **Private balance**: Alice has encrypted notes \[Note1: 30 BOB, Note2: 50 BOB, Note3: 20 BOB] that only she can decrypt When Alice spends 40 BOB tokens at Bob's clinic: 1. She consumes Note1 (30 BOB) and Note2 (50 BOB) = 80 BOB total 2. She creates a new note for Bob's clinic (40 BOB) 3. She creates a "change" note for herself (40 BOB) 4. The consumed notes are nullified (marked as spent) What is a nullifier? A **nullifier** is a unique, one-way tag emitted when a private note is spent. The network adds it to a nullifier tree so the same note can't be spent twice, but because the nullifier is derived from secrets only the note's owner knows, nobody can link a nullifier back to the note it invalidated. See [State Management](/developers/docs/foundational-topics/state_management.md#private-state) for more. In this case, all that the network sees (including Giggle) is just "something happening to some state in some contract". How cool is that? ### Updating Storage for Privacy[​](#updating-storage-for-privacy "Direct link to Updating Storage for Privacy") For something like balances, you can use a simple library called `balance_set` which abstracts away a custom private Note. A Note is at the core of how private state works in Aztec and you can read about it [here](/developers/docs/foundational-topics/state_management.md). For now, let's add it by replacing the `[dependencies]` section in `Nargo.toml`: ``` [dependencies] aztec = { git="https://github.com/AztecProtocol/aztec-nr", tag="v4.3.1", directory="aztec" } balance_set = { git = "https://github.com/AztecProtocol/aztec-nr/", tag = "v4.3.1", directory = "balance-set" } ``` Then import `BalanceSet` in our contract: ``` use aztec::macros::aztec; #[aztec] pub contract BobToken { // ... other imports use balance_set::BalanceSet; // ... } ``` We need to update the contract storage to have private balances as well: storage ``` #[storage] struct Storage { // Giggle's admin address owner: PublicMutable, // Public balances - visible for transparency public_balances: Map, Context>, // Private balances - only the owner can see these private_balances: Owned, Context>, } ``` > [Source code: docs/examples/contracts/bob\_token\_contract/src/main.nr#L19-L30](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/bob_token_contract/src/main.nr#L19-L30) The `private_balances` use `BalanceSet` which manages encrypted notes automatically. ### Moving Tokens to Privateland[​](#moving-tokens-to-privateland "Direct link to Moving Tokens to Privateland") Great, now our contract knows about private balances. Let's implement a method to allow users to move their publicly minted tokens there: public\_to\_private ``` #[external("private")] fn public_to_private(amount: u64) { let sender = self.msg_sender(); // This will enqueue a public function to deduct from public balance self.enqueue_self._deduct_public_balance(sender, amount); // Add to private balance self.storage.private_balances.at(sender).add(amount as u128).deliver( MessageDelivery.ONCHAIN_CONSTRAINED, ); } ``` > [Source code: docs/examples/contracts/bob\_token\_contract/src/main.nr#L81-L92](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/bob_token_contract/src/main.nr#L81-L92) And the helper function: \_deduct\_public\_balance ``` #[external("public")] #[only_self] fn _deduct_public_balance(owner: AztecAddress, amount: u64) { let balance = self.storage.public_balances.at(owner).read(); assert(balance >= amount, "Insufficient public BOB tokens"); self.storage.public_balances.at(owner).write(balance - amount); } ``` > [Source code: docs/examples/contracts/bob\_token\_contract/src/main.nr#L94-L102](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/bob_token_contract/src/main.nr#L94-L102) By calling `public_to_private` we're telling the network "deduct this amount from my balance" while simultaneously creating a Note with that balance in privateland. ### Private Transfers[​](#private-transfers "Direct link to Private Transfers") Now for the crucial privacy feature - transferring BOB tokens in privacy. This is actually pretty simple: transfer\_private ``` #[external("private")] fn transfer_private(to: AztecAddress, amount: u64) { let sender = self.msg_sender(); // Spend sender's notes (consumes existing notes) self.storage.private_balances.at(sender).sub(amount as u128).deliver( MessageDelivery.ONCHAIN_CONSTRAINED, ); // Create new notes for recipient self.storage.private_balances.at(to).add(amount as u128).deliver( MessageDelivery.ONCHAIN_CONSTRAINED, ); } ``` > [Source code: docs/examples/contracts/bob\_token\_contract/src/main.nr#L104-L117](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/bob_token_contract/src/main.nr#L104-L117) This function simply nullifies the sender's notes, while adding them to the recipient. Real-World Impact When an employee uses 50 BOB tokens at Bob's clinic, this private transfer ensures Giggle has no visibility into: * The fact that the employee is seeking mental health services * The frequency of visits * The amount spent on treatment ### Checking Balances[​](#checking-balances "Direct link to Checking Balances") Employees can check their BOB token balances without hitting the network by using utility unconstrained functions: check\_balances ``` #[external("utility")] unconstrained fn private_balance_of(owner: AztecAddress) -> pub u128 { self.storage.private_balances.at(owner).balance_of() } #[external("utility")] unconstrained fn public_balance_of(owner: AztecAddress) -> pub u64 { self.storage.public_balances.at(owner).read() } ``` > [Source code: docs/examples/contracts/bob\_token\_contract/src/main.nr#L119-L129](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/bob_token_contract/src/main.nr#L119-L129) ## Part 3: Securing Private Minting[​](#part-3-securing-private-minting "Direct link to Part 3: Securing Private Minting") Let's make this a little bit harder, and more interesting. Let's say Giggle doesn't want to mint the tokens in public. Can we have private minting on Aztec? Sure we can. Let's see. ### Understanding Execution Domains[​](#understanding-execution-domains "Direct link to Understanding Execution Domains") Our BOB token system operates in two domains: 1. **Public Domain**: Where Giggle mints tokens transparently 2. **Private Domain**: Where employees spend tokens confidentially The key challenge: How do we ensure only Giggle can mint tokens when the minting happens in a private function? Privacy Trade-off Private functions can't directly read current public state (like who the owner is). They can only read historical public state or enqueue public function calls for validation. ### The Access Control Challenge[​](#the-access-control-challenge "Direct link to The Access Control Challenge") We want Giggle to mint BOB tokens directly to employees' private balances (for maximum privacy), but we need to ensure only Giggle can do this. The challenge: ownership is stored publicly, but private functions can't read current public state. Let's use a clever pattern where private functions enqueue public validation checks. First we make a little helper function in public. Remember, public functions always run *after* private functions, since private functions run client-side. \_assert\_is\_owner ``` #[external("public")] #[only_self] fn _assert_is_owner(address: AztecAddress) { assert_eq(address, self.storage.owner.read(), "Only Giggle can mint BOB tokens"); } ``` > [Source code: docs/examples/contracts/bob\_token\_contract/src/main.nr#L131-L137](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/bob_token_contract/src/main.nr#L131-L137) Now we can add a secure private minting function. It looks pretty easy, and it is, since the whole thing will revert if the public function fails: mint\_private ``` #[external("private")] fn mint_private(employee: AztecAddress, amount: u64) { // Enqueue ownership check (will revert if not Giggle) self.enqueue_self._assert_is_owner(self.msg_sender()); // If check passes, mint tokens privately self.storage.private_balances.at(employee).add(amount as u128).deliver( MessageDelivery.ONCHAIN_CONSTRAINED, ); } ``` > [Source code: docs/examples/contracts/bob\_token\_contract/src/main.nr#L139-L150](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/bob_token_contract/src/main.nr#L139-L150) This pattern ensures: 1. The private minting executes first (creating the proof) 2. The public ownership check executes after 3. If the check fails, the entire transaction (including the private part) reverts 4. Only Giggle can successfully mint BOB tokens ## Part 4: Converting Back to Public[​](#part-4-converting-back-to-public "Direct link to Part 4: Converting Back to Public") For the sake of completeness, let's also have a function that brings the tokens back to publicland: private\_to\_public ``` #[external("private")] fn private_to_public(amount: u64) { let sender = self.msg_sender(); // Remove from private balance self.storage.private_balances.at(sender).sub(amount as u128).deliver( MessageDelivery.ONCHAIN_CONSTRAINED, ); // Enqueue public credit self.enqueue_self._credit_public_balance(sender, amount); } #[external("public")] #[only_self] fn _credit_public_balance(owner: AztecAddress, amount: u64) { let balance = self.storage.public_balances.at(owner).read(); self.storage.public_balances.at(owner).write(balance + amount); } ``` > [Source code: docs/examples/contracts/bob\_token\_contract/src/main.nr#L152-L170](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/bob_token_contract/src/main.nr#L152-L170) Now you've made changes to your contract, you need to recompile your contract. Here are the steps from above, for reference: ``` aztec compile aztec codegen target --outdir artifacts ``` ## Testing the Complete Privacy System[​](#testing-the-complete-privacy-system "Direct link to Testing the Complete Privacy System") Before running the updated script, double-check your local network is still running: ``` aztec start --local-network ``` If you stopped it between parts of the tutorial, start it again here. Because we set `ephemeral: true` when creating the wallet, restarting the network is safe — the script won't try to reuse stale PXE state from a previous run. Now that you've implemented all the privacy features, let's update our test script to showcase the full privacy flow: ### Update Your Test Script[​](#update-your-test-script "Direct link to Update Your Test Script") Let's stop being lazy and add a nice little "log" function that just spits out everyone's balances to the console, for example: ``` // at the top of your file async function getBalances( contract: BobTokenContract, aliceAddress: AztecAddress, bobAddress: AztecAddress, ) { await Promise.all([ contract.methods .public_balance_of(aliceAddress) .simulate({ from: aliceAddress }) .then(({ result }) => result), contract.methods .private_balance_of(aliceAddress) .simulate({ from: aliceAddress }) .then(({ result }) => result), contract.methods .public_balance_of(bobAddress) .simulate({ from: bobAddress }) .then(({ result }) => result), contract.methods .private_balance_of(bobAddress) .simulate({ from: bobAddress }) .then(({ result }) => result), ]).then( ([ alicePublicBalance, alicePrivateBalance, bobPublicBalance, bobPrivateBalance, ]) => { console.log( `📊 Alice has ${alicePublicBalance} public BOB tokens and ${alicePrivateBalance} private BOB tokens`, ); console.log( `📊 Bob's Clinic has ${bobPublicBalance} public BOB tokens and ${bobPrivateBalance} private BOB tokens`, ); }, ); } ``` Looks ugly but it does what it says: prints Alice's and Bob's balances. This will make it easier to see our contract working. Now let's add some more stuff to our `index.ts`: ``` async function main() { // ...etc await bobToken.methods .mint_public(aliceAddress, 100n) .send({ from: giggleAddress }); await getBalances(bobToken, aliceAddress, bobClinicAddress); await bobToken.methods .transfer_public(bobClinicAddress, 10n) .send({ from: aliceAddress }); await getBalances(bobToken, aliceAddress, bobClinicAddress); await bobToken.methods.public_to_private(90n).send({ from: aliceAddress }); await getBalances(bobToken, aliceAddress, bobClinicAddress); await bobToken.methods .transfer_private(bobClinicAddress, 50n) .send({ from: aliceAddress }); await getBalances(bobToken, aliceAddress, bobClinicAddress); await bobToken.methods.private_to_public(10n).send({ from: aliceAddress }); await getBalances(bobToken, aliceAddress, bobClinicAddress); await bobToken.methods .mint_private(aliceAddress, 100n) .send({ from: giggleAddress }); await getBalances(bobToken, aliceAddress, bobClinicAddress); } main().catch(console.error); ``` The flow is something like: * Giggle mints Alice 100 BOB in public * Alice transfers 10 BOB to Bob in public * Alice makes the remaining 90 BOB private * Alice transfers 50 of those to Bob, in private * Of the remaining 40 BOB, she makes 10 public again * Giggle mints 100 BOB tokens for Alice, in private Let's give it a try: ``` npx tsx index.ts ``` You should see the complete privacy journey from transparent allocation to confidential usage. The final pair of log lines should look like: ``` 📊 Alice has 10 public BOB tokens and 130 private BOB tokens 📊 Bob's Clinic has 10 public BOB tokens and 50 private BOB tokens ``` If your output doesn't match, double-check that the local network is running and that you started this run with a fresh `aztec start --local-network`. ## Summary[​](#summary "Direct link to Summary") You've built a privacy-preserving token system that solves a real-world problem: enabling corporate mental health benefits while protecting employee privacy. This demonstrates Aztec's unique ability to provide both transparency and privacy where each is most needed. The BOB token shows how blockchain can enable new models of corporate benefits that weren't possible before - where verification and privacy coexist, empowering employees to seek help without fear of judgment or career impact. ### What You Learned[​](#what-you-learned "Direct link to What You Learned") * How to create tokens with both public and private states * How to bridge between public and private domains * How to implement access control across execution contexts * How to build real-world privacy solutions on Aztec ## Going Further: The AIP-20 Token Standard[​](#going-further-the-aip-20-token-standard "Direct link to Going Further: The AIP-20 Token Standard") The BOB token you built in this tutorial implements a simplified version of the patterns formalized in **AIP-20**, Aztec's fungible token standard. AIP-20 extends these patterns with commitment-based transfers for DeFi composability, recursive note consumption for large balances, and tokenized vault support (AIP-4626). Read the full [AIP-20 standard reference](/developers/docs/aztec-nr/standards/aip-20.md) for details, or explore all [Aztec Contract Standards](/developers/docs/aztec-nr/standards.md). ### Continue Your Journey[​](#continue-your-journey "Direct link to Continue Your Journey") * Explore [cross-chain communication](/developers/docs/foundational-topics/ethereum-aztec-messaging.md) to integrate with existing health systems * Learn about [account abstraction](/developers/docs/foundational-topics/accounts.md) for recovery mechanisms --- # Deploying a Token Contract In this guide, we will retrieve the local network and deploy a pre-written token contract to it using Aztec.js. [Check out the source code](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr). We will then use Aztec.js to interact with this contract and transfer tokens. Before starting, make sure to be running Aztec local network at version 4.3.1. Check out [the guide](/developers/getting_started_on_local_network.md) for info about that. ## Set up the project[​](#set-up-the-project "Direct link to Set up the project") First, create a new directory for your project and initialize it with yarn: ``` mkdir token-tutorial cd token-tutorial yarn init -y ``` Next, add the TypeScript dependencies: ``` yarn add typescript @types/node tsx ``` tip Never heard of `tsx`? Well, it will just run `typescript` with reasonable defaults. Pretty cool for a small example like this one. You may want to tune in your own project's `tsconfig.json` later! Let's also import the Aztec dependencies for this tutorial: ``` yarn add @aztec/aztec.js@4.3.1 @aztec/accounts@4.3.1 @aztec/noir-contracts.js@4.3.1 @aztec/wallets@4.3.1 ``` Aztec.js assumes your project is using ESM, so make sure you add `"type": "module"` to `package.json`. You probably also want at least a `start` script. For example: ``` { "type": "module", "scripts": { "start": "tsx index.ts" } } ``` ### Connecting to the local network[​](#connecting-to-the-local-network "Direct link to Connecting to the local network") Now let's connect to the Aztec local network and set up test accounts. **Step 1: Start the Aztec Local Network** In a separate terminal, run: ``` aztec start --local-network ``` Keep this terminal running throughout the tutorial. **Step 2: Create the index.ts file** Create an `index.ts` file in the root of your project with the following code. This connects to the local network and imports test accounts (Alice and Bob): setup ``` import { EmbeddedWallet } from "@aztec/wallets/embedded"; import { getInitialTestAccountsData } from "@aztec/accounts/testing"; const nodeUrl = process.env.AZTEC_NODE_URL ?? "http://localhost:8080"; const wallet = await EmbeddedWallet.create(nodeUrl, { ephemeral: true }); const [alice, bob] = await getInitialTestAccountsData(); await wallet.createSchnorrAccount(alice.secret, alice.salt); await wallet.createSchnorrAccount(bob.secret, bob.salt); ``` > [Source code: docs/examples/ts/aztecjs\_getting\_started/index.ts#L1-L11](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_getting_started/index.ts#L1-L11) **Step 3: Verify the script runs** Run the script to make sure everything is set up correctly: ``` yarn start ``` If there are no errors, you're ready to continue. For more details on connecting to the local network, see [this guide](/developers/docs/aztec-js/how_to_connect_to_local_network.md). ## Deploy the token contract[​](#deploy-the-token-contract "Direct link to Deploy the token contract") Now that we have our accounts loaded, let's deploy a pre-compiled token contract from the Aztec library. You can find the full code for the contract [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/v4.3.1/noir-projects/noir-contracts/contracts/app/token_contract/src). Add the following to `index.ts` to import the contract and deploy it with Alice as the admin: deploy ``` import { TokenContract } from "@aztec/noir-contracts.js/Token"; const { contract: token } = await TokenContract.deploy( wallet, alice.address, "TokenName", "TKN", 18, ).send({ from: alice.address }); ``` > [Source code: docs/examples/ts/aztecjs\_getting\_started/index.ts#L13-L23](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_getting_started/index.ts#L13-L23) ## Mint and transfer[​](#mint-and-transfer "Direct link to Mint and transfer") Let's go ahead and have Alice mint herself some tokens, in private: mint ``` await token.methods .mint_to_private(alice.address, 100) .send({ from: alice.address }); ``` > [Source code: docs/examples/ts/aztecjs\_getting\_started/index.ts#L25-L29](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_getting_started/index.ts#L25-L29) Let's check both Alice's and Bob's balances now: check\_balances ``` let { result: aliceBalance } = await token.methods .balance_of_private(alice.address) .simulate({ from: alice.address }); console.log(`Alice's balance: ${aliceBalance}`); let { result: bobBalance } = await token.methods .balance_of_private(bob.address) .simulate({ from: bob.address }); console.log(`Bob's balance: ${bobBalance}`); ``` > [Source code: docs/examples/ts/aztecjs\_getting\_started/index.ts#L31-L40](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_getting_started/index.ts#L31-L40) Alice should have 100 tokens, while Bob has none yet. Great! Let's have Alice transfer some tokens to Bob, also in private: transfer ``` await token.methods.transfer(bob.address, 10).send({ from: alice.address }); ({ result: bobBalance } = await token.methods .balance_of_private(bob.address) .simulate({ from: bob.address })); console.log(`Bob's balance: ${bobBalance}`); ``` > [Source code: docs/examples/ts/aztecjs\_getting\_started/index.ts#L42-L48](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_getting_started/index.ts#L42-L48) Bob should now see 10 tokens in his balance. ## Other cool things[​](#other-cool-things "Direct link to Other cool things") Say that Alice is nice and wants to set Bob as a minter. Even though it's a public function, it can be called in a similar way: set\_minter ``` await token.methods.set_minter(bob.address, true).send({ from: alice.address }); ``` > [Source code: docs/examples/ts/aztecjs\_getting\_started/index.ts#L50-L52](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_getting_started/index.ts#L50-L52) Bob is now the minter, so he can mint some tokens to himself: bob\_mints ``` await token.methods .mint_to_private(bob.address, 100) .send({ from: bob.address }); ({ result: bobBalance } = await token.methods .balance_of_private(bob.address) .simulate({ from: bob.address })); console.log(`Bob's balance: ${bobBalance}`); ``` > [Source code: docs/examples/ts/aztecjs\_getting\_started/index.ts#L54-L62](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/aztecjs_getting_started/index.ts#L54-L62) info Have a look at the [contract source](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr). Notice is that the `mint_to_private` function we used above actually starts a partial note. This allows the total balance to increase while keeping the recipient private! How cool is that? ## Going Further[​](#going-further "Direct link to Going Further") The pre-compiled token contract used in this tutorial is Aztec's reference implementation. It covers the core operations you need to get started: minting, private transfers, and public balance management. For production applications, consider the **AIP-20 Token Standard** maintained by [DeFi Wonderland](https://github.com/defi-wonderland/aztec-standards/tree/dev/src/token_contract). AIP-20 formalizes the same patterns used in the reference contract and adds: * **Commitment-based transfers** for DeFi protocols where the recipient is determined asynchronously * **Recursive note consumption** for handling large balances that span many notes * **Tokenized vault support (AIP-4626)** for yield-bearing tokens that issue shares against an underlying asset To learn how to write a token contract from scratch rather than deploying a pre-compiled one, see the [Private Token Contract tutorial](/developers/docs/tutorials/contract_tutorials/token_contract.md). For the full specifications of all Aztec contract standards, see the [Aztec Contract Standards](/developers/docs/aztec-nr/standards.md) reference. --- # Bridge Your NFT to Aztec ## Why Bridge an NFT?[​](#why-bridge-an-nft "Direct link to Why Bridge an NFT?") Imagine you own a CryptoPunk NFT on Ethereum. You want to use it in games, social apps, or DeFi protocols, but gas fees on Ethereum make every interaction expensive. What if you could move your Punk to Aztec (L2), use it **privately** in dozens of applications, and then bring it back to Ethereum when you're ready to sell? In this tutorial, you'll build a **private NFT bridge**. By the end, you'll understand how **portals** work and how **cross-chain messages** flow between L1 and L2. Before starting, make sure you have the Aztec local network running at version v4.3.1. Check out [the local network guide](/developers/getting_started_on_local_network.md) for setup instructions. ## What You'll Build[​](#what-youll-build "Direct link to What You'll Build") You'll create two contracts with **privacy at the core**: * **NFTPunk (L2)** - An NFT contract with encrypted ownership using `PrivateSet` * **NFTBridge (L2)** - A bridge that mints NFTs privately when claiming L1 messages This tutorial focuses on the L2 side to keep things manageable. You'll learn the essential privacy patterns that apply to any asset bridge on Aztec. ## Project Setup[​](#project-setup "Direct link to Project Setup") Let's start simple. Since this is an Ethereum project, it's easier to just start with Hardhat: ``` git clone https://github.com/critesjosh/hardhat-aztec-example ``` You're cloning a repo here to make it easier for Aztec's `l1-contracts` to be mapped correctly. You should now have a `hardhat-aztec-example` folder with Hardhat's default starter, with a few changes in `package.json`. We want to add a few more dependencies now before we start: ``` cd hardhat-aztec-example yarn add @aztec/aztec.js@4.3.1 @aztec/accounts@4.3.1 @aztec/stdlib@4.3.1 @aztec/wallets@4.3.1 tsx ``` Match the `@aztec/l1-contracts` version The starter repo pins its `@aztec/l1-contracts` dependency to an older release. In `package.json`, update the tag to match the network version used in this tutorial, then run `yarn install` again: ``` "@aztec/l1-contracts": "git+https://github.com/AztecProtocol/l1-contracts.git#v4.3.1" ``` The L1 interfaces the portal imports later in this tutorial must match the contracts deployed by your running network. Now start the local network in another terminal: ``` aztec start --local-network ``` This should start two important services on ports 8080 and 8545, respectively: Aztec and Anvil (an Ethereum development node). ## Part 1: Building the NFT Contract[​](#part-1-building-the-nft-contract "Direct link to Part 1: Building the NFT Contract") Let's start with a basic NFT contract on Aztec. That's the representation of the NFT locked on the L2 side: Let's create that crate in the `contracts` folder so it looks tidy: ``` aztec new contracts/aztec/nft cd contracts/aztec/nft ``` This creates a workspace with two crates: an `nft_contract` crate for the smart contract code and an `nft_test` crate for Noir tests. The `aztec` dependency is already configured in `nft_contract/Nargo.toml`. Noir Language Server If you're using VS Code, install the [Noir Language Support extension](https://marketplace.visualstudio.com/items?itemName=noir-lang.vscode-noir) for syntax highlighting, error checking, and code completion while writing Noir contracts. ### Create the NFT Note[​](#create-the-nft-note "Direct link to Create the NFT Note") First, let's create a custom note type for private NFT ownership. In the `nft_contract/src/` directory, create a new file called `nft.nr`: ``` touch nft_contract/src/nft.nr ``` In this file, you're going to create a **private note** that represents NFT ownership. This is a struct with macros that indicate it is a note that can be compared and packed: nft\_note\_struct ``` use aztec::{macros::notes::note, protocol::traits::Packable}; #[derive(Eq, Packable)] #[note] pub struct NFTNote { pub token_id: Field, } ``` > [Source code: docs/examples/contracts/nft/src/nft.nr#L1-L9](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/nft/src/nft.nr#L1-L9) You now have a note that represents the owner of a particular NFT. Next, move on to the contract itself. Custom Notes Notes are powerful concepts. Learn more about how to use them in the [state management guide](/developers/docs/foundational-topics/state_management.md). ### Define Storage[​](#define-storage "Direct link to Define Storage") Back in `nft_contract/src/main.nr`, you can now build the contract storage. You need: * **admin**: Who controls the contract (set once, never changes) * **minter**: The bridge address (set once by admin) * **nfts**: Track which NFTs exist (public, needed for bridging) * **owners**: Private ownership using the NFTNote One interesting aspect of this storage configuration is the use of `DelayedPublicMutable`, which allows private functions to read and use public state. You're using it to publicly track which NFTs are already minted while keeping their owners private. Read more about `DelayedPublicMutable` in [the storage guide](/developers/docs/aztec-nr/framework-description/state_variables.md). Write the storage struct and a simple [initializer](/developers/docs/foundational-topics/contract_creation.md#initialization) to set the admin in the `nft_contract/src/main.nr` file: ``` use aztec::macros::aztec; pub mod nft; #[aztec] pub contract NFTPunk { use crate::nft::NFTNote; use aztec::{ macros::{functions::{external, initializer, only_self}, storage::storage}, protocol::address::AztecAddress, state_vars::{DelayedPublicMutable, Map, Owned, PrivateSet, PublicImmutable}, }; use aztec::messages::message_delivery::MessageDelivery; use aztec::note::{ note_getter_options::NoteGetterOptions, note_interface::NoteProperties, note_viewer_options::NoteViewerOptions, }; use aztec::utils::comparison::Comparator; #[storage] struct Storage { admin: PublicImmutable, minter: PublicImmutable, nfts: Map, Context>, owners: Owned, Context>, } #[external("public")] #[initializer] fn constructor(admin: AztecAddress) { self.storage.admin.initialize(admin); } } ``` ### Utility Functions[​](#utility-functions "Direct link to Utility Functions") Add an internal function to handle the `DelayedPublicMutable` value change. Mark the function as public and `#[only_self]` so only the contract can call it: mark\_nft\_exists ``` #[external("public")] #[only_self] fn _mark_nft_exists(token_id: Field, exists: bool) { self.storage.nfts.at(token_id).schedule_value_change(exists); } ``` > [Source code: docs/examples/contracts/nft/src/main.nr#L42-L48](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/nft/src/main.nr#L42-L48) This function is marked with `#[only_self]`, meaning only the contract itself can call it. It uses `schedule_value_change` to update the `nfts` storage, preventing the same NFT from being minted twice or burned when it doesn't exist. You'll call this public function from a private function later using `enqueue_self`. Another useful function checks how many notes a caller has. You can use this later to verify the claim and exit from L2: notes\_of ``` #[external("utility")] unconstrained fn notes_of(from: AztecAddress) -> Field { let notes = self.storage.owners.at(from).view_notes(NoteViewerOptions::new()); notes.len() as Field } ``` > [Source code: docs/examples/contracts/nft/src/main.nr#L67-L73](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/nft/src/main.nr#L67-L73) ### Add Minting and Burning[​](#add-minting-and-burning "Direct link to Add Minting and Burning") Before anything else, you need to set the minter. This will be the bridge contract, so only the bridge contract can mint NFTs. This value doesn't need to change after initialization. Here's how to initialize the `PublicImmutable`: set\_minter ``` #[external("public")] fn set_minter(minter: AztecAddress) { assert(self.storage.admin.read().eq(self.msg_sender()), "caller is not admin"); self.storage.minter.initialize(minter); } ``` > [Source code: docs/examples/contracts/nft/src/main.nr#L34-L40](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/nft/src/main.nr#L34-L40) Now for the magic - minting NFTs **privately**. The bridge will call this to mint to a user, deliver the note using [constrained message delivery](/developers/docs/aztec-nr/framework-description/events_and_logs.md) (best practice when "sending someone a note") and then [enqueue a public call](/developers/docs/aztec-nr/framework-description/calling_contracts.md) to the `_mark_nft_exists` function: mint ``` #[external("private")] fn mint(to: AztecAddress, token_id: Field) { assert( self.storage.minter.read().eq(self.msg_sender()), "caller is not the authorized minter", ); // we create an NFT note and insert it to the PrivateSet - a collection of notes meant to be read in private let new_nft = NFTNote { token_id }; self.storage.owners.at(to).insert(new_nft).deliver(MessageDelivery.ONCHAIN_CONSTRAINED); // calling the internal public function above to indicate that the NFT is taken self.enqueue_self._mark_nft_exists(token_id, true); } ``` > [Source code: docs/examples/contracts/nft/src/main.nr#L50-L65](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/nft/src/main.nr#L50-L65) The bridge will also need to burn NFTs when users withdraw back to L1: burn ``` #[external("private")] fn burn(from: AztecAddress, token_id: Field) { assert( self.storage.minter.read().eq(self.msg_sender()), "caller is not the authorized minter", ); // from the NFTNote properties, selects token_id and compares it against the token_id to be burned let options = NoteGetterOptions::new() .select(NFTNote::properties().token_id, Comparator.EQ, token_id) .set_limit(1); let notes = self.storage.owners.at(from).pop_notes(options); assert(notes.len() == 1, "NFT not found"); self.enqueue_self._mark_nft_exists(token_id, false); } ``` > [Source code: docs/examples/contracts/nft/src/main.nr#L75-L92](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/nft/src/main.nr#L75-L92) ### Compiling\![​](#compiling "Direct link to Compiling!") Let's verify it compiles: ``` aztec compile ``` 🎉 You should see "Compiled successfully!" This means our private NFT contract is ready. Now let's build the bridge. ## Part 2: Building the Bridge[​](#part-2-building-the-bridge "Direct link to Part 2: Building the Bridge") We have built the L2 NFT contract. This is the L2 representation of an NFT that is locked on the L1 bridge. The L2 bridge is the contract that talks to the L1 bridge through cross-chain messaging. You can read more about this protocol [here](/developers/docs/foundational-topics/ethereum-aztec-messaging.md). Let's create a new contract in the same tidy `contracts/aztec` folder: ``` cd .. aztec new nft_bridge cd nft_bridge ``` Now add the `NFTPunk` contract dependency to `nft_bridge_contract/Nargo.toml`. The `aztec` dependency is already there: ``` [dependencies] aztec = { git="https://github.com/AztecProtocol/aztec-nr", tag = "v4.3.1", directory = "aztec" } NFTPunk = { path = "../../nft/nft_contract" } ``` ### Understanding Bridges[​](#understanding-bridges "Direct link to Understanding Bridges") A bridge has two jobs: 1. **Claim**: When someone deposits an NFT on L1, mint it on L2 2. **Exit**: When someone wants to withdraw, burn on L2 and unlock on L1 This means having knowledge about the L2 NFT contract, and the bridge on the L1 side. That's what goes into our bridge's storage. ### Bridge Storage[​](#bridge-storage "Direct link to Bridge Storage") Clean up `nft_bridge_contract/src/main.nr` which is just a placeholder, and let's write the storage struct and the constructor. We'll use `PublicImmutable` since these values never change: ``` use aztec::macros::aztec; #[aztec] pub contract NFTBridge { use aztec::{ macros::{functions::{external, initializer}, storage::storage}, protocol::{address::{AztecAddress, EthAddress}, hash::sha256_to_field}, state_vars::PublicImmutable, }; use NFTPunk::NFTPunk; #[storage] struct Storage { nft: PublicImmutable, portal: PublicImmutable, } #[external("public")] #[initializer] fn constructor(nft: AztecAddress) { self.storage.nft.initialize(nft); } #[external("public")] fn set_portal(portal: EthAddress) { self.storage.portal.initialize(portal); } } ``` You can't initialize the `portal` value in the constructor because the L1 portal hasn't been deployed yet. You'll need another function to set it up after the L1 portal is deployed. ### Adding the Bridge Functions[​](#adding-the-bridge-functions "Direct link to Adding the Bridge Functions") The Aztec network provides a way to consume messages from L1 to L2 called `consume_l1_to_l2_message`. You need to define how to encode messages. Here's a simple approach: when an NFT is being bridged, the L1 portal sends a hash of its `token_id` through the bridge, signaling which `token_id` was locked and can be minted on L2. This approach is simple but sufficient for this tutorial. Build the `claim` function, which consumes the message and mints the NFT on the L2 side: claim ``` #[external("private")] fn claim(to: AztecAddress, token_id: Field, secret: Field, message_leaf_index: Field) { // Compute the message hash that was sent from L1 let token_id_bytes: [u8; 32] = (token_id as Field).to_be_bytes(); let content_hash = sha256_to_field(token_id_bytes); // Consume the L1 -> L2 message self.context.consume_l1_to_l2_message( content_hash, secret, self.storage.portal.read(), message_leaf_index, ); // Mint the NFT on L2 let nft: AztecAddress = self.storage.nft.read(); self.call(NFTPunk::at(nft).mint(to, token_id)); } ``` > [Source code: docs/examples/contracts/nft\_bridge/src/main.nr#L31-L50](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/nft_bridge/src/main.nr#L31-L50) Secret The secret prevents front-running. Certainly you don't want anyone to claim your NFT on the L2 side by just being faster. Adding a secret acts like a "password": you can only claim it if you know it. Similarly, exiting to L1 means burning the NFT on the L2 side and pushing a message through the protocol. To ensure only the L1 recipient can claim it, hash the `token_id` together with the `recipient`: exit ``` #[external("private")] fn exit(token_id: Field, recipient: EthAddress) { // Create L2->L1 message to unlock NFT on L1 let token_id_bytes: [u8; 32] = token_id.to_be_bytes(); let recipient_bytes: [u8; 20] = recipient.to_be_bytes(); let content = sha256_to_field(token_id_bytes.concat(recipient_bytes)); self.context.message_portal(self.storage.portal.read(), content); // Burn the NFT on L2 let nft: AztecAddress = self.storage.nft.read(); self.call(NFTPunk::at(nft).burn(self.msg_sender(), token_id)); } ``` > [Source code: docs/examples/contracts/nft\_bridge/src/main.nr#L52-L65](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/contracts/nft_bridge/src/main.nr#L52-L65) Cross-chain messaging on Aztec is powerful because it doesn't conform to any specific format—you can structure messages however you want. Private Functions Both `claim` and `exit` are `#[external("private")]`, which means the bridging process is private—nobody can see who's bridging which NFT by watching the chain. ### Compile the Bridge[​](#compile-the-bridge "Direct link to Compile the Bridge") ``` aztec compile ``` Bridge compiled successfully! Now process both contracts and generate TypeScript bindings: ``` cd ../nft aztec codegen target --outdir ../artifacts cd ../nft_bridge aztec codegen target --outdir ../artifacts ``` An `artifacts` folder should appear with TypeScript bindings for each contract. You'll use these when deploying the contracts. ## Part 3: The Ethereum Side[​](#part-3-the-ethereum-side "Direct link to Part 3: The Ethereum Side") Now build the L1 contracts. You need: * A simple ERC721 NFT contract (the "CryptoPunk") * A portal contract that locks/unlocks NFTs and communicates with Aztec ### Install Dependencies[​](#install-dependencies "Direct link to Install Dependencies") Aztec's contracts are already in your `package.json`. You just need to add the OpenZeppelin contracts that provide the default ERC721 implementation: ``` cd ../../.. yarn add @openzeppelin/contracts ``` ### Create a Simple NFT[​](#create-a-simple-nft "Direct link to Create a Simple NFT") Delete the "Counter" contracts that show up by default in `contracts` and create `contracts/SimpleNFT.sol`: ``` touch contracts/SimpleNFT.sol ``` Create a minimal NFT contract sufficient for demonstrating bridging: simple\_nft ``` pragma solidity >=0.8.27; import {ERC721} from "@oz/token/ERC721/ERC721.sol"; contract SimpleNFT is ERC721 { uint256 private _currentTokenId; constructor() ERC721("SimplePunk", "SPUNK") {} function mint(address to) external returns (uint256) { uint256 tokenId = _currentTokenId++; _mint(to, tokenId); return tokenId; } } ``` > [Source code: docs/examples/solidity/nft\_bridge/SimpleNFT.sol#L2-L18](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/solidity/nft_bridge/SimpleNFT.sol#L2-L18) ### Create the NFT Portal[​](#create-the-nft-portal "Direct link to Create the NFT Portal") The NFT Portal has more code, so build it step-by-step. Create `contracts/NFTPortal.sol`: ``` touch contracts/NFTPortal.sol ``` Initialize it with Aztec's registry, which holds the canonical contracts for Aztec-related contracts, including the Inbox and Outbox. These are the message-passing contracts—Aztec sequencers read any messages on these contracts. ``` import {IERC721} from "@oz/token/ERC721/IERC721.sol"; import {IRegistry} from "@aztec/governance/interfaces/IRegistry.sol"; import {IInbox} from "@aztec/core/interfaces/messagebridge/IInbox.sol"; import {IOutbox} from "@aztec/core/interfaces/messagebridge/IOutbox.sol"; import {IRollup} from "@aztec/core/interfaces/IRollup.sol"; import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; import {Hash} from "@aztec/core/libraries/crypto/Hash.sol"; import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; contract NFTPortal { IRegistry public registry; IERC721 public nftContract; bytes32 public l2Bridge; IRollup public rollup; IOutbox public outbox; IInbox public inbox; uint256 public rollupVersion; function initialize(address _registry, address _nftContract, bytes32 _l2Bridge) external { registry = IRegistry(_registry); nftContract = IERC721(_nftContract); l2Bridge = _l2Bridge; rollup = IRollup(address(registry.getCanonicalRollup())); outbox = rollup.getOutbox(); inbox = rollup.getInbox(); rollupVersion = rollup.getVersion(); } } ``` The core logic is similar to the L2 logic. `depositToAztec` calls the `Inbox` canonical contract to send a message to Aztec, and `withdraw` calls the `Outbox` contract. Add these two functions with explanatory comments: portal\_deposit\_and\_withdraw ``` // Lock NFT and send message to L2 function depositToAztec(uint256 tokenId, bytes32 secretHash) external returns (bytes32, uint256) { // Lock the NFT nftContract.transferFrom(msg.sender, address(this), tokenId); // Prepare L2 message - just a naive hash of our tokenId DataStructures.L2Actor memory actor = DataStructures.L2Actor(l2Bridge, rollupVersion); bytes32 contentHash = Hash.sha256ToField(abi.encode(tokenId)); // Send message to Aztec (bytes32 key, uint256 index) = inbox.sendL2Message(actor, contentHash, secretHash); return (key, index); } // Unlock NFT after L2 burn function withdraw( uint256 tokenId, Epoch epoch, uint256 leafIndex, bytes32[] calldata path ) external { // Verify message from L2 DataStructures.L2ToL1Msg memory message = DataStructures.L2ToL1Msg({ sender: DataStructures.L2Actor(l2Bridge, rollupVersion), recipient: DataStructures.L1Actor(address(this), block.chainid), content: Hash.sha256ToField(abi.encodePacked(tokenId, msg.sender)) }); outbox.consume(message, epoch, leafIndex, path); // Unlock NFT nftContract.transferFrom(address(this), msg.sender, tokenId); } ``` > [Source code: docs/examples/solidity/nft\_bridge/NFTPortal.sol#L36-L70](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/solidity/nft_bridge/NFTPortal.sol#L36-L70) The portal handles two flows: * **depositToAztec**: Locks NFT on L1, sends message to L2 * **withdraw**: Verifies L2 message, unlocks NFT on L1 ### Compile[​](#compile "Direct link to Compile") Let's make sure everything compiles: ``` npx hardhat compile ``` You should see successful compilation of both contracts! ## Part 4: Compiling, Deploying, and Testing[​](#part-4-compiling-deploying-and-testing "Direct link to Part 4: Compiling, Deploying, and Testing") Now deploy everything and test the full flow. This will help you understand how everything fits together. Delete the placeholders in `scripts` and create `index.ts`: ``` touch scripts/index.ts ``` This script will implement the user flow. Testnet This section assumes you're working locally using the local network. For the testnet, you need to account for some things: * Your clients need to point to some Sepolia Node and to the public Aztec Full Node * You need to [deploy your own Aztec accounts](/developers/docs/aztec-js/how_to_create_account.md) * You need to pay fees in some other way. Learn how in the [fees guide](/developers/docs/aztec-js/how_to_pay_fees.md) ### Deploying and Initializing[​](#deploying-and-initializing "Direct link to Deploying and Initializing") First, initialize the clients: `aztec.js` for Aztec and `viem` for Ethereum: setup ``` import { getInitialTestAccountsData } from "@aztec/accounts/testing"; import { AztecAddress, EthAddress } from "@aztec/aztec.js/addresses"; import { Fr } from "@aztec/aztec.js/fields"; import { createAztecNodeClient } from "@aztec/aztec.js/node"; import { createExtendedL1Client } from "@aztec/ethereum/client"; import { deployL1Contract } from "@aztec/ethereum/deploy-l1-contract"; import { sha256ToField } from "@aztec/foundation/crypto/sha256"; import { computeL2ToL1MessageHash, computeSecretHash, } from "@aztec/stdlib/hash"; import { computeL2ToL1MembershipWitness } from "@aztec/stdlib/messaging"; import { EmbeddedWallet } from "@aztec/wallets/embedded"; import { decodeEventLog, pad } from "@aztec/viem"; import { foundry } from "@aztec/viem/chains"; import NFTPortal from "../../../target/solidity/nft_bridge/NFTPortal.sol/NFTPortal.json" with { type: "json" }; import SimpleNFT from "../../../target/solidity/nft_bridge/SimpleNFT.sol/SimpleNFT.json" with { type: "json" }; import { NFTBridgeContract } from "./artifacts/NFTBridge.js"; import { NFTPunkContract } from "./artifacts/NFTPunk.js"; // Setup L1 client using anvil's default mnemonic (same as e2e tests) const MNEMONIC = "test test test test test test test test test test test junk"; const l1Client = createExtendedL1Client(["http://localhost:8545"], MNEMONIC); const ownerEthAddress = l1Client.account.address; // Setup L2 using Aztec's local network and one of its initial accounts console.log("Setting up L2...\n"); const node = createAztecNodeClient("http://localhost:8080"); const aztecWallet = await EmbeddedWallet.create(node); const [accData] = await getInitialTestAccountsData(); const account = await aztecWallet.createSchnorrAccount( accData.secret, accData.salt, ); console.log(`Account: ${account.address.toString()}\n`); // Get node info const nodeInfo = await node.getNodeInfo(); const registryAddress = nodeInfo.l1ContractAddresses.registryAddress.toString(); const inboxAddress = nodeInfo.l1ContractAddresses.inboxAddress.toString(); ``` > [Source code: docs/examples/ts/token\_bridge/index.ts#L1-L42](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/token_bridge/index.ts#L1-L42) Adjust the artifact imports for this project's layout The snippet above comes from the monorepo's runnable example, and its artifact imports point at that repo's layout. In the Hardhat project used in this tutorial, replace the four artifact imports with: ``` import NFTPortal from "../artifacts/contracts/NFTPortal.sol/NFTPortal.json" with { type: "json" }; import SimpleNFT from "../artifacts/contracts/SimpleNFT.sol/SimpleNFT.json" with { type: "json" }; import { NFTBridgeContract } from "../contracts/aztec/artifacts/NFTBridge.js"; import { NFTPunkContract } from "../contracts/aztec/artifacts/NFTPunk.js"; ``` `npx hardhat compile` writes the Solidity artifacts to `artifacts/contracts/`, and the `aztec codegen` commands from earlier wrote the TypeScript bindings to `contracts/aztec/artifacts/`. Hardhat artifacts also store the bytecode as a plain string, so in the deployment snippet below use `SimpleNFT.bytecode` and `NFTPortal.bytecode` instead of `.bytecode.object`. You now have wallets for both chains, correctly connected to their respective chains. Next, deploy the L1 contracts: deploy\_l1\_contracts ``` console.log("Deploying L1 contracts...\n"); const { address: nftAddress } = await deployL1Contract( l1Client, SimpleNFT.abi, SimpleNFT.bytecode.object as `0x${string}`, ); const { address: portalAddress } = await deployL1Contract( l1Client, NFTPortal.abi, NFTPortal.bytecode.object as `0x${string}`, ); console.log(`SimpleNFT: ${nftAddress}`); console.log(`NFTPortal: ${portalAddress}\n`); ``` > [Source code: docs/examples/ts/token\_bridge/index.ts#L44-L61](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/token_bridge/index.ts#L44-L61) Now deploy the L2 contracts. Thanks to the TypeScript bindings generated with `aztec codegen`, deployment is straightforward: deploy\_l2\_contracts ``` console.log("Deploying L2 contracts...\n"); const { contract: l2Nft } = await NFTPunkContract.deploy( aztecWallet, account.address, ).send({ from: account.address, }); const { contract: l2Bridge } = await NFTBridgeContract.deploy( aztecWallet, l2Nft.address, ).send({ from: account.address }); console.log(`L2 NFT: ${l2Nft.address.toString()}`); console.log(`L2 Bridge: ${l2Bridge.address.toString()}\n`); ``` > [Source code: docs/examples/ts/token\_bridge/index.ts#L63-L80](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/token_bridge/index.ts#L63-L80) Now that you have the L2 bridge's contract address, initialize the L1 bridge: initialize\_portal ``` console.log("Initializing portal..."); // Initialize the portal contract // @ts-expect-error - viem type inference doesn't work with JSON-imported ABIs const initHash = await l1Client.writeContract({ address: portalAddress.toString() as `0x${string}`, abi: NFTPortal.abi, functionName: "initialize", args: [registryAddress, nftAddress.toString(), l2Bridge.address.toString()], }); await l1Client.waitForTransactionReceipt({ hash: initHash }); console.log("Portal initialized\n"); ``` > [Source code: docs/examples/ts/token\_bridge/index.ts#L82-L96](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/token_bridge/index.ts#L82-L96) The L2 contracts were already initialized when you deployed them, but you still need to: * Tell the L2 bridge about Ethereum's portal address (by calling `set_portal` on the bridge) * Tell the L2 NFT contract who the minter is (by calling `set_minter` on the L2 NFT contract) Complete these initialization steps: initialize\_l2\_bridge ``` console.log("Setting up L2 bridge..."); await l2Bridge.methods .set_portal(EthAddress.fromString(portalAddress.toString())) .send({ from: account.address }); await l2Nft.methods .set_minter(l2Bridge.address) .send({ from: account.address }); console.log("Bridge configured\n"); ``` > [Source code: docs/examples/ts/token\_bridge/index.ts#L98-L110](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/token_bridge/index.ts#L98-L110) This completes the setup. It's a lot of configuration, but you're dealing with four contracts across two chains. ### L1 → L2 Flow[​](#l1--l2-flow "Direct link to L1 → L2 Flow") Now for the main flow. Mint a CryptoPunk on L1, deposit it to Aztec, and claim it on Aztec. Put everything in the same script. To mint, call the L1 contract with `mint`, which will mint `tokenId = 0`: mint\_nft\_l1 ``` console.log("Minting NFT on L1..."); // @ts-expect-error - viem type inference doesn't work with JSON-imported ABIs const mintHash = await l1Client.writeContract({ address: nftAddress.toString() as `0x${string}`, abi: SimpleNFT.abi, functionName: "mint", args: [ownerEthAddress], }); await l1Client.waitForTransactionReceipt({ hash: mintHash }); // no need to parse logs, this will be tokenId 0 since it's a fresh contract const tokenId = 0n; console.log(`Minted tokenId: ${tokenId}\n`); ``` > [Source code: docs/examples/ts/token\_bridge/index.ts#L112-L128](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/token_bridge/index.ts#L112-L128) To bridge, first approve the portal address to transfer the NFT, then transfer it by calling `depositToAztec`: deposit\_to\_aztec ``` console.log("Depositing NFT to Aztec..."); const secret = Fr.random(); const secretHash = await computeSecretHash(secret); // Approve portal to transfer the NFT // @ts-expect-error - viem type inference doesn't work with JSON-imported ABIs const approveHash = await l1Client.writeContract({ address: nftAddress.toString() as `0x${string}`, abi: SimpleNFT.abi, functionName: "approve", args: [portalAddress.toString(), tokenId], }); await l1Client.waitForTransactionReceipt({ hash: approveHash }); // Deposit to Aztec // @ts-expect-error - viem type inference doesn't work with JSON-imported ABIs const depositHash = await l1Client.writeContract({ address: portalAddress.toString() as `0x${string}`, abi: NFTPortal.abi, functionName: "depositToAztec", args: [ tokenId, pad(secretHash.toString() as `0x${string}`, { dir: "left", size: 32 }), ], }); const depositReceipt = await l1Client.waitForTransactionReceipt({ hash: depositHash, }); ``` > [Source code: docs/examples/ts/token\_bridge/index.ts#L130-L160](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/token_bridge/index.ts#L130-L160) The `Inbox` contract will emit an important log: `MessageSent(inProgress, index, leaf, updatedRollingHash);`. This log provides the **leaf index** of the message in the [L1-L2 Message Tree](/developers/docs/foundational-topics/ethereum-aztec-messaging.md)—the location of the message in the tree that will appear on L2. You need this index, plus the secret, to correctly claim and decrypt the message. Use viem to extract this information: get\_message\_leaf\_index ``` const INBOX_ABI = [ { type: "event", name: "MessageSent", inputs: [ { name: "checkpointNumber", type: "uint256", indexed: true }, { name: "index", type: "uint256", indexed: false }, { name: "hash", type: "bytes32", indexed: true }, { name: "rollingHash", type: "bytes16", indexed: false }, ], }, ] as const; // Find and decode the MessageSent event from the Inbox contract const messageSentLogs = depositReceipt.logs .filter((log) => log.address.toLowerCase() === inboxAddress.toLowerCase()) .map((log: any) => { try { const decoded = decodeEventLog({ abi: INBOX_ABI, data: log.data, topics: log.topics, }); return { log, decoded }; } catch { // Not a decodable event from this ABI return null; } }) .filter( (item): item is { log: any; decoded: any } => item !== null && (item.decoded as any).eventName === "MessageSent", ); const messageLeafIndex = new Fr(messageSentLogs[0].decoded.args.index); ``` > [Source code: docs/examples/ts/token\_bridge/index.ts#L162-L198](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/token_bridge/index.ts#L162-L198) This extracts the logs from the deposit and retrieves the leaf index. You can now claim it on L2. However, for security reasons, at least 2 blocks must pass before a message can be claimed on L2. If you called `claim` on the L2 contract immediately, it would return "no message available". Add a utility function to mine two blocks (it deploys a contract with a random salt): mine\_blocks ``` async function mine2Blocks( aztecWallet: EmbeddedWallet, accountAddress: AztecAddress, ) { await NFTPunkContract.deploy(aztecWallet, accountAddress).send({ from: accountAddress, }); await NFTPunkContract.deploy(aztecWallet, accountAddress).send({ from: accountAddress, }); } ``` > [Source code: docs/examples/ts/token\_bridge/index.ts#L200-L212](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/token_bridge/index.ts#L200-L212) Now claim the message on L2: claim\_on\_l2 ``` // Mine blocks await mine2Blocks(aztecWallet, account.address); // Check notes before claiming (should be 0) console.log("Checking notes before claim..."); const { result: notesBefore } = await l2Nft.methods .notes_of(account.address) .simulate({ from: account.address }); console.log(` Notes count: ${notesBefore}`); console.log("Claiming NFT on L2..."); await l2Bridge.methods .claim(account.address, new Fr(Number(tokenId)), secret, messageLeafIndex) .send({ from: account.address }); console.log("NFT claimed on L2\n"); // Check notes after claiming (should be 1) console.log("Checking notes after claim..."); const { result: notesAfterClaim } = await l2Nft.methods .notes_of(account.address) .simulate({ from: account.address }); console.log(` Notes count: ${notesAfterClaim}\n`); ``` > [Source code: docs/examples/ts/token\_bridge/index.ts#L214-L237](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/token_bridge/index.ts#L214-L237) ### L2 → L1 Flow[​](#l2--l1-flow "Direct link to L2 → L1 Flow") Great! You can expand the L2 contract to add features like NFT transfers. For now, exit the NFT on L2 and redeem it on L1. Mine two blocks because of `DelayedMutable`: exit\_from\_l2 ``` // L2 -> L1 flow console.log("Exiting NFT from L2..."); // Mine blocks, not necessary on devnet, but must wait for 2 blocks await mine2Blocks(aztecWallet, account.address); const recipientEthAddress = EthAddress.fromString(ownerEthAddress); const { receipt: exitReceipt } = await l2Bridge.methods .exit(new Fr(Number(tokenId)), recipientEthAddress) .send({ from: account.address }); console.log(`Exit message sent (block: ${exitReceipt.blockNumber})\n`); // Check notes after burning (should be 0 again) console.log("Checking notes after burn..."); const { result: notesAfterBurn } = await l2Nft.methods .notes_of(account.address) .simulate({ from: account.address }); console.log(` Notes count: ${notesAfterBurn}\n`); ``` > [Source code: docs/examples/ts/token\_bridge/index.ts#L239-L259](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/token_bridge/index.ts#L239-L259) Just like in the L1 → L2 flow, you need to know what to claim on L1. Where in the message tree is the message you want to claim? Use the utility `computeL2ToL1MembershipWitness`, which provides the leaf and the sibling path of the message: get\_withdrawal\_witness ``` // Compute the message hash directly from known parameters // This matches what the portal contract expects: Hash.sha256ToField(abi.encodePacked(tokenId, recipient)) const tokenIdBuffer = new Fr(Number(tokenId)).toBuffer(); const recipientBuffer = Buffer.from( recipientEthAddress.toString().slice(2), "hex", ); const content = sha256ToField([tokenIdBuffer, recipientBuffer]); // Get rollup version from the portal contract (it stores it during initialize) // @ts-expect-error - viem type inference doesn't work with JSON-imported ABIs const version = (await l1Client.readContract({ address: portalAddress.toString() as `0x${string}`, abi: NFTPortal.abi, functionName: "rollupVersion", })) as bigint; // Compute the L2->L1 message hash const msgLeaf = computeL2ToL1MessageHash({ l2Sender: l2Bridge.address, l1Recipient: EthAddress.fromString(portalAddress.toString()), content, rollupVersion: new Fr(version), chainId: new Fr(foundry.id), }); // Wait for the block to be proven before withdrawing // Waiting for the block to be proven is not necessary on the local network, but it is necessary on devnet console.log("Waiting for block to be proven..."); console.log(` Exit block number: ${exitReceipt.blockNumber}`); let provenBlockNumber = await node.getProvenBlockNumber(); console.log(` Current proven block: ${provenBlockNumber}`); while (provenBlockNumber < exitReceipt.blockNumber!) { console.log( ` Waiting... (proven: ${provenBlockNumber}, needed: ${exitReceipt.blockNumber})`, ); await new Promise((resolve) => setTimeout(resolve, 10000)); // Wait 10 seconds provenBlockNumber = await node.getProvenBlockNumber(); } console.log("Block proven!\n"); // Compute the membership witness using the message hash and the L2 tx hash const witness = await computeL2ToL1MembershipWitness( node, msgLeaf, exitReceipt.txHash, ); const epoch = witness!.epochNumber; console.log(` Epoch for block ${exitReceipt.blockNumber}: ${epoch}`); const siblingPathHex = witness!.siblingPath .toBufferArray() .map((buf: Buffer) => `0x${buf.toString("hex")}` as `0x${string}`); ``` > [Source code: docs/examples/ts/token\_bridge/index.ts#L261-L318](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/token_bridge/index.ts#L261-L318) With this information, call the L1 contract and use the index and the sibling path to claim the L1 NFT: withdraw\_on\_l1 ``` console.log("Withdrawing NFT on L1..."); // @ts-expect-error - viem type inference doesn't work with JSON-imported ABIs const withdrawHash = await l1Client.writeContract({ address: portalAddress.toString() as `0x${string}`, abi: NFTPortal.abi, functionName: "withdraw", args: [tokenId, BigInt(epoch), BigInt(witness!.leafIndex), siblingPathHex], }); await l1Client.waitForTransactionReceipt({ hash: withdrawHash }); console.log("NFT withdrawn to L1\n"); ``` > [Source code: docs/examples/ts/token\_bridge/index.ts#L320-L331](https://github.com/AztecProtocol/aztec-packages/blob/v4.3.1/docs/examples/ts/token_bridge/index.ts#L320-L331) You can now try the whole flow with: ``` npx tsx scripts/index.ts ``` ## What You Built[​](#what-you-built "Direct link to What You Built") A complete private NFT bridge with: 1. **L1 Contracts** (Solidity) * `SimpleNFT`: Basic ERC721 for testing * `NFTPortal`: Locks/unlocks NFTs and handles L1↔L2 messaging 2. **L2 Contracts** (Noir) * `NFTPunk`: Private NFT with encrypted ownership using `PrivateSet` * `NFTBridge`: Claims L1 messages and mints NFTs privately 3. **Full Flow** * Mint NFT on L1 * Deploy portal and bridge * Lock NFT on L1 → message sent to L2 * Claim on L2 → private NFT minted * Later: Burn on L2 → message to L1 → unlock ## Next Steps[​](#next-steps "Direct link to Next Steps") * Add a web frontend for easy bridging * Implement batch bridging for multiple NFTs * Add metadata bridging * Write comprehensive tests * Add proper access controls Learn More * [State management page](/developers/docs/foundational-topics/state_management.md) * [Cross-chain messaging](/developers/docs/foundational-topics/ethereum-aztec-messaging.md) --- # Run Aztec in a Local Network * Current version: `v4.3.1` * Update with `aztec-up 4.3.1` On this page you will find * [Versions](#versions) * [Dependency versions](#dependency-versions) * [Example contract versions](#example-contract-versions) * [Language server version](#language-server-version) * [Updating](#updating) * [Steps to keep up to date](#steps-to-keep-up-to-date) * [Updating Aztec.nr packages](#updating-aztecnr-packages) * [Automatic update](#automatic-update) * [Manual update](#manual-update) * [Updating Aztec.js packages](#updating-aztecjs-packages) * [Local Network PXE Proving](#local-network-pxe-proving) * [Local Network in Proving Mode](#local-network-in-proving-mode) * [Usage](#usage) * [Proving with `aztec-wallet`](#proving-with-aztec-wallet) ## Versions[​](#versions "Direct link to Versions") Aztec tools (local network, nargo), dependencies (Aztec.nr), and sample contracts are constantly being improved. When developing and referring to example .nr files/snippets, it is helpful to verify the versions of different components (below), and if required keep them in lock-step by [updating](#updating). ### Dependency versions[​](#dependency-versions "Direct link to Dependency versions") Dependency versions in a contract's `Nargo.toml` file correspond to the `aztec-packages` repository tag `aztec-packages` (filter tags by `aztec`...) If you get an error like: `Cannot read file ~/nargo/github.com/AztecProtocol/aztec-packages/...` Check the `git=` github url, tag, and directory. ### Example contract versions[​](#example-contract-versions "Direct link to Example contract versions") Example contracts serve as a helpful reference between versions of the Aztec.nr framework since they are strictly maintained with each release. Code referenced in the documentation is sourced from contracts within [this directory (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/v4.3.1/noir-projects/noir-contracts/contracts). As in the previous section, the location of the noir contracts moved at version `0.24.0`, from `yarn-project/noir-contracts` before, to `noir-projects/noir-contracts`. tip Notice the difference between the sample Counter contract from `0.23.0` to `0.24.0` shows the `note_type_id` was added. ``` diff ~/nargo/github.com/AztecProtocol/v0.23.0/yarn-project/noir-contracts/contracts/test/counter_contract/src/main.nr ~/nargo/github.com/AztecProtocol/v0.24.0/noir-projects/noir-contracts/contracts/test/counter_contract/src/main.nr ``` ``` 57a58 > note_type_id: Field, ``` ### Language server version[​](#language-server-version "Direct link to Language server version") The [Noir LSP](/developers/docs/aztec-nr/installation.md) uses your local version of `aztec`, and thus also `aztec compile`. The path of the former (once installed) can be seen by hovering over "Nargo" in the bottom status bar of VS Code, and the latter via the `which aztec` command. caution For Aztec contract files, this should be `aztec` and for noir-only files this should be `nargo`. Mismatching tools and file types will generate misleading syntax and compiler errors. This can present confusion when opening older contracts (and dependencies) written in older version of noir, such as: * Logs filled with errors from the dependencies * Or the LSP fails (re-runs automatically then stops) The second point requires a restart of the extension, which you can trigger with the command palette (Ctrl + Shift + P) and typing "Reload Window". ## Updating[​](#updating "Direct link to Updating") ### Steps to keep up to date[​](#steps-to-keep-up-to-date "Direct link to Steps to keep up to date") 1. Update the Aztec local network to the latest version (includes `aztec` command, pxe, etc): ``` aztec-up ``` To update to a specific version, pass the version number after the `aztec-up` command, or set `VERSION` for a particular git tag, eg for [v**0.77.0**](https://github.com/AztecProtocol/aztec-packages/tree/v0.77.0) ``` aztec-up 0.77.0 # or VERSION=0.77.0 aztec-up ``` 2. Update Aztec.nr and individual @aztec dependencies: Inside your project run: ``` cd your/aztec/project aztec update . --contract src/contract1 --contract src/contract2 ``` The local network must be running for the update command to work. Make sure it is [installed and running](/developers/getting_started_on_local_network.md). Follow [updating Aztec.nr packages](#updating-aztecnr-packages) and [updating JavaScript packages](#updating-aztecjs-packages) guides. 3. Refer to [Migration Notes](/developers/docs/resources/migration_notes.md) on any breaking changes that might affect your dapp *** There are four components whose versions need to be kept compatible: 1. Aztec local network (includes the `aztec` command) 2. `Aztec.nr`, the Noir framework for writing Aztec contracts You can manage Aztec versions using `aztec-up`. Use `aztec-up install ` to install a specific version, or `aztec-up use ` to switch between installed versions. You need to update your Aztec.nr version manually or using `aztec update`. ## Updating Aztec.nr packages[​](#updating-aztecnr-packages "Direct link to Updating Aztec.nr packages") ### Automatic update[​](#automatic-update "Direct link to Automatic update") You can update your Aztec.nr packages to the appropriate version with the `aztec update` command. Run this command from the root of your project and pass the paths to the folders containing the Nargo.toml files for your projects like so: ``` aztec update . --contract src/contract1 --contract src/contract2 ``` ### Manual update[​](#manual-update "Direct link to Manual update") To update the aztec.nr packages manually, update the tags of the `aztec.nr` dependencies in the `Nargo.toml` file. ``` [dependencies] -aztec = { git="https://github.com/AztecProtocol/aztec-packages", tag="v0.7.5", directory="noir-projects/aztec-nr/aztec" } +aztec = { git="https://github.com/AztecProtocol/aztec-packages", tag="v4.3.1", directory="noir-projects/aztec-nr/aztec" } -value_note = { git="https://github.com/AztecProtocol/aztec-packages", tag="v0.7.5", directory="noir-projects/aztec-nr/value-note" } +value_note = { git="https://github.com/AztecProtocol/aztec-packages", tag="v4.3.1", directory="noir-projects/aztec-nr/value-note" } ``` Go to the contract directory and try compiling it to verify that the update was successful: ``` cd /your/contract/directory aztec compile # compiles the contract ``` If the dependencies fail to resolve ensure that the tag matches a tag in the [aztec-packages repository (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tags). ## Updating Aztec.js packages[​](#updating-aztecjs-packages "Direct link to Updating Aztec.js packages") To update Aztec.js packages, go to your `package.json` and replace the versions in the dependencies. ``` [dependencies] -"@aztec/accounts": "0.7.5", +"@aztec/accounts": "v4.3.1", -"@aztec/noir-contracts.js": "0.35.1", +"@aztec/accounts": "v4.3.1", ``` ## Local Network PXE Proving[​](#local-network-pxe-proving "Direct link to Local Network PXE Proving") The local network does not have client-side proving in the PXE enabled by default. This reduces testing times and increases development speed by allowing for rapid iteration. You may want to enable client-side proving in the local network to better understand how long it takes to execute Aztec transactions. There are 2 ways of doing this: 1. Run the local network in proving mode (every transaction wil be proved) or 2. Use `aztec-wallet` cli to prove a one-off transaction note Proving is much slower and should only be used sparingly to analyze real proving times of executing private functions of a contract. ### Local Network in Proving Mode[​](#local-network-in-proving-mode "Direct link to Local Network in Proving Mode") Here every transaction, contract deployment will be proved. If you want to just prove a single transaction, follow [proving with aztec-wallet cli](#proving-with-aztec-wallet). #### Usage[​](#usage "Direct link to Usage") To enable client-side proving: ``` PXE_PROVER_ENABLED=1 aztec start --local-network ``` The local network will take much longer to start. The first time it starts, it will need to download a large crs file, which can take several minutes even on a fast internet connection. This is a one-time operation, you will not need to download it again until you update to a new Aztec version. The local network will also deploy 3 Schnorr account contracts on startup. The local network will need to generate transaction proofs for deployment, which will take additional time. Once everything has been set up, you will see that the PXE is listening on `localhost:8080` as you would see with the local network running in the default mode. At this point you can use the local network as you would without client-side proving enabled. ### Proving with `aztec-wallet`[​](#proving-with-aztec-wallet "Direct link to proving-with-aztec-wallet") You can enable proving on a per-transaction basis using the `aztec-wallet` CLI by setting the `PXE_PROVER_ENABLED` environment variable to `1`. This will use your local `bb` binary to prove the transaction. ``` PXE_PROVER_ENABLED=1 aztec-wallet create-account -a test ``` Check the [Getting Started on Local Network](/developers/getting_started_on_local_network.md) for a refresher on how to send transactions using `aztec-wallet` or check the [reference here](/developers/docs/cli/aztec_wallet_cli_reference.md) Note that you do not need to restart the local network in order to start sending proven transactions. You can optionally set this for one-off transactions. If this is the first time you are sending transactions with proving enabled, it will take a while to download a CRS file (which is several MBs) that is required for proving. note You can also profile your transactions to get gate count, if you don't want to prove your transactions but check how many constraints it is. Follow the [guide here](/developers/docs/aztec-nr/framework-description/advanced/how_to_profile_transactions.md) You can learn more about custom commands in the [Aztec CLI Reference](/developers/docs/cli/aztec_cli_reference.md). --- # Testing Governance Rollup Upgrade on Local Network This guide walks through deploying a new rollup and executing a governance upgrade on a local Aztec network. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * [Aztec tooling](/developers/getting_started_on_local_network.md) * Node.js and yarn ## Local Network Governance Timing[​](#local-network-governance-timing "Direct link to Local Network Governance Timing") The default governance configuration for local networks: | Parameter | Value | Description | | -------------- | ---------------- | ------------------------------------------- | | votingDelay | 60 seconds | Time before voting starts | | votingDuration | 1 hour | Voting period length | | executionDelay | 60 seconds | Delay after voting ends before execution | | gracePeriod | 7 days | Window to execute after becoming executable | | lockDelay | 30 days | Token lock period for proposers | | lockAmount | 1,000,000 tokens | Tokens locked when proposing | *** ## Step 1: Start Local Network[​](#step-1-start-local-network "Direct link to Step 1: Start Local Network") Ensure you are on the correct Aztec version: ``` aztec-up install 4.3.1 ``` ``` aztec start --local-network ``` Wait for output showing deployed contract addresses. To get the **Registry Address** and other L1 contract addresses, query the running node: ``` curl -s http://localhost:8080 -X POST -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"node_getNodeInfo","params":[],"id":1}' | jq '.result.l1ContractAddresses' ``` Note the `registryAddress` from the output. *** ## Step 2: Clone and Set Up l1-contracts[​](#step-2-clone-and-set-up-l1-contracts "Direct link to Step 2: Clone and Set Up l1-contracts") Clone the l1-contracts repo and checkout the version matching your Aztec installation. Run `aztec --version` to find your version: ``` git clone https://github.com/AztecProtocol/l1-contracts.git cd l1-contracts git checkout 4.3.1 ``` Install dependencies and set up the build environment: ``` # Install forge dependencies mkdir -p lib cd lib git clone --depth 1 https://github.com/foundry-rs/forge-std forge-std git clone --depth 1 https://github.com/OpenZeppelin/openzeppelin-contracts openzeppelin-contracts cd .. # Install solc (uses forge's built-in svm). The Aztec installer ships # Foundry as `aztec-forge`/`aztec-cast`/`aztec-anvil` -- substitute your # own `forge` install if you have one. aztec-forge build --use 0.8.30 src/core/libraries/ConstantsGen.sol cp ~/.svm/0.8.30/solc-0.8.30 ./solc-0.8.30 # Copy the HonkVerifier to the generated directory (required for build) mkdir -p generated cp src/HonkVerifier.sol generated/HonkVerifier.sol echo '{}' > generated/default.json # Remove zkpassport-dependent files (not needed for rollup deployment) rm -f src/mock/StakingAssetHandler.sol rm -rf src/mock/staking_asset_handler/ ``` *** ## Step 3: Set Environment Variables[​](#step-3-set-environment-variables "Direct link to Step 3: Set Environment Variables") ``` # Anvil's default account 0 export PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 export DEPLOYER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 # Replace with actual address from Step 1 export REGISTRY_ADDRESS=0x... # L1 RPC export L1_RPC_URL=http://localhost:8545 export L1_CHAIN_ID=31337 # Rollup configuration (local network defaults) export AZTEC_SLOT_DURATION=36 export AZTEC_EPOCH_DURATION=16 export AZTEC_TARGET_COMMITTEE_SIZE=48 export AZTEC_LAG_IN_EPOCHS_FOR_VALIDATOR_SET=2 export AZTEC_LAG_IN_EPOCHS_FOR_RANDAO=2 export AZTEC_INBOX_LAG=2 export AZTEC_PROOF_SUBMISSION_EPOCHS=2 export AZTEC_LOCAL_EJECTION_THRESHOLD=0 export AZTEC_SLASHING_ROUND_SIZE_IN_EPOCHS=1 export AZTEC_SLASHING_LIFETIME_IN_ROUNDS=10 export AZTEC_SLASHING_EXECUTION_DELAY_IN_ROUNDS=1 export AZTEC_SLASHING_OFFSET_IN_ROUNDS=0 export AZTEC_SLASHER_FLAVOR=none export AZTEC_SLASHING_VETOER=0x0000000000000000000000000000000000000000 export AZTEC_SLASHING_DISABLE_DURATION=0 export AZTEC_MANA_TARGET=100000000 export AZTEC_EXIT_DELAY_SECONDS=0 export AZTEC_PROVING_COST_PER_MANA=0 export AZTEC_SLASH_AMOUNT_SMALL=0 export AZTEC_SLASH_AMOUNT_MEDIUM=0 export AZTEC_SLASH_AMOUNT_LARGE=0 export AZTEC_INITIAL_ETH_PER_FEE_ASSET=10000000 ``` *** ## Step 4: Deploy New Rollup[​](#step-4-deploy-new-rollup "Direct link to Step 4: Deploy New Rollup") ``` aztec-forge script script/deploy/DeployRollupForUpgrade.s.sol:DeployRollupForUpgrade \ --rpc-url $L1_RPC_URL \ --broadcast \ --private-key $PRIVATE_KEY ``` Note the **new rollup address** from the JSON output. ``` export NEW_ROLLUP_ADDRESS=0x... ``` *** ## Step 5: Deploy Governance Payload[​](#step-5-deploy-governance-payload "Direct link to Step 5: Deploy Governance Payload") **Important:** Place flags before the contract path to avoid argument parsing issues. ``` cd l1-contracts aztec-forge create \ --rpc-url $L1_RPC_URL \ --private-key $PRIVATE_KEY \ --broadcast \ test/governance/scenario/RegisterNewRollupVersionPayload.sol:RegisterNewRollupVersionPayload \ --constructor-args $REGISTRY_ADDRESS $NEW_ROLLUP_ADDRESS ``` Note the **payload address** from the output. ``` export PAYLOAD_ADDRESS=0x... ``` *** ## Step 6: Deposit Governance Tokens[​](#step-6-deposit-governance-tokens "Direct link to Step 6: Deposit Governance Tokens") Mint and deposit tokens to get voting power. You need at least 1,000,000 tokens (1e24 wei) to propose: ``` aztec deposit-governance-tokens \ -r $REGISTRY_ADDRESS \ --recipient $DEPLOYER_ADDRESS \ --amount "2000000000000000000000000" \ --mint \ --l1-rpc-urls $L1_RPC_URL \ -c $L1_CHAIN_ID \ --private-key $PRIVATE_KEY ``` *** ## Step 7: Advance Time for Token Checkpoint[​](#step-7-advance-time-for-token-checkpoint "Direct link to Step 7: Advance Time for Token Checkpoint") Critical Step Tokens must be deposited **before** the proposal is created. The governance contract snapshots voting power at the proposal creation timestamp. If your deposit checkpoint timestamp >= proposal creation timestamp, your voting power will be **0** and the proposal will be rejected. Advance Anvil's time to ensure the checkpoint is in the past when the proposal is created: ``` # Get current timestamp and add 120 seconds CURRENT_TS=$(cast block latest --rpc-url $L1_RPC_URL --json | jq -r '.timestamp') TARGET_TS=$((CURRENT_TS + 120)) cast rpc anvil_setNextBlockTimestamp $TARGET_TS --rpc-url $L1_RPC_URL cast rpc anvil_mine 1 --rpc-url $L1_RPC_URL ``` Verify the time has advanced: ``` NEW_TS=$(cast block latest --rpc-url $L1_RPC_URL --json | jq -r '.timestamp') echo "New timestamp: $NEW_TS (should be > $CURRENT_TS)" ``` note `anvil_increaseTime` may not reliably update block timestamps. For consistent results, always use `anvil_setNextBlockTimestamp` with an explicit timestamp. *** ## Step 8: Create Proposal[​](#step-8-create-proposal "Direct link to Step 8: Create Proposal") ``` aztec propose-with-lock \ -r $REGISTRY_ADDRESS \ -p $PAYLOAD_ADDRESS \ --l1-rpc-urls $L1_RPC_URL \ -c $L1_CHAIN_ID \ --private-key $PRIVATE_KEY \ --json ``` Note the **proposal ID** from output. ``` export PROPOSAL_ID=0 ``` *** ## Step 9: Advance Time Past Voting Delay[​](#step-9-advance-time-past-voting-delay "Direct link to Step 9: Advance Time Past Voting Delay") The proposal must transition from Pending to Active (votingDelay = 60 seconds): ``` # Get current timestamp and add 120 seconds (buffer over 60s voting delay) CURRENT_TS=$(cast block latest --rpc-url $L1_RPC_URL --json | jq -r '.timestamp') TARGET_TS=$((CURRENT_TS + 120)) cast rpc anvil_setNextBlockTimestamp $TARGET_TS --rpc-url $L1_RPC_URL cast rpc anvil_mine 1 --rpc-url $L1_RPC_URL ``` Verify the proposal is now Active (state 1): ``` # Get governance address from node info or use the one from Step 1 cast call "getProposalState(uint256)(uint8)" $PROPOSAL_ID --rpc-url $L1_RPC_URL # Expected output: 1 (Active) ``` *** ## Step 10: Vote on Proposal[​](#step-10-vote-on-proposal "Direct link to Step 10: Vote on Proposal") ``` aztec vote-on-governance-proposal \ -p $PROPOSAL_ID \ --in-favor yea \ --wait false \ -r $REGISTRY_ADDRESS \ --l1-rpc-urls $L1_RPC_URL \ -c $L1_CHAIN_ID \ --private-key $PRIVATE_KEY ``` Verify the vote was recorded with your voting power. The CLI output should show non-zero `summedBallot yea` values. If it shows `[0]`, your checkpoint timing was incorrect (see Troubleshooting). *** ## Step 11: Advance Time Past Voting Duration + Execution Delay[​](#step-11-advance-time-past-voting-duration--execution-delay "Direct link to Step 11: Advance Time Past Voting Duration + Execution Delay") Voting duration is 1 hour (3600s) and execution delay is 60 seconds: ``` # Get current timestamp and add 3700 seconds (voting duration + execution delay + buffer) CURRENT_TS=$(cast block latest --rpc-url $L1_RPC_URL --json | jq -r '.timestamp') TARGET_TS=$((CURRENT_TS + 3700)) cast rpc anvil_setNextBlockTimestamp $TARGET_TS --rpc-url $L1_RPC_URL cast rpc anvil_mine 1 --rpc-url $L1_RPC_URL ``` Verify the proposal is now Executable (state 3): ``` cast call "getProposalState(uint256)(uint8)" $PROPOSAL_ID --rpc-url $L1_RPC_URL # Expected output: 3 (Executable) ``` *** ## Step 12: Execute Proposal[​](#step-12-execute-proposal "Direct link to Step 12: Execute Proposal") ``` aztec execute-governance-proposal \ -p $PROPOSAL_ID \ -r $REGISTRY_ADDRESS \ --wait false \ --l1-rpc-urls $L1_RPC_URL \ -c $L1_CHAIN_ID \ --private-key $PRIVATE_KEY ``` ## Step 13: Verify the Upgrade[​](#step-13-verify-the-upgrade "Direct link to Step 13: Verify the Upgrade") Confirm the new rollup is now the canonical rollup: ``` # Check the canonical rollup address (should match NEW_ROLLUP_ADDRESS) cast call $REGISTRY_ADDRESS "getCanonicalRollup()(address)" --rpc-url $L1_RPC_URL # Check the number of rollup versions (should be 2) cast call $REGISTRY_ADDRESS "numberOfVersions()(uint256)" --rpc-url $L1_RPC_URL ``` *** ## Helper Commands[​](#helper-commands "Direct link to Helper Commands") ### Set Anvil timestamp directly[​](#set-anvil-timestamp-directly "Direct link to Set Anvil timestamp directly") If time advancement isn't working as expected, set the timestamp explicitly: ``` # Get the target timestamp (current + desired seconds) cast rpc anvil_setNextBlockTimestamp --rpc-url $L1_RPC_URL cast rpc anvil_mine 1 --rpc-url $L1_RPC_URL ``` ### Check proposal state[​](#check-proposal-state "Direct link to Check proposal state") ``` # States: 0=Pending, 1=Active, 2=Queued, 3=Executable, 4=Rejected, 5=Executed, 6=Dropped, 7=Expired cast call "getProposalState(uint256)(uint8)" $PROPOSAL_ID --rpc-url $L1_RPC_URL ``` ### Check current block timestamp[​](#check-current-block-timestamp "Direct link to Check current block timestamp") ``` cast block latest --rpc-url $L1_RPC_URL | grep timestamp ``` ### Check L1 addresses[​](#check-l1-addresses "Direct link to Check L1 addresses") ``` aztec get-l1-addresses \ -r $REGISTRY_ADDRESS \ -v canonical \ --l1-rpc-urls $L1_RPC_URL \ -c $L1_CHAIN_ID \ --json ``` ### Debug rollup state[​](#debug-rollup-state "Direct link to Debug rollup state") ``` aztec debug-rollup \ --rollup $NEW_ROLLUP_ADDRESS \ --l1-rpc-urls $L1_RPC_URL \ -c $L1_CHAIN_ID ``` The `--rollup` flag is required; without it the command may fail trying to resolve the default rollup address. *** ## Quick Test (Empty Payload)[​](#quick-test-empty-payload "Direct link to Quick Test (Empty Payload)") If you just want to test the governance flow without deploying a real rollup: ``` cd l1-contracts # Deploy empty payload (no constructor args needed) aztec-forge create \ --rpc-url $L1_RPC_URL \ --private-key $PRIVATE_KEY \ --broadcast \ test/governance/governance/TestPayloads.sol:EmptyPayload # Use the deployed address as PAYLOAD_ADDRESS and continue from Step 6 ``` *** ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### "Governance**CheckpointedUintLib**InsufficientValue"[​](#governancecheckpointeduintlibinsufficientvalue "Direct link to governancecheckpointeduintlibinsufficientvalue") * You need more tokens. The minimum to propose is 1,000,000 tokens (1e24 wei). * Deposit more tokens in Step 6. ### "Governance**CheckpointedUintLib**NotInPast"[​](#governancecheckpointeduintlibnotinpast "Direct link to governancecheckpointeduintlibnotinpast") * Tokens were deposited at or after the proposal creation time. * Advance Anvil's time and mine a block before creating the proposal (Step 7). ### "Proposal is not active"[​](#proposal-is-not-active "Direct link to \"Proposal is not active\"") * The voting delay hasn't passed yet. * Advance time past the votingDelay (60 seconds for local networks). ### "Proposal is not executable"[​](#proposal-is-not-executable "Direct link to \"Proposal is not executable\"") * Either voting period is not complete, or execution delay hasn't passed. * Advance time past votingDuration (1 hour) + executionDelay (60 seconds). ### Forge create fails with "Error accessing local wallet"[​](#forge-create-fails-with-error-accessing-local-wallet "Direct link to Forge create fails with \"Error accessing local wallet\"") * Constructor args may be parsing incorrectly. Place `--constructor-args` at the end of the command, after the contract path. ### Time advancement not working[​](#time-advancement-not-working "Direct link to Time advancement not working") * Anvil may have auto-mined blocks that reset the accumulated time. * Use `anvil_setNextBlockTimestamp` to set an explicit timestamp instead of `anvil_increaseTime`. ### Vote fails without explicit amount[​](#vote-fails-without-explicit-amount "Direct link to Vote fails without explicit amount") * If you see `NotInPast` errors during voting, the CLI may have a bug determining voting power. * Workaround: specify `--vote-amount` explicitly with your deposited token amount. --- # Getting Started on Local Network Get started on your local environment using a local network. If you'd rather deploy to a live network, read the [getting started on testnet guide](/developers/getting_started_on_testnet.md). The local network is a local development Aztec network running fully on your machine, and interacting with a development Ethereum node. You can develop and deploy on it just like on a testnet or mainnet (when the time comes). The local network makes it faster and easier to develop and test your Aztec applications. What's included in the local network: * Local Ethereum network (Anvil) * Deployed Aztec protocol contracts (for L1 and L2) * A set of test accounts with some test tokens to pay fees * Development tools to compile contracts and interact with the network (`aztec` and `aztec-wallet`) This guide will teach you how to install the Aztec local network, run it using the Aztec CLI, and interact with contracts using the wallet CLI. To jump right into the testnet instead, click the `Testnet` tab. To see the whole flow before you start, watch this one-minute walkthrough (find more on the [video lessons](/developers/docs/resources/video_lessons.md) page): [Get Started on Aztec in Under 60 Seconds](https://www.youtube-nocookie.com/embed/_jgHNdNgFOg) ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Aztec libraries require Node.js version 24. If you have an older version installed, the installer will try to upgrade via [nvm](https://github.com/nvm-sh/nvm) if available. If nvm is not installed, you will need to upgrade Node.js manually (e.g. `nvm install 24` after installing nvm). ### macOS-specific requirements[​](#macos-specific-requirements "Direct link to macOS-specific requirements") * **Homebrew**: [Homebrew](https://brew.sh/) is required for installing dependencies on macOS. * **Bash**: macOS ships with an outdated version of Bash (v3.2) that is known to cause issues with the Aztec installer. Install a modern version with `brew install bash`. Even if you use zsh as your default shell, the installer explicitly invokes `bash`. If the installer still picks up the old version, add the Homebrew `bash` to your `$PATH` or [set it as your default shell](https://support.apple.com/en-gb/guide/terminal/trml113/mac). ## Install and run the local network[​](#install-and-run-the-local-network "Direct link to Install and run the local network") ### Install the Aztec toolchain[​](#install-the-aztec-toolchain "Direct link to Install the Aztec toolchain") Run: ``` VERSION=4.3.1 bash -i <(curl -sL https://install.aztec.network) ``` This will install the following tools and add them to your `PATH`: * **aztec** - compiles and tests Aztec contracts and launches various infrastructure subsystems (full local network, sequencer, prover, PXE, etc.) and provides utility commands to interact with the network * **aztec-up** - a version manager for the Aztec toolchain. Use `aztec-up install ` to install a new version, `aztec-up use ` to switch between installed versions, or `aztec-up list` to see installed versions. * **aztec-wallet** - a tool for interacting with the Aztec network * **aztec-bb** - the Barretenberg proving backend * **aztec-nargo** - the Noir compiler and simulator * **aztec-forge**, **aztec-cast**, **aztec-anvil**, **aztec-chisel** - the bundled Foundry tools Foundry, Noir, and Barretenberg are bundled at the versions `aztec` needs. Your own `forge` / `nargo` / `bb` installs still work under their bare names. For syntax highlighting and LSP support while editing contracts, see the [Noir VSCode Extension guide](/developers/docs/aztec-nr/installation.md). ### Start the local network[​](#start-the-local-network "Direct link to Start the local network") Once these have been installed, to start the local network, run: ``` aztec start --local-network ``` **Congratulations, you have just installed and run the Aztec local network!** ``` /\ | | / \ ___| |_ ___ ___ / /\ \ |_ / __/ _ \/ __| / ____ \ / /| || __/ (__ /_/___ \_\/___|\__\___|\___| ``` In the terminal, you will see some logs: 1. Local network version 2. Contract addresses of rollup contracts 3. PXE (private execution environment) setup logs 4. Initial accounts that are shipped with the local network and can be used in tests You'll know the local network is ready to go when you see something like this: ``` [INFO] Aztec Server listening on port 8080 ``` ## Using the local network test accounts[​](#using-the-local-network-test-accounts "Direct link to Using the local network test accounts") For convenience, the local network comes with 3 initial accounts that are prefunded, helping bootstrap payment of any transaction. To use them, you will need to add them to your pxe/wallet. To add the test accounts in the wallet, run this in another terminal: ``` aztec-wallet import-test-accounts ``` We'll use the first test account, `test0`, throughout to pay for transactions. ## Creating an account in the local network[​](#creating-an-account-in-the-local-network "Direct link to Creating an account in the local network") ``` aztec-wallet create-account -a my-wallet -f test0 ``` info `aztec-wallet` will generate transaction proofs by default. This is not required when sending transactions on the local network, but it is required when sending transactions on the devnet or mainnet. You can turn off proof generation by adding the `--prover none` flag to the command or setting `PXE_PROVER=none`. This will create a new wallet with an account and give it the alias `my-wallet`. Accounts can be referenced with `accounts:`. You will see logs telling you the address, public key, secret key, and more. On successful deployment of the account, you should see something like this: ``` New account: Address: 0x066108a2398e3e2ff53ec4b502e4c2e778c6de91bb889de103d5b4567530d99c Public key: 0x007343da506ea513e6c05ba4d5e92e3c682333d97447d45db357d05a28df0656181e47a6257e644c3277c0b11223b28f2b36c94f9b0a954523de61ac967b42662b60e402f55e3b7384ba61261335040fe4cd52cb0383f559a36eeea304daf67d1645b06c38ee6098f90858b21b90129e7e1fdc4666dd58d13ef8fab845b2211906656d11b257feee0e91a42cb28f46b80aabdc70baad50eaa6bb2c5a7acff4e30b5036e1eb8bdf96fad3c81e63836b8aa39759d11e1637bd71e3fc76e3119e500fbcc1a22e61df8f060004104c5a75b52a1b939d0f315ac29013e2f908ca6bc50529a5c4a2604c754d52c9e7e3dee158be21b7e8008e950991174e2765740f58 Secret key: 0x1c94f8b19e91d23fd3ab6e15f7891fde7ba7cae01d3fa94e4c6afb4006ec0cfb Partial address: 0x2fd6b540a6bb129dd2c05ff91a9c981fb5aa2ac8beb4268f10b3aa5fb4a0fcd1 Salt: 0x0000000000000000000000000000000000000000000000000000000000000000 Init hash: 0x28df95b579a365e232e1c63316375c45a16f6a6191af86c5606c31a940262db2 Deployer: 0x0000000000000000000000000000000000000000000000000000000000000000 Waiting for account contract deployment... Deploy tx hash: 0a632ded6269bda38ad6b54cd49bef033078218b4484b902e326c30ce9dc6a36 Deploy tx fee: 200013616 Account stored in database with aliases last & my-wallet ``` You may need to scroll up as there are some other logs printed after it. You can double check by running `aztec-wallet get-alias accounts:my-wallet`. For simplicity we'll keep using the test account, let's deploy our own test token! ## Deploying a contract[​](#deploying-a-contract "Direct link to Deploying a contract") The local network comes with some contracts that you can deploy and play with. One of these is an example token contract. Deploy it with this: ``` aztec-wallet deploy TokenContractArtifact --from accounts:test0 --args accounts:test0 TestToken TST 18 -a testtoken ``` This takes * the contract artifact as the argument, which is `TokenContractArtifact` * the deployer account, which we used `test0` * the args that the contract constructor takes, which is the `admin` (`accounts:test0`), `name` (`TestToken`), `symbol` (`TST`), and `decimals` (`18`). * an alias `testtoken` (`-a`) so we can easily reference it later with `contracts:testtoken` On successful deployment, you should see something like this: ``` aztec:wallet [INFO] Using wallet with address 0x066108a2398e3e2ff53ec4b502e4c2e778c6de91bb889de103d5b4567530d99c +0ms Contract deployed at 0x15ce68d4be65819fe9c335132f10643b725a9ebc7d86fb22871f6eb8bdbc3abd Contract partial address 0x25a91e546590d77108d7b184cb81b0a0999e8c0816da1a83a2fa6903480ea138 Contract init hash 0x0abbaf0570bf684da355bd9a9a4b175548be6999625b9c8e0e9775d140c78506 Deployment tx hash: 0a8ccd1f4e28092a8fa4d1cb85ef877f8533935c4e94b352a38af73eee17944f Deployment salt: 0x266295eb5da322aba96fbb24f9de10b2ba01575dde846b806f884f749d416707 Deployment fee: 200943060 Contract stored in database with aliases last & testtoken ``` In the next step, let's mint some tokens! ## Minting public tokens[​](#minting-public-tokens "Direct link to Minting public tokens") Call the public mint function like this: ``` aztec-wallet send mint_to_public --from accounts:test0 --contract-address contracts:testtoken --args accounts:test0 100 ``` This takes * the function name as the argument, which is `mint_to_public` * the `from` account (caller) which is `accounts:test0` * the contract address, which is aliased as `contracts:testtoken` (or simply `testtoken`) * the args that the function takes, which is the account to mint the tokens into (`test0`), and `amount` (`100`). This only works because we are using the secret key of the admin who has permissions to mint. A successful call should print something like this: ``` aztec:wallet [INFO] Using wallet with address 0x066108a2398e3e2ff53ec4b502e4c2e778c6de91bb889de103d5b4567530d99c +0ms Maximum total tx fee: 1161660 Estimated total tx fee: 116166 Estimated gas usage: da=1127,l2=115039,teardownDA=0,teardownL2=0 Transaction hash: 2ac383e8e2b68216cda154b52e940207a905c1c38dadba7a103c81caacec403d Transaction has been mined Tx fee: 200106180 Status: success Block number: 17 Block hash: 1e27d200600bc45ab94d467c230490808d1e7d64f5ee6cee5e94a08ee9580809 Transaction hash stored in database with aliases last & mint_to_public-9044 ``` You can double-check by calling the function that checks your public account balance: ``` aztec-wallet simulate balance_of_public --from test0 --contract-address testtoken --args accounts:test0 ``` This should print ``` Simulation result: 100n ``` ## Playing with hybrid state and private functions[​](#playing-with-hybrid-state-and-private-functions "Direct link to Playing with hybrid state and private functions") In the following steps, we'll move some tokens from public to private state and check our private and public balance. ``` aztec-wallet send transfer_to_private --from accounts:test0 --contract-address testtoken --args accounts:test0 25 ``` The arguments for `transfer_to_private` function are: * the account address to transfer to * the amount of tokens to send to private A successful call should print something similar to what you've seen before. Now when you call `balance_of_public` again you will see 75! ``` aztec-wallet simulate balance_of_public --from test0 --contract-address testtoken --args accounts:test0 ``` This should print ``` Simulation result: 75n ``` And then call `balance_of_private` to check that you have your tokens! ``` aztec-wallet simulate balance_of_private --from test0 --contract-address testtoken --args accounts:test0 ``` This should print ``` Simulation result: 25n ``` **Congratulations, you now know the fundamentals of working with the Aztec local network!** You are ready to move onto the more fun stuff. ## What's next?[​](#whats-next "Direct link to What's next?") Want to build something cool on Aztec? * Check out the [Token Contract Tutorial](/developers/docs/tutorials/contract_tutorials/token_contract.md) for a beginner tutorial, or jump into more advanced ones * Ready for a live network? Try [deploying on testnet](/developers/getting_started_on_testnet.md) * Start on your own thing and check out the How To Guides to help you! --- # Getting Started on Testnet This guide walks you through deploying your first contract on the Aztec testnet. You will install the CLI tools, create an account using the Sponsored FPC (so you don't need to bridge Fee Juice yourself), and deploy and interact with a contract. ## Testnet vs Local Network[​](#testnet-vs-local-network "Direct link to Testnet vs Local Network") | Feature | Local Network | Testnet | | --------------- | ------------------------------ | -------------------------------- | | **Environment** | Local machine | Decentralized network on Sepolia | | **Fees** | Free (test accounts prefunded) | Sponsored FPC available | | **Block times** | Instant | \~36 seconds | | **Proving** | Optional | Required | | **Accounts** | Test accounts pre-deployed | Must create and deploy your own | info If you want to develop and iterate quickly, start with the [local network guide](/developers/getting_started_on_local_network.md). The local network has instant blocks and no proving, making it faster for development. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Aztec libraries require Node.js version 24. If you have an older version installed, the installer will try to upgrade via [nvm](https://github.com/nvm-sh/nvm) if available. If nvm is not installed, you will need to upgrade Node.js manually (e.g. `nvm install 24` after installing nvm). ## Install the Aztec toolchain[​](#install-the-aztec-toolchain "Direct link to Install the Aztec toolchain") Install the testnet version of the Aztec CLI: ``` VERSION=5.0.0-rc.2 bash -i <(curl -sL https://install.aztec.network/5.0.0-rc.2) ``` warning Testnet is version-dependent. It is currently running version `5.0.0-rc.2`. Maintain version consistency when interacting with the testnet to avoid errors. This installs: * **aztec** - Compiles and tests Aztec contracts, launches infrastructure, and provides utility commands * **aztec-up** - Version manager for the Aztec toolchain (`aztec-up install`, `aztec-up use`, `aztec-up list`) * **aztec-wallet** - CLI tool for interacting with the Aztec network ## Getting started on testnet[​](#getting-started-on-testnet "Direct link to Getting started on testnet") ### Step 1: Set up your environment[​](#step-1-set-up-your-environment "Direct link to Step 1: Set up your environment") Set the required environment variables: ``` export NODE_URL=https://v5.testnet.rpc.aztec-labs.com export SPONSORED_FPC_ADDRESS=0x1969946536f0c09269e2c75e414eef4e21a76e763c5514125208db33d7d944d7 ``` ### Step 2: Register the Sponsored FPC[​](#step-2-register-the-sponsored-fpc "Direct link to Step 2: Register the Sponsored FPC") The Sponsored FPC (Fee Payment Contract) pays transaction fees on your behalf, so you don't need to bridge Fee Juice from L1. Register it in your wallet: ``` aztec-wallet register-contract \ --node-url $NODE_URL \ --alias sponsoredfpc \ $SPONSORED_FPC_ADDRESS SponsoredFPC \ --salt 0 ``` ### Step 3: Create and deploy an account[​](#step-3-create-and-deploy-an-account "Direct link to Step 3: Create and deploy an account") Unlike the local network, testnet has no pre-deployed accounts. Create and deploy your own: ``` aztec-wallet create-account \ --node-url $NODE_URL \ --alias my-wallet \ --payment method=fpc-sponsored,fpc=$SPONSORED_FPC_ADDRESS ``` note The first transaction will take longer as it downloads proving keys. If you see `Timeout awaiting isMined`, the transaction is still processing — this is normal on testnet. ### Step 4: Deploy a contract[​](#step-4-deploy-a-contract "Direct link to Step 4: Deploy a contract") Deploy a token contract as an example: ``` aztec-wallet deploy \ --node-url $NODE_URL \ --from accounts:my-wallet \ --payment method=fpc-sponsored,fpc=$SPONSORED_FPC_ADDRESS \ --alias token \ TokenContract \ --args accounts:my-wallet Token TOK 18 ``` This deploys the `TokenContract` with: * `admin`: your wallet address * `name`: Token * `symbol`: TOK * `decimals`: 18 You can check the transaction status on [Aztecscan](https://testnet.aztecscan.xyz). ### Step 5: Interact with your contract[​](#step-5-interact-with-your-contract "Direct link to Step 5: Interact with your contract") Mint some tokens: ``` aztec-wallet send mint_to_public \ --node-url $NODE_URL \ --from accounts:my-wallet \ --payment method=fpc-sponsored,fpc=$SPONSORED_FPC_ADDRESS \ --contract-address token \ --args accounts:my-wallet 100 ``` Check your balance: ``` aztec-wallet simulate balance_of_public \ --node-url $NODE_URL \ --from accounts:my-wallet \ --contract-address token \ --args accounts:my-wallet ``` This should print: ``` Simulation result: 100n ``` Move tokens to private state: ``` aztec-wallet send transfer_to_private \ --node-url $NODE_URL \ --from accounts:my-wallet \ --payment method=fpc-sponsored,fpc=$SPONSORED_FPC_ADDRESS \ --contract-address token \ --args accounts:my-wallet 25 ``` Check your private balance: ``` aztec-wallet simulate balance_of_private \ --node-url $NODE_URL \ --from accounts:my-wallet \ --contract-address token \ --args accounts:my-wallet ``` This should print: ``` Simulation result: 25n ``` ## Viewing transactions on the block explorer[​](#viewing-transactions-on-the-block-explorer "Direct link to Viewing transactions on the block explorer") You can view your transactions, contracts, and account on the testnet block explorers: * [Aztecscan](https://testnet.aztecscan.xyz) * [Aztec Explorer](https://aztecexplorer.xyz/?network=testnet) Search by transaction hash, contract address, or account address to see details and status. ## Registering existing contracts[​](#registering-existing-contracts "Direct link to Registering existing contracts") To interact with a contract deployed by someone else, you need to register it in your local PXE first: ``` aztec-wallet register-contract \ --node-url $NODE_URL \ --alias mycontract \ ``` For example, to register a `TokenContract` deployed by someone else: ``` aztec-wallet register-contract \ --node-url $NODE_URL \ --alias external-token \ 0x1234...abcd TokenContract ``` After registration, you can interact with it using `aztec-wallet send` and `aztec-wallet simulate` as shown above. ## Paying fees without the Sponsored FPC[​](#paying-fees-without-the-sponsored-fpc "Direct link to Paying fees without the Sponsored FPC") The Sponsored FPC is convenient for getting started, but you can also pay fees directly by bridging Fee Juice from Ethereum Sepolia. See [Paying Fees](/developers/docs/aztec-js/how_to_pay_fees.md#bridge-fee-juice-from-l1) for details on bridging and other fee payment methods. ## Getting Fee Juice from the faucet[​](#getting-fee-juice-from-the-faucet "Direct link to Getting Fee Juice from the faucet") If you want to pay fees directly instead of using the Sponsored FPC, you can request **Fee Juice** from the testnet faucet: * [Aztec Fee Juice Faucet](https://aztec-faucet.nethermind.io/) - dispenses testnet Fee Juice to your account Fee Juice is not the AZTEC token This faucet dispenses **Fee Juice**, the asset used to pay transaction fees (gas) on Aztec. Fee Juice lives on Aztec (L2) and is only used to pay fees. It is **not** the AZTEC token, which is a separate asset that lives on Ethereum (L1). This faucet does not dispense AZTEC tokens. ## Testnet information[​](#testnet-information "Direct link to Testnet information") For complete testnet technical details including contract addresses and network configuration, see the [Networks page](/networks.md#testnet). ## Next steps[​](#next-steps "Direct link to Next steps") * Check out the [Tutorials](/developers/docs/tutorials/contract_tutorials/counter_contract.md) for building more complex contracts * Learn about [paying fees](/developers/docs/aztec-js/how_to_pay_fees.md) with different methods * Explore [Aztec Playground](https://play.aztec.network/) for an interactive development experience --- # Aztec Overview This page outlines Aztec's fundamental technical concepts. It is recommended to read this before diving into building on Aztec. ## What is Aztec?[​](#what-is-aztec "Direct link to What is Aztec?") Aztec is a privacy-first Layer 2 on Ethereum. It supports smart contracts with both private & public state and private & public execution. ![](/assets/ideal-img/Aztec_overview.4d3e9fb.640.png) ## Getting started[​](#getting-started "Direct link to Getting started") Learn about Aztec, what it is, how it works and how to get start writing smart contracts on Aztec with programmable privacy by watching this video course: [Aztec Video Course](https://www.youtube.com/embed/cQIPG_J1W9g) ## High level view[​](#high-level-view "Direct link to High level view") ![](/assets/ideal-img/aztec-high-level.4ac0d53.640.png) 1. A user interacts with Aztec through Aztec.js (like web3js or ethersjs) 2. Private functions are executed in the PXE, which is client-side 3. Proofs and tree updates are sent to the Public VM (running on an Aztec node) 4. Public functions are executed in the Public VM 5. The Public VM rolls up the transactions that include private and public state updates into blocks 6. The block data and proof of a correct state transition are submitted to Ethereum for verification ## Private and public execution[​](#private-and-public-execution "Direct link to Private and public execution") Private functions are executed client side, on user devices to maintain maximum privacy. Public functions are executed by a remote network of nodes, similar to other blockchains. These distinct execution environments create a directional execution flow for a single transaction--a transaction begins in the private context on the user's device then moves to the public network. This means that private functions executed by a transaction can enqueue public functions to be executed later in the transaction life cycle, but public functions cannot call private functions. ### Private Execution Environment (PXE)[​](#private-execution-environment-pxe "Direct link to Private Execution Environment (PXE)") Private functions are executed on the user's device in the Private Execution Environment (PXE, pronounced 'pixie'), then it generates proofs for onchain verification. It is a client-side library for execution and proof-generation of private operations. It holds keys, notes, and generates proofs. It is included in aztec.js, a TypeScript library, and can be run within Node or the browser. Note: It is easy for private functions to be written in a detrimentally unoptimized way, because many intuitions of regular program execution do not apply to proving. For more about writing performant private functions in Noir, see [this page](https://noir-lang.org/docs/explainers/explainer-writing-noir) of the Noir documentation. ### Aztec Virtual Machine (AVM)[​](#aztec-virtual-machine-avm "Direct link to Aztec Virtual Machine (AVM)") Public functions are executed by the Aztec Virtual Machine (AVM), which is conceptually similar to the Ethereum Virtual Machine (EVM). As such, writing efficient public functions follow the same intuition as gas-efficient solidity contracts. The PXE is unaware of the Public VM. And the Public VM is unaware of the PXE. They are completely separate execution environments. This means: * The PXE and the Public VM cannot directly communicate with each other * Private transactions in the PXE are executed first, followed by public transactions ## Private and public state[​](#private-and-public-state "Direct link to Private and public state") Private state works with UTXOs, which are chunks of data that we call notes. To keep things private, notes are stored in an [append-only UTXO tree](/developers/docs/foundational-topics/advanced/storage/indexed_merkle_tree.md), and a nullifier is created when notes are invalidated (aka deleted). Nullifiers are stored in their own [nullifier tree](/developers/docs/foundational-topics/advanced/storage/indexed_merkle_tree.md). Public state works similarly to other chains like Ethereum, behaving like a public ledger. Public data is stored in a public data tree. ![Public vs private state](/assets/images/public-and-private-state-diagram-ff88262b40b259d4fe4c8b7d667924aa.png) Aztec [smart contract](/developers/docs/aztec-nr/framework-description/contract_structure.md) developers should keep in mind that different data types are used when manipulating private or public state. Working with private state is creating commitments and nullifiers to state, whereas working with public state is directly updating state. ## Accounts and keys[​](#accounts-and-keys "Direct link to Accounts and keys") ### Account abstraction[​](#account-abstraction "Direct link to Account abstraction") Every account in Aztec is a smart contract (account abstraction). This allows implementing different schemes for authorizing transactions, nonce management, and fee payments. Developers can write their own account contract to define the rules by which user transactions are authorized and paid for, as well as how user keys are managed. Learn more about account contracts [here](/developers/docs/foundational-topics/accounts.md). ### Key pairs[​](#key-pairs "Direct link to Key pairs") Each account in Aztec is backed by 3 key pairs: * A **nullifier key pair** used for note nullifier computation * A **incoming viewing key pair** used to encrypt a note for the recipient * A **outgoing viewing key pair** used to encrypt a note for the sender As Aztec has native account abstraction, accounts do not automatically have a signing key pair to authenticate transactions. This is up to the account contract developer to implement. ## Noir[​](#noir "Direct link to Noir") Noir is a zero-knowledge domain specific language used for writing smart contracts for the Aztec network. It is also possible to write circuits with Noir that can be verified on or offchain. For more in-depth docs into the features of Noir, go to the [Noir website](https://noir-lang.org/). --- # Support This page tells you where to go when something does not work, when you want to file a bug, when you have a feature idea, or when you have found a possible security issue. Pick the section that matches your situation. Security issues are different If your issue involves loss of funds, key or seed disclosure, leakage of private notes, a way to forge or replay transactions, or anything you suspect could harm users, **do not open a public GitHub issue or post in Discord**. Use the [security disclosure process](#security-issues) instead. ## Quick decision tree[​](#quick-decision-tree "Direct link to Quick decision tree") | Your situation | Where to go | | ------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------- | | Possible security issue (funds, keys, privacy, exploit) | [Security disclosure](#security-issues) | | You are not sure if the bug is real, or you cannot reproduce it yet | [Ask first: Forum or Discord](#ask-first-forum-and-discord) | | You have a reproducible bug in `aztec-packages` (PXE, aztec.js, aztec-nr, local network, CLI, AVM, barretenberg, L1 contracts) | [File a bug on GitHub](#file-a-bug) | | You have a Noir compiler or language bug | [Noir issues on `noir-lang/noir`](https://github.com/noir-lang/noir/issues) | | You have a feature request or enhancement idea | [File a feature request](#feature-requests) | | You are running a node, sequencer, or prover and hit an operator problem | [Operator support](#operator-and-node-issues) | | You want to suggest a documentation change | [File a docs issue](#documentation-issues) | ## Ask first: Forum and Discord[​](#ask-first-forum-and-discord "Direct link to Ask first: Forum and Discord") If you are not yet sure whether your problem is a real bug or a configuration issue, start in the community channels. You can often get a faster answer there, and the team can help you build a minimal reproduction before you open a GitHub issue. * [Noir Discord](https://discord.com/invite/JtqzkdeQ6G): the developer-focused channel for syntax issues, compiler questions, and language-level help with Aztec.nr contracts. * [Aztec Forum](https://forum.aztec.network): long-form Q\&A, best for design discussions, complex bug reports, and conversations you want indexed and searchable. * [Aztec Discord](https://discord.gg/aztec): the broader Aztec community space, and the live support channel for node operators (sequencers, provers, RPC nodes). Once you have a clear reproduction, the right next step is to file a GitHub issue using the form below. ## File a bug[​](#file-a-bug "Direct link to File a bug") Reproducible bugs in the Aztec stack belong on GitHub. Use the bug template, which automatically labels your issue and helps a maintainer triage it. [Open a new bug report](https://github.com/AztecProtocol/aztec-packages/issues/new?template=bug_report.yml) ### What a high-quality bug report includes[​](#what-a-high-quality-bug-report-includes "Direct link to What a high-quality bug report includes") The template asks for these, and your issue will be triaged faster if you provide all of them. 1. **Aztec version**, for example `0.85.0-alpha-testnet.2`. Use `aztec-up list` if you are not sure. 2. **What you were trying to do**, in one or two sentences. 3. **A minimal, runnable reproduction**. A code snippet that compiles, or a link to a public repo branch, is much more useful than prose. If your reproduction is large, please trim it before filing. 4. **Expected vs. actual behavior**. 5. **Environment**: operating system, Node.js version, and browser if relevant. 6. **Logs and errors**: the failing block, ideally with `LOG_LEVEL=debug` for the module that failed. 7. **What you already tried**: workarounds, version downgrades, related issues you read. ### Where bugs in specific components go[​](#where-bugs-in-specific-components-go "Direct link to Where bugs in specific components go") All of the components below live in [`AztecProtocol/aztec-packages`](https://github.com/AztecProtocol/aztec-packages), so use the bug template above and let triage attach the component label. * **PXE, wallet, CLI**: `aztec`, `aztec-wallet`, `aztec.js`, `bb.js`, local network. * **Aztec.nr framework**: the `aztec-nr` smart contract framework. * **Protocol circuits or protocol specs**: the rollup, kernels, and other circuits. * **Barretenberg, AVM, L1 contracts**: the prover backend, the Aztec Virtual Machine, and the Ethereum-side rollup contracts. For the **Noir compiler** or the Noir language itself, file on [`noir-lang/noir`](https://github.com/noir-lang/noir/issues) instead. ## Feature requests[​](#feature-requests "Direct link to Feature requests") Use the feature request template for new functionality, enhancements to existing components, or proposed changes to the developer experience. [Open a feature request](https://github.com/AztecProtocol/aztec-packages/issues/new?template=feature_request.yml) Include: * The problem you are trying to solve. * A concrete example or use case, if you have one. * Why this is impactful: what is unblocked or made easier if it ships. ## Documentation issues[​](#documentation-issues "Direct link to Documentation issues") If something in the docs is wrong, outdated, or missing, please **[open an issue](https://github.com/AztecProtocol/aztec-packages/issues/new?template=bug_report.yml)** with the bug template and quote the URL and paragraph that needs fixing. Docs issues are routed to [`@AztecProtocol/devrel`](https://github.com/orgs/AztecProtocol/teams/devrel) so a docs maintainer can pick them up. This includes typos and small text issues: please file them as issues rather than opening single-line PRs. A maintainer can fix several at once, which is faster to review than a stream of one-line pull requests. For larger restructures or new pages, open an issue first so a maintainer can confirm the direction before you write the change. Every docs page in this site has an "Edit this page" link at the bottom that takes you to the right file in [`docs-developers/`](https://github.com/AztecProtocol/aztec-packages/tree/next/docs/docs-developers) once the direction is agreed. ## Operator and node issues[​](#operator-and-node-issues "Direct link to Operator and node issues") For real-time help, the [Aztec Discord](https://discord.gg/aztec) is the fastest way to reach other operators and the Aztec team. Use it to sanity-check a failure mode before filing, or to coordinate with the team during an incident. If you are running a node, sequencer, or prover and you hit a reproducible operational problem (sync failures, missed proposals, prover crashes, deployment errors), [open a bug report](https://github.com/AztecProtocol/aztec-packages/issues/new?template=bug_report.yml) and include the following alongside the standard bug-report fields: * **Network** (mainnet, testnet, devnet, or local). * **Role** (sequencer, prover, RPC node). * **Block height at failure** and approximate UTC timestamp. * **Hardware**: CPU model, RAM, disk type and size. * **Container runtime and image version**. * **Configuration** (your `config.json` or environment block, with secrets redacted). * **Logs**: the last few hundred lines around the failure, with sensitive keys redacted. Operator-specific guides live in the [Operate section](/operate/operators.md). ## Security issues[​](#security-issues "Direct link to Security issues") **Do not open a public GitHub issue for a suspected vulnerability.** Public disclosure can put users at risk before a fix is available. Use one of the following, in order of preference: 1. **[Aztec Network Bug Bounty on Cantina](https://cantina.xyz/bounties/80e74370-10d8-4e52-8e4b-7294deb7c9ee)** if the issue is in scope of the bounty program. 2. **[GitHub Private Vulnerability Reporting (PVR)](https://github.com/AztecProtocol/aztec-packages/security/advisories/new)** for any other suspected vulnerability. Go to the "Security" tab of the repository and click "Report a vulnerability". 3. **Email `security@aztec.foundation`** if neither Cantina nor PVR is available to you. Send a brief impact summary first, without exploit details or reproduction steps, and wait for the team to confirm a secure channel before sharing them. If you believe a vulnerability is being actively exploited or has severe impact (loss of funds, key compromise, or broad user impact), mark the report as **CRITICAL** in the PVR or email subject. See the full [security policy](https://github.com/AztecProtocol/aztec-packages/blob/next/SECURITY.md) for more. ## What happens after you file[​](#what-happens-after-you-file "Direct link to What happens after you file") When you file a GitHub issue using one of the templates above, it is automatically tagged so the team can triage it. A maintainer will: 1. Confirm the component the issue belongs to. 2. Set a priority based on impact. 3. Ask follow-up questions if the report is missing a reproduction or context. 4. Route the issue to the owning team. The fastest way to a fix is a small, runnable reproduction. If you can attach one, please do. ## See also[​](#see-also "Direct link to See also") * [`CONTRIBUTING.md`](https://github.com/AztecProtocol/aztec-packages/blob/next/CONTRIBUTING.md) for contribution guidelines. * [`SECURITY.md`](https://github.com/AztecProtocol/aztec-packages/blob/next/SECURITY.md) for the full security disclosure policy. * The [Aztec project board](https://github.com/orgs/AztecProtocol/projects/22) for in-flight work. --- # Operating Aztec Infrastructure This section covers everything you need to run and maintain Aztec network infrastructure. Whether you're running a full node for personal use or operating a professional sequencer, you'll find the guides you need here. ## Getting Started[​](#getting-started "Direct link to Getting Started") 1. Review the [Prerequisites](/operate/testnet/operators/prerequisites.md) to ensure you have the necessary hardware and software 2. [Run a Full Node](/operate/testnet/operators/setup/running_a_node.md) - the foundation for all other roles 3. Choose your path: [Sequencer](/operate/testnet/operators/setup/sequencer_management.md) or [Prover](/operate/testnet/operators/setup/running_a_prover.md) ## Roles[​](#roles "Direct link to Roles") ### Full Node Operator[​](#full-node-operator "Direct link to Full Node Operator") Run a node to interact with the network, submit transactions, and maintain a copy of the state. * [Running a Node](/operate/testnet/operators/setup/running_a_node.md) * [Syncing Best Practices](/operate/testnet/operators/setup/syncing_best_practices.md) ### Sequencer Operator[​](#sequencer-operator "Direct link to Sequencer Operator") Produce blocks, participate in consensus, and earn rewards. * [Sequencer Setup](/operate/testnet/operators/setup/sequencer_management.md) * [Registration](/operate/testnet/operators/setup/registering_sequencer.md) * [Governance Participation](/operate/testnet/operators/sequencer-management/creating_and_voting_on_proposals.md) ### Prover Operator[​](#prover-operator "Direct link to Prover Operator") Generate cryptographic proofs for the network. * [Running a Prover](/operate/testnet/operators/setup/running_a_prover.md) ### Staking Provider[​](#staking-provider "Direct link to Staking Provider") Accept delegated stake and operate sequencers on behalf of token holders. * [Becoming a Staking Provider](/operate/testnet/operators/setup/become_a_staking_provider.md) ## Operations[​](#operations "Direct link to Operations") * [Monitoring](/operate/testnet/operators/monitoring.md) - Set up observability for your infrastructure * [Keystore Management](/operate/testnet/operators/keystore.md) - Secure key handling * [Sequencer Management](/operate/testnet/operators/sequencer-management.md) - Day-to-day operations ## Reference[​](#reference "Direct link to Reference") * [CLI Reference](/operate/testnet/operators/reference/cli-reference.md) * [Node API Reference](/operate/testnet/operators/reference/node_api_reference.md) * [Changelog](/operate/testnet/operators/reference/changelog.md) *** Conceptual Background For background on how the network works, see the [Participate section](/participate.md). --- # Advanced Keystore Usage ## Overview[​](#overview "Direct link to Overview") The keystore manages private keys and addresses for your Aztec sequencer or prover. This guide covers advanced keystore configurations including secure key storage methods, multi-account setups, and production deployment patterns. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before proceeding, you should: * Be familiar with running a sequencer or prover node * Understand the basic keystore structure from the [sequencer setup guide](/operate/testnet/operators/setup/sequencer_management.md) * Have access to appropriate key management infrastructure (if using remote signers) ## Understanding Keystore Roles[​](#understanding-keystore-roles "Direct link to Understanding Keystore Roles") The keystore manages different types of keys depending on your node type. Understanding these roles helps you configure the right keys for your needs. ### Sequencer Keys[​](#sequencer-keys "Direct link to Sequencer Keys") When running a sequencer, you configure these keys and addresses: * **Attester** (required): Your sequencer's identity. This key signs block proposals and attestations. The corresponding Ethereum address uniquely identifies your sequencer on the network. * **Publisher** (optional): Submits block proposals to L1. Defaults to using the attester key if not specified. Must be funded with at least 0.1 ETH. * **Coinbase** (optional): Ethereum address that receives L2 block rewards on L1. Defaults to the attester address if not set. * **Fee Recipient** (required): Aztec address that receives unburnt L2 transaction fees from blocks you produce. ### Prover Keys[​](#prover-keys "Direct link to Prover Keys") Prover nodes use a simpler configuration: * **Prover ID**: Ethereum address identifying your prover and receiving rewards. * **Publisher**: Submits proof transactions to L1. Must be funded with ETH for gas costs. ### Slasher Keys[​](#slasher-keys "Direct link to Slasher Keys") If you're running a slasher to monitor the network: * **Slasher**: Key used to create slash payloads on L1 when detecting sequencer misbehavior. ## What This Guide Covers[​](#what-this-guide-covers "Direct link to What This Guide Covers") This guide walks you through advanced keystore configurations in three parts: ### 1. Key Storage Methods[​](#1-key-storage-methods "Direct link to 1. Key Storage Methods") Learn about different ways to store and access private keys: * Inline private keys (for testing) * Remote signers with Web3Signer (recommended for production Ethereum keys) * JSON V3 encrypted keystores * BIP44 mnemonic derivation See [Key Storage Methods](/operate/testnet/operators/keystore/storage-methods.md) for detailed instructions. ### 2. Advanced Configuration Patterns[​](#2-advanced-configuration-patterns "Direct link to 2. Advanced Configuration Patterns") Explore complex deployment scenarios: * Using multiple publisher accounts for load distribution * Running multiple sequencers on a single node * Infrastructure provider configurations * High availability setups See [Advanced Configuration Patterns](/operate/testnet/operators/keystore/advanced-patterns.md) for examples. ### 3. Troubleshooting[​](#3-troubleshooting "Direct link to 3. Troubleshooting") Get help with common issues: * Keystore loading failures * Key format validation * Security best practices * Permission problems See [Troubleshooting](/operate/testnet/operators/keystore/troubleshooting.md) for solutions. ## Getting Started[​](#getting-started "Direct link to Getting Started") **First time creating a keystore?** Start with the [Creating Validator Keystores guide](/operate/testnet/operators/keystore/creating_keystores.md) to learn how to use the Aztec CLI to generate keystores for sequencers and provers. Once you have a basic keystore, explore the [Key Storage Methods](/operate/testnet/operators/keystore/storage-methods.md) guide to understand advanced options like remote signers and encrypted keystores. Then check out [Advanced Configuration Patterns](/operate/testnet/operators/keystore/advanced-patterns.md) for complex deployment scenarios. For production deployments, we strongly recommend using remote signers or encrypted keystores instead of inline private keys. --- # Sample configuration patterns ## Overview[​](#overview "Direct link to Overview") This guide covers advanced keystore configuration patterns for complex deployments, including multi-publisher setups, running multiple sequencers, and infrastructure provider scenarios. ## Multiple publishers[​](#multiple-publishers "Direct link to Multiple publishers") Multiple publisher accounts provide: * **Load distribution**: Spread L1 transaction costs across accounts * **Parallelization**: Submit multiple transactions simultaneously * **Resilience**: Continue operating if one publisher runs out of gas **Array of publishers:** ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "0xATTESTER_ETH_PRIVATE_KEY", "bls": "0xATTESTER_BLS_PRIVATE_KEY" }, "publisher": [ "0xPUBLISHER_1_PRIVATE_KEY", "0xPUBLISHER_2_PRIVATE_KEY", "0xPUBLISHER_3_PRIVATE_KEY" ], "feeRecipient": "0x1234567890123456789012345678901234567890123456789012345678901234" } ] } ``` **Mixed storage methods:** ``` { "schemaVersion": 1, "remoteSigner": "https://signer1.example.com:8080", "validators": [ { "attester": { "eth": "0xATTESTER_ETH_PRIVATE_KEY", "bls": "0xATTESTER_BLS_PRIVATE_KEY" }, "publisher": [ "0xLOCAL_PRIVATE_KEY", "0xREMOTE_SIGNER_ADDRESS_1", { "address": "0xREMOTE_SIGNER_ADDRESS_2", "remoteSignerUrl": "https://signer2.example.com:8080" }, { "mnemonic": "test test test test test test test test test test test junk", "addressCount": 2 } ], "feeRecipient": "0x1234567890123456789012345678901234567890123456789012345678901234" } ] } ``` This creates 5 publishers: 1. Local private key 2. Address in default remote signer (signer1.example.com) 3. Address in alternative remote signer (signer2.example.com) 4. Two mnemonic-derived addresses Publisher Funding Required All publisher accounts must be funded with ETH. Monitor balances to avoid missed proposals or proofs. ## Multiple sequencers[​](#multiple-sequencers "Direct link to Multiple sequencers") Run multiple sequencer identities in a single node. This is useful when you operate multiple sequencers but want to consolidate infrastructure. info This section covers running **multiple different sequencer identities** on a single node. **When to use multiple sequencers per node:** * You have multiple sequencer identities (different attester addresses) * You want to consolidate infrastructure and reduce operational overhead * You're running sequencers for multiple entities or clients * You want to simplify management of several sequencers **Use two approaches:** **Option 1: Shared configuration** Multiple attesters sharing the same publisher, coinbase, and fee recipient: ``` { "schemaVersion": 1, "validators": [ { "attester": [ { "eth": "0xSEQUENCER_1_ETH_KEY", "bls": "0xSEQUENCER_1_BLS_KEY" }, { "eth": "0xSEQUENCER_2_ETH_KEY", "bls": "0xSEQUENCER_2_BLS_KEY" } ], "publisher": ["0xSHARED_PUBLISHER"], "coinbase": "0xSHARED_COINBASE", "feeRecipient": "0xSHARED_FEE_RECIPIENT" } ] } ``` **Option 2: Separate configurations** Each sequencer with its own publisher, coinbase, and fee recipient: ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "0xSEQUENCER_1_ETH_KEY", "bls": "0xSEQUENCER_1_BLS_KEY" }, "publisher": ["0xPUBLISHER_1"], "coinbase": "0xCOINBASE_1", "feeRecipient": "0xFEE_RECIPIENT_1" }, { "attester": { "eth": "0xSEQUENCER_2_ETH_KEY", "bls": "0xSEQUENCER_2_BLS_KEY" }, "publisher": ["0xPUBLISHER_2"], "coinbase": "0xCOINBASE_2", "feeRecipient": "0xFEE_RECIPIENT_2" } ] } ``` ## Infrastructure provider scenarios[​](#infrastructure-provider-scenarios "Direct link to Infrastructure provider scenarios") ### Scenario 1: Multiple sequencers with isolation[​](#scenario-1-multiple-sequencers-with-isolation "Direct link to Scenario 1: Multiple sequencers with isolation") For sequencers requiring complete separation, use separate keystore files: **keystore-sequencer-a.json:** ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "0xSEQUENCER_A_ETH_KEY", "bls": "0xSEQUENCER_A_BLS_KEY" }, "feeRecipient": "0xFEE_RECIPIENT_A" } ] } ``` **keystore-sequencer-b.json:** ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "0xSEQUENCER_B_ETH_KEY", "bls": "0xSEQUENCER_B_BLS_KEY" }, "feeRecipient": "0xFEE_RECIPIENT_B" } ] } ``` Point `KEY_STORE_DIRECTORY` to the directory containing both files. ### Scenario 2: Shared publisher infrastructure[​](#scenario-2-shared-publisher-infrastructure "Direct link to Scenario 2: Shared publisher infrastructure") Multiple sequencers sharing a publisher pool for simplified gas management: ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "0xSEQUENCER_1_ETH_KEY", "bls": "0xSEQUENCER_1_BLS_KEY" }, "publisher": ["0xPUBLISHER_1", "0xPUBLISHER_2"], "feeRecipient": "0xFEE_RECIPIENT_1" }, { "attester": { "eth": "0xSEQUENCER_2_ETH_KEY", "bls": "0xSEQUENCER_2_BLS_KEY" }, "publisher": ["0xPUBLISHER_1", "0xPUBLISHER_2"], "feeRecipient": "0xFEE_RECIPIENT_2" } ] } ``` Both sequencers share publishers while maintaining separate identities and fee recipients. ## Prover configurations[​](#prover-configurations "Direct link to Prover configurations") **Simple prover** (uses same key for identity and publishing): ``` { "schemaVersion": 1, "prover": "0xPROVER_PRIVATE_KEY" } ``` **Prover with dedicated publishers:** ``` { "schemaVersion": 1, "prover": { "id": "0xPROVER_IDENTITY_ADDRESS", "publisher": [ "0xPUBLISHER_1_PRIVATE_KEY", "0xPUBLISHER_2_PRIVATE_KEY" ] } } ``` The `id` receives prover rewards while `publisher` accounts submit proofs. ## Complete Configuration Examples[​](#complete-configuration-examples "Direct link to Complete Configuration Examples") ### High Availability Sequencer Setup[​](#high-availability-sequencer-setup "Direct link to High Availability Sequencer Setup") Creating keystores for running the same sequencer across multiple nodes: ``` # Step 1: Generate a base keystore with your attester and multiple publishers aztec validator-keys new \ --fee-recipient [YOUR_FEE_RECIPIENT] \ --mnemonic "your shared mnemonic..." \ --address-index 0 \ --publisher-count 3 \ --data-dir ~/keys-temp # This generates ONE keystore with: # - Attester keys (ETH and BLS) at derivation index 0 # - Three publisher keys at indices 1, 2, and 3 ``` After generation, you'll have a keystore with one attester and multiple publishers. Create separate keystores for each node by copying the base keystore and editing each to use only one publisher: **Node 1** - Uses publisher at index 1 **Node 2** - Uses publisher at index 2 **Node 3** - Uses publisher at index 3 Each node's keystore will have the **same attester keys** (both ETH and BLS) but a **different publisher key**. ## Next steps[​](#next-steps "Direct link to Next steps") * See [Troubleshooting](/operate/testnet/operators/keystore/troubleshooting.md) for common issues * Return to [Key Storage Methods](/operate/testnet/operators/keystore/storage-methods.md) for more options * Start with basics at [Creating Keystores](/operate/testnet/operators/keystore/creating_keystores.md) --- # Creating Sequencer Keystores ## Overview[​](#overview "Direct link to Overview") Keystores are configuration files that store the cryptographic keys and addresses your sequencer node needs to operate on the Aztec network. This guide shows you how to create keystores using the Aztec CLI's `validator-keys` commands. A keystore contains: * **Attester keys**: Your sequencer's identity (Ethereum and BLS keys for signing proposals and attestations) * **Publisher keys**: Keys used to submit blocks to L1 (requires ETH for gas) * **Fee recipient**: Aztec address for L2 transaction fees (currently not used) * **Coinbase address**: Ethereum address receiving L1 block rewards (optional, defaults to attester address) ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before creating keystores, ensure you have: * Basic understanding of Ethereum addresses and private keys * Access to an Ethereum L1 RPC endpoint * Foundry toolkit installed (for creating publisher addresses) ## Installing the Aztec CLI[​](#installing-the-aztec-cli "Direct link to Installing the Aztec CLI") First, install the Aztec CLI using the official installer: ``` VERSION=5.0.0-rc.2 bash -i <(curl -sL https://install.aztec.network/5.0.0-rc.2) ``` Verify your CLI installation: ``` aztec --version ``` ## Recommended Setup: Multiple Validators with Shared Publisher[​](#recommended-setup-multiple-validators-with-shared-publisher "Direct link to Recommended Setup: Multiple Validators with Shared Publisher") This approach creates multiple sequencer identities (validators) that share a single publisher address for submitting transactions to L1. This is the recommended configuration for production deployments. ### Step 1: Create Publisher Address and Set RPC Endpoint[​](#step-1-create-publisher-address-and-set-rpc-endpoint "Direct link to Step 1: Create Publisher Address and Set RPC Endpoint") First, set your Ethereum Sepolia L1 RPC endpoint: ``` export ETH_RPC=https://ethereum-rpc.publicnode.com ``` Or use your preferred Ethereum RPC provider (Infura, Alchemy, etc.). Then generate a separate address for publishing transactions to L1 using the Foundry toolkit: ``` cast wallet new-mnemonic --words 24 ``` **Example output:** ``` Successfully generated a new mnemonic. Phrase: word1 word2 word3 word4 word5 word6 word7 word8 word9 word10 word11 word12 word13 word14 word15 word16 word17 word18 word19 word20 word21 word22 word23 word24 Accounts: - Account 0: Address: 0xE434A95e816991E66bF7052955FD699aEf8a286b Private key: 0x7988a4a7...79f058a0 ``` Critical: Save Your Publisher Mnemonic The 24-word mnemonic is the **only way** to recover your publisher private key. Store it securely offline (not on the server running the node). **Save from the output:** * ✅ The 24-word mnemonic (for recovery) * ✅ The private key (you'll use this in the next step) * ✅ The address (you'll fund this with ETH) ### Step 2: Generate Your Keystores with Publisher[​](#step-2-generate-your-keystores-with-publisher "Direct link to Step 2: Generate Your Keystores with Publisher") Generate 5 validators with the publisher private key from Step 1: ``` aztec validator-keys new \ --fee-recipient 0x0000000000000000000000000000000000000000000000000000000000000000 \ --staker-output \ --gse-address 0xb6a38a51a6c1de9012f9d8ea9745ef957212eaac \ --l1-rpc-urls $ETH_RPC \ --count 5 \ --publishers 0x7988a4a779f058a0 ``` Replace `0x7988a4a779f058a0` with your actual publisher private key from Step 1. **What this command does:** * Generates a new mnemonic for your validator keys (save this securely!) * Creates 5 sequencer identities (validators) with Ethereum and BLS keys * Configures all validators to use the same publisher address for L1 submissions * Generates public keystore data for the staking dashboard * Saves files to `~/.aztec/keystore/` **Example output:** ``` No mnemonic provided, generating new one... Using new mnemonic: absent city nephew garment million badge front text memory grape two lizard Wrote validator keystore to /Users/your-name/.aztec/keystore/key1.json Wrote staker output for 5 validator(s) to /Users/your-name/.aztec/keystore/key1_staker_output.json acc1: attester: eth: 0x8E76a8B8D66E0A56E241F2768fD2ad4eba07E565 bls: 0x29eaf46e4699e33a1abe7300258567c624a7304a2134e31aa2609437f281d81d publisher: - 0x7988a4a779f058a0 acc2: attester: eth: 0x2037b472537a4246B1A7325f327028EF450ba0Ef bls: 0x8d7eb7d9436ac6cb9b8f1c211673ea228c7f438882e6438b2caefca753df28e8 publisher: - 0x7988a4a779f058a0 acc3: attester: eth: 0x0c14593f7465DeDbb86d68982374BB05F4C60386 bls: 0xad1cccf512d2f180238af795831344445f7ac47e2d623f3dac854e93e5b1e76d publisher: - 0x7988a4a779f058a0 acc4: attester: eth: 0x4D213928988f0123f6b3B4A377F856812F08E831 bls: 0xa90f5889dddd4cd6bc5a28db5e0db60d3cbf5147eb6e82b313024b2d0634110e publisher: - 0x7988a4a779f058a0 acc5: attester: eth: 0x29f147Da38d5F66bB84e791969b365c796829c92 bls: 0x0d683001c2ce866e322f0c7509f087a909508787d125336931aa9168d2a1f95b publisher: - 0x7988a4a779f058a0 Note: The publisher value shown is the private key (truncated in this example). All validators share the same publisher private key. Staker outputs: [ { "attester": "0x8E76a8B8D66E0A56E241F2768fD2ad4eba07E565", "publicKeyG1": { "x": "0x...", "y": "0x..." }, "publicKeyG2": { "x0": "0x...", "x1": "0x...", "y0": "0x...", "y1": "0x..." }, "proofOfPossession": { "x": "0x...", "y": "0x..." } }, ... (4 more validators) ] ``` Critical: Save Both Mnemonics You now have **two separate mnemonics** to secure: 1. **Validator mnemonic** (shown above, 12 words) - Regenerates your attester keys 2. **Publisher mnemonic** (from Step 1, 24 words) - Regenerates your publisher key Both must be stored securely offline. Losing either mnemonic means losing access to those keys. **Files created:** * `~/.aztec/keystore/key1.json` - Private keystore with all 5 validators and publisher configured * `~/.aztec/keystore/key1_staker_output.json` - Public keystore for staking dashboard ### Step 3: Fund the Publisher Address[​](#step-3-fund-the-publisher-address "Direct link to Step 3: Fund the Publisher Address") Your publisher address needs ETH to pay for L1 gas when submitting proposals. **Funding requirement:** At least **0.3 ETH** for 5 validators (rule of thumb: 0.1 ETH per validator) Transfer ETH to the publisher address from Step 1. You can check the balance with: ``` cast balance 0xE434A95e816991E66bF7052955FD699aEf8a286b --rpc-url $ETH_RPC ``` Replace the address with your actual publisher address. Monitor Publisher Balance Set up monitoring to alert when the publisher balance falls below 0.5 ETH to prevent failed block publications. ### Step 4: Upload Keystore to Your Node[​](#step-4-upload-keystore-to-your-node "Direct link to Step 4: Upload Keystore to Your Node") Now you're ready to spin up your sequencer node! **Upload the private keystore to your server:** The `key1.json` file contains your private keys and must be uploaded to your sequencer node. **For standard server deployments:** ``` # Upload to your server's keystore directory scp ~/.aztec/keystore/key1.json user@your-server:/path/to/aztec-sequencer/keys/keystore.json ``` **For dAppNode deployments:** * Upload `key1.json` to the dAppNode keystore folder * Rename it to `keystore.json` Keep the Public Keystore Local Keep `key1_staker_output.json` on your local machine - you'll need it for registration on the staking dashboard. **Do not upload this to your server.** ### Step 5: Start Your Node[​](#step-5-start-your-node "Direct link to Step 5: Start Your Node") Start your sequencer node following the [Sequencer Setup guide](/operate/testnet/operators/setup/sequencer_management.md). When your node starts successfully, you'll see output similar to: ``` Started validator with addresses: 0x8E76a8B8D66E0A56E241F2768fD2ad4eba07E565, 0x2037b472537a4246B1A7325f327028EF450ba0Ef, 0x0c14593f7465DeDbb86d68982374BB05F4C60386, 0x4D213928988f0123f6b3B4A377F856812F08E831, 0x29f147Da38d5F66bB84e791969b365c796829c92 ``` These are your validator attester addresses - they match the addresses shown when you generated your keys. ### Step 6: Register Your Validators[​](#step-6-register-your-validators "Direct link to Step 6: Register Your Validators") Use the public keystore (`key1_staker_output.json`) to register your validators on the staking dashboard. See [Registering a Sequencer](/operate/testnet/operators/setup/registering_sequencer.md) for details. *** ### Quick Setup Summary[​](#quick-setup-summary "Direct link to Quick Setup Summary") By following the recommended setup, you've accomplished: ✅ **Generated a dedicated publisher address** with its own 24-word mnemonic ✅ **Created 5 validator identities** with a separate 12-word mnemonic ✅ **Configured all validators** to use the shared publisher for L1 transactions ✅ **Funded the publisher** with at least 0.3 ETH for gas costs ✅ **Uploaded the private keystore** (`key1.json`) to your sequencer node ✅ **Started your node** and verified validator addresses in the output ✅ **Ready to register** using the public keystore (`key1_staker_output.json`) **Two mnemonics to keep secure:** 1. **Publisher mnemonic** (24 words) - Recovers publisher private key 2. **Validator mnemonic** (12 words) - Recovers all 5 validator attester keys ## Alternative: Single Validator Setup[​](#alternative-single-validator-setup "Direct link to Alternative: Single Validator Setup") For testing or simpler setups, you can create a single validator that uses its attester key as the publisher. ### Basic Single Validator[​](#basic-single-validator "Direct link to Basic Single Validator") ``` aztec validator-keys new \ --fee-recipient 0x0000000000000000000000000000000000000000000000000000000000000000 \ --staker-output \ --gse-address 0xb6a38a51a6c1de9012f9d8ea9745ef957212eaac \ --l1-rpc-urls $ETH_RPC ``` This creates: * One validator with attester keys * No separate publisher (attester key used for publishing) * Private keystore at `~/.aztec/keystore/keyN.json` * Public keystore at `~/.aztec/keystore/keyN_staker_output.json` When to Use Single Validator Use single validator setup for: * Testing and development * Simple deployments with one sequencer identity * When you don't need to isolate attester and publisher keys ## Understanding Keystore Structure[​](#understanding-keystore-structure "Direct link to Understanding Keystore Structure") ### Private Keystore Format[​](#private-keystore-format "Direct link to Private Keystore Format") The private keystore (`key1.json`) contains sensitive private keys: ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "0x...", // Ethereum private key - sequencer identifier "bls": "0x..." // BLS private key - signs proposals and attestations }, "publisher": ["0x..."], // Publisher private key(s) for L1 submissions "feeRecipient": "0x0000000000000000000000000000000000000000000000000000000000000000", "coinbase": "0x..." // Optional: custom address for L1 rewards } ] } ``` **Field descriptions:** * **attester.eth**: Derives the address that serves as your sequencer's unique identifier * **attester.bls**: Signs proposals and attestations, used for staking operations * **publisher**: Array of private keys for submitting signed messages to L1 (pays gas) * **feeRecipient**: L2 fee recipient (not currently used, set to all zeros) * **coinbase**: L1 block reward recipient (optional, defaults to attester address) ### Public Keystore Format[​](#public-keystore-format "Direct link to Public Keystore Format") The public keystore (`key1_staker_output.json`) contains only public information safe to share: ``` [ { "attester": "0xYOUR_ATTESTER_ADDRESS", "publicKeyG1": { "x": "0x...", "y": "0x..." }, "publicKeyG2": { "x0": "0x...", "x1": "0x...", "y0": "0x...", "y1": "0x..." }, "proofOfPossession": { "x": "0x...", "y": "0x..." } } ] ``` This file is used for registration on the staking dashboard and contains no private keys. ## Advanced Options[​](#advanced-options "Direct link to Advanced Options") ### Providing Your Own Mnemonic[​](#providing-your-own-mnemonic "Direct link to Providing Your Own Mnemonic") For deterministic key generation or to recreate keys from an existing mnemonic: ``` aztec validator-keys new \ --fee-recipient 0x0000000000000000000000000000000000000000000000000000000000000000 \ --staker-output \ --gse-address 0xb6a38a51a6c1de9012f9d8ea9745ef957212eaac \ --l1-rpc-urls $ETH_RPC \ --mnemonic "your existing twelve word mnemonic phrase here" \ --count 5 \ --publishers 0x7988a4a779f058a0 ``` This regenerates the same validators if you've used this mnemonic before, or creates new ones at the next derivation indices. ### Custom Output Location[​](#custom-output-location "Direct link to Custom Output Location") Specify custom directory and filename: ``` aztec validator-keys new \ --fee-recipient 0x0000000000000000000000000000000000000000000000000000000000000000 \ --staker-output \ --gse-address 0xb6a38a51a6c1de9012f9d8ea9745ef957212eaac \ --l1-rpc-urls $ETH_RPC \ --count 5 \ --publishers 0x7988a4a779f058a0 \ --data-dir ~/my-sequencer/keys \ --file sequencer1.json ``` This creates keystores at: * `~/my-sequencer/keys/sequencer1.json` (private keystore) * `~/my-sequencer/keys/sequencer1_staker_output.json` (public keystore) **Default behavior** (if you don't specify `--data-dir` or `--file`): * **Directory**: `~/.aztec/keystore/` * **Filename**: `key1.json`, `key2.json`, etc. (auto-increments) ## Verifying Your Keystore[​](#verifying-your-keystore "Direct link to Verifying Your Keystore") Verify the keystore is valid JSON: ``` cat ~/.aztec/keystore/key1.json | jq . ``` Check validator count: ``` jq '.validators | length' ~/.aztec/keystore/key1.json ``` Verify BLS keys are present: ``` jq '.validators[0].attester.bls' ~/.aztec/keystore/key1.json ``` Extract attester addresses: ``` # Get attester ETH private key (to derive address) jq -r '.validators[0].attester.eth' ~/.aztec/keystore/key1.json ``` ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") If you encounter issues during keystore creation or management, see the **[Troubleshooting and Best Practices Guide](/operate/testnet/operators/keystore/troubleshooting.md)** for: * Keystore creation issues (RPC, permissions, invalid JSON, legacy BLS keys) * Runtime and operational issues (node startup, remote signers, nonce conflicts) * Comprehensive security best practices * Complete CLI reference ## Next Steps[​](#next-steps "Direct link to Next Steps") Now that you've created your keystores: ### For Sequencer Operators[​](#for-sequencer-operators "Direct link to For Sequencer Operators") 1. **Fund publisher addresses** - At least 0.1 ETH per validator 2. **Set up your node** - See [Sequencer Management](/operate/testnet/operators/setup/sequencer_management.md) 3. **Register validators** - Use the public keystore with the staking dashboard 4. **Monitor operations** - Track attestations and publisher balance ### Advanced Configurations[​](#advanced-configurations "Direct link to Advanced Configurations") * **[Advanced Keystore Patterns](/operate/testnet/operators/keystore/advanced-patterns.md)** - Multiple validators, high availability, remote signers * **[Key Storage Methods](/operate/testnet/operators/keystore/storage-methods.md)** - Encrypted keystores, HSMs, key management systems * **[Troubleshooting and Best Practices](/operate/testnet/operators/keystore/troubleshooting.md)** - Common issues, security best practices, and CLI reference ### Getting Help[​](#getting-help "Direct link to Getting Help") * Review the [Operator FAQ](/operate/testnet/operators/operator-faq.md) for common questions * Join the [Aztec Discord](https://discord.gg/aztec) for operator support * Check the [CLI reference](/operate/testnet/operators/reference/cli-reference.md) for all available commands --- # Key storage methods ## Overview[​](#overview "Direct link to Overview") The keystore supports four methods for storing and accessing private keys. These methods can be mixed within a single configuration. ## Private keys (inline)[​](#private-keys-inline "Direct link to Private keys (inline)") The simplest method is to include private keys directly in the keystore. The `validator-keys new` command generates keystores in this format by default: ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "0xef17bcb86452f3f6a73678c01bee757e9d46d1cd0050f043c10cfc953b17bad2", "bls": "0x20f2f5989b66462b39229900948c7846403768fec5b76d1c2937d64e04aac4b9" }, "feeRecipient": "0x0000000000000000000000000000000000000000000000000000000000000000" } ] } ``` Note that the attester field now contains both Ethereum (`eth`) and BLS (`bls`) private keys. Both are required for sequencer operation. Not for Production Use Inline private keys are convenient for testing but should be avoided in production. Use remote signers or encrypted keystores for production deployments. ## Remote signers (Web3Signer)[​](#remote-signers-web3signer "Direct link to Remote signers (Web3Signer)") Remote signers keep private keys in a separate, secure signing service. This is the recommended approach for production environments for Ethereum keys. The keystore supports [Web3Signer](https://docs.web3signer.consensys.io/) endpoints for Ethereum keys. The keystore automatically detects whether a value is a private key or an address based on string length: * **66 characters** (`0x` + 64 hex characters): Interpreted as a private key (stored inline) * **42 characters** (`0x` + 40 hex characters): Interpreted as an address and uses the nearest `remoteSignerUrl` BLS Keys Do Not Support Remote Signers BLS keys must always be stored as private keys directly in the keystore. The keystore does not check `remoteSignerUrl` for BLS keys. Web3Signer's BLS support is designed for Ethereum consensus layer operations and is not compatible with Aztec's BLS key requirements. Remote signers can be configured at three levels: **Global level** (applies to all ETH keys): ``` { "schemaVersion": 1, "remoteSigner": "https://signer.example.com:8080", "validators": [ { "attester": { "eth": "0x1234567890123456789012345678901234567890", "bls": "0x20f2f5989b66462b39229900948c7846403768fec5b76d1c2937d64e04aac4b9" }, "feeRecipient": "0x1234567890123456789012345678901234567890123456789012345678901234" } ] } ``` In this example, the Ethereum attester address (42 characters) is managed by the remote signer, while the BLS key (66 characters) is a private key stored directly in the keystore. **Validator (sequencer) block level** (applies to all ETH keys in a sequencer configuration): ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "0x1234567890123456789012345678901234567890", "bls": "0x20f2f5989b66462b39229900948c7846403768fec5b76d1c2937d64e04aac4b9" }, "feeRecipient": "0x1234567890123456789012345678901234567890123456789012345678901234", "remoteSigner": "https://signer.example.com:8080" } ] } ``` **Account level** (applies to a specific ETH key): ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": { "address": "0x1234567890123456789012345678901234567890", "remoteSignerUrl": "https://signer.example.com:8080" }, "bls": "0x20f2f5989b66462b39229900948c7846403768fec5b76d1c2937d64e04aac4b9" }, "feeRecipient": "0x1234567890123456789012345678901234567890123456789012345678901234" } ] } ``` ### Client certificate authentication[​](#client-certificate-authentication "Direct link to Client certificate authentication") For remote signers requiring client certificates: ``` { "schemaVersion": 1, "remoteSigner": { "remoteSignerUrl": "https://signer.example.com:8080", "certPath": "/path/to/client-cert.p12", "certPass": "certificate-password" }, "validators": [...] } ``` ## JSON V3 encrypted keystores[​](#json-v3-encrypted-keystores "Direct link to JSON V3 encrypted keystores") JSON V3 keystores provide standard Ethereum-compatible encrypted key storage. **Single file:** ``` { "schemaVersion": 1, "validators": [ { "attester": { "path": "/path/to/keystore.json", "password": "keystore-password" }, "feeRecipient": "0x1234567890123456789012345678901234567890123456789012345678901234" } ] } ``` **Directory of keystores:** ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "0x1234567890123456789012345678901234567890123456789012345678901234", "bls": "0x2345678901234567890123456789012345678901234567890123456789012345" }, "publisher": { "path": "/path/to/keystores/", "password": "shared-password" }, "feeRecipient": "0x1234567890123456789012345678901234567890123456789012345678901234" } ] } ``` All `.json` files in the directory will be loaded using the provided password. ## Mnemonics (BIP44 derivation)[​](#mnemonics-bip44-derivation "Direct link to Mnemonics (BIP44 derivation)") Mnemonics derive multiple keys from a single seed phrase using [BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) paths. **Single key** (default path `m/44'/60'/0'/0/0`): ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "0x1234567890123456789012345678901234567890123456789012345678901234", "bls": "0x2345678901234567890123456789012345678901234567890123456789012345" }, "publisher": { "mnemonic": "test test test test test test test test test test test junk" }, "feeRecipient": "0x1234567890123456789012345678901234567890123456789012345678901234" } ] } ``` **Multiple sequential keys:** ``` { "publisher": { "mnemonic": "test test test test test test test test test test test junk", "addressCount": 4 } } ``` Generates 4 keys at paths `m/44'/60'/0'/0/0` through `m/44'/60'/0'/0/3`. **Custom derivation paths:** ``` { "publisher": { "mnemonic": "test test test test test test test test test test test junk", "accountIndex": 5, "addressIndex": 3, "addressCount": 2 } } ``` Not for Production Use Mnemonics are convenient for testing but should be avoided in production. Use remote signers or encrypted keystores for production deployments. ## Next steps[​](#next-steps "Direct link to Next steps") * Learn about [Advanced Configuration Patterns](/operate/testnet/operators/keystore/advanced-patterns.md) * See [Troubleshooting](/operate/testnet/operators/keystore/troubleshooting.md) if you encounter issues --- # Troubleshooting and Best Practices ## Keystore Creation Issues[​](#keystore-creation-issues "Direct link to Keystore Creation Issues") ### Missing fee-recipient Flag[​](#missing-fee-recipient-flag "Direct link to Missing fee-recipient Flag") **Error message:** ``` error: required option '--fee-recipient
' not specified ``` **Solution:** The CLI requires the `--fee-recipient` flag. Use the zero address: ``` --fee-recipient 0x0000000000000000000000000000000000000000000000000000000000000000 ``` ### RPC Connection Issues[​](#rpc-connection-issues "Direct link to RPC Connection Issues") **Error message:** ``` Error: HTTP request failed ``` **Solutions:** * Verify `$ETH_RPC` is set correctly: `echo $ETH_RPC` * Test RPC connectivity: `cast block-number --rpc-url $ETH_RPC` * Try a different RPC provider if the current one is rate-limited ### Permission Denied[​](#permission-denied "Direct link to Permission Denied") **Error message:** ``` Error: permission denied ``` **Solution:** Ensure you have write permissions for the target directory: ``` mkdir -p ~/.aztec/keystore chmod 755 ~/.aztec/keystore ``` ### Invalid Keystore JSON[​](#invalid-keystore-json "Direct link to Invalid Keystore JSON") **Error:** Node fails to load keystore or CLI rejects keystore file **Solutions:** * Validate JSON syntax: `jq . ~/.aztec/keystore/key1.json` * Ensure all required fields are present * Check that publisher is an array: `["0x..."]` not `"0x..."` * Verify private keys are 64-character hex strings (with or without `0x` prefix) ### Legacy BLS Key Derivation (2.1.4 Users)[​](#legacy-bls-key-derivation-214-users "Direct link to Legacy BLS Key Derivation (2.1.4 Users)") **Issue:** Need to regenerate keys that were created with CLI version 2.1.4 or earlier Version 2.1.5 changed the BLS key derivation path, which means keys generated from the same mnemonic produce different results. This affects users who: * Generated keys with version 2.1.4 using `--count` parameter * Used `--account-index` explicitly in version 2.1.4 * Need to regenerate keys from mnemonic that are already registered in the GSE contract **The derivation path change:** * **2.1.4**: `m/12381/3600/0/0/0`, `m/12381/3600/1/0/0`, `m/12381/3600/2/0/0` * **2.1.5+**: `m/12381/3600/0/0/0`, `m/12381/3600/0/0/1`, `m/12381/3600/0/0/2` **Solution: Use the --legacy flag** If you generated keys with version 2.1.4 and need to regenerate them from your mnemonic, use the `--legacy` flag: ``` aztec validator-keys new \ --fee-recipient 0x0000000000000000000000000000000000000000000000000000000000000000 \ --staker-output \ --gse-address 0xb6a38a51a6c1de9012f9d8ea9745ef957212eaac \ --l1-rpc-urls $ETH_RPC \ --mnemonic "your twelve word mnemonic phrase here" \ --count 5 \ --legacy ``` The `--legacy` flag uses the 2.1.4 derivation path to reproduce your original keys. When NOT to Use --legacy Do NOT use the `--legacy` flag if: * You're generating keys for the first time * You generated keys with version 2.1.5 or later * You didn't use `--count` or `--account-index` in version 2.1.4 Using `--legacy` unnecessarily will create keys with the old derivation path that won't match your newer registrations. Why This Matters BLS keys are registered in the GSE (Governance Staking Escrow) contract and cannot be easily updated. If you regenerate keys with a different derivation path, they won't match what's registered on chain, and your sequencer won't be able to attest properly. ## Runtime and Operational Issues[​](#runtime-and-operational-issues "Direct link to Runtime and Operational Issues") ### "No validators found in keystore"[​](#no-validators-found-in-keystore "Direct link to \"No validators found in keystore\"") **Symptoms**: Node fails to start with no sequencer configurations loaded **Causes**: * Keystore file not found at specified path * Invalid JSON syntax * Missing required fields * File permissions prevent reading **Solutions**: 1. Verify keystore path: ``` ls -la $KEY_STORE_DIRECTORY ``` 2. Validate JSON syntax: ``` cat keystore.json | jq . ``` 3. Check required fields: ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "REQUIRED - Ethereum private key", "bls": "REQUIRED - BLS private key" }, "feeRecipient": "REQUIRED - Aztec address" } ] } ``` 4. Fix file permissions: ``` chmod 600 keystore.json chown aztec:aztec keystore.json ``` ### "Failed to connect to remote signer"[​](#failed-to-connect-to-remote-signer "Direct link to \"Failed to connect to remote signer\"") **Symptoms**: Node cannot reach Web3Signer endpoint **Causes**: * Incorrect URL or port * Network connectivity issues * Certificate validation failures * Remote signer not running **Solutions**: 1. Test connectivity: ``` curl https://signer.example.com:8080/upcheck ``` 2. Verify certificate: ``` openssl s_client -connect signer.example.com:8080 -showcerts ``` 3. Check remote signer logs for authentication errors 4. For self-signed certificates, ensure proper certificate configuration in keystore ### "Insufficient funds for gas"[​](#insufficient-funds-for-gas "Direct link to \"Insufficient funds for gas\"") **Symptoms**: Transactions fail with insufficient balance errors **Causes**: * Publisher accounts not funded * ETH balance depleted **Solutions**: 1. Check publisher balances: ``` cast balance 0xPUBLISHER_ADDRESS --rpc-url $ETHEREUM_HOST ``` 2. Fund publisher accounts with ETH 3. Set up automated balance monitoring and alerts ### "Nonce too low" or "Replacement transaction underpriced"[​](#nonce-too-low-or-replacement-transaction-underpriced "Direct link to \"Nonce too low\" or \"Replacement transaction underpriced\"") **Symptoms**: Transaction submission failures related to nonces **Causes**: * Multiple nodes using same publisher key * Publisher key reused across keystores * Transaction pool issues **Solutions**: 1. **Never share publisher keys across multiple running nodes** 2. If you must use the same key, ensure only one node is active at a time 3. Clear pending transactions if safe to do so ### "Keystore file not loaded"[​](#keystore-file-not-loaded "Direct link to \"Keystore file not loaded\"") **Symptoms**: Only some keystores load from a directory **Causes**: * Invalid JSON in some files * Incorrect file extensions * Schema version mismatch **Solutions**: 1. Check all files in directory: ``` for file in /path/to/keystores/*.json; do echo "Checking $file" jq . "$file" || echo "Invalid JSON in $file" done ``` 2. Ensure all files use `.json` extension 3. Verify `schemaVersion: 1` in all keystores ### "Cannot decrypt JSON V3 keystore"[​](#cannot-decrypt-json-v3-keystore "Direct link to \"Cannot decrypt JSON V3 keystore\"") **Symptoms**: Failed to load encrypted keystore files **Causes**: * Incorrect password * Corrupted keystore file * Unsupported encryption algorithm **Solutions**: 1. Verify password is correct 2. Test decryption manually: ``` # Using ethereumjs-wallet or similar tool ``` 3. Re-generate keystore if corrupted 4. Ensure keystore was generated using standard tools (geth, web3.py, ethers.js) ## Security Best Practices[​](#security-best-practices "Direct link to Security Best Practices") ### Protecting Private Keys[​](#protecting-private-keys "Direct link to Protecting Private Keys") 1. **Never commit keystores to version control** * Add `keystore.json` to `.gitignore` * Store keystores outside your project directory 2. **Backup your mnemonic securely** * Write it down offline * Store in a secure location (not on the server) * Consider using a hardware wallet or password manager 3. **Limit keystore access** ``` chmod 600 ~/.aztec/keystore/key1.json ``` 4. **Separate publisher from attester** * Use dedicated publisher keys * Keep attester keys offline when possible * Use remote signers for production ### Key Storage[​](#key-storage "Direct link to Key Storage") **DO:** * Use remote signers (Web3Signer) for production deployments * Store keystores in encrypted volumes * Use JSON V3 keystores with strong passwords * Restrict file permissions to 600 (owner read/write only) * Keep backups of keystores in secure, encrypted locations **DON'T:** * Commit keystores or private keys to version control * Store unencrypted private keys on disk * Share private keys between nodes * Use the same keys across test and production environments * Log private keys or keystore passwords ### Publisher Key Management[​](#publisher-key-management "Direct link to Publisher Key Management") **DO:** * Use separate publisher keys for each sequencer if possible * Monitor publisher account balances with alerting * Rotate publisher keys periodically * Maintain multiple funded publishers for resilience * Keep publisher keys separate from attester keys **DON'T:** * Reuse publisher keys across multiple nodes * Run out of gas in publisher accounts * Use sequencer attester keys as publishers if avoidable * Share publisher keys between sequencers ### Remote Signer Security[​](#remote-signer-security "Direct link to Remote Signer Security") **DO:** * Use TLS/HTTPS for all remote signer connections * Implement client certificate authentication * Run remote signers on isolated networks * Monitor remote signer access logs * Use firewall rules to restrict access **DON'T:** * Use unencrypted HTTP connections * Expose remote signers to the public internet * Share remote signer endpoints between untrusted parties * Disable certificate verification ### Operational Security[​](#operational-security "Direct link to Operational Security") **DO:** * Implement principle of least privilege for file access * Use hardware security modules (HSMs) for high-value sequencers * Maintain audit logs of key access and usage * Test keystore configurations in non-production environments first * Document your key management procedures **DON'T:** * Run nodes as root user * Store passwords in shell history or scripts * Share attester keys between sequencers * Neglect monitoring and alerting ### Production Deployments[​](#production-deployments "Direct link to Production Deployments") For production, consider: * **Hardware Security Modules (HSMs)** for key storage * **Remote signers** to keep keys off the node * **Encrypted keystores** with password protection * **Key management systems** (HashiCorp Vault, AWS Secrets Manager) See [Key Storage Methods](/operate/testnet/operators/keystore/storage-methods.md) for advanced security patterns. ## CLI Reference[​](#cli-reference "Direct link to CLI Reference") ### validator-keys new[​](#validator-keys-new "Direct link to validator-keys new") Create a new keystore with validators: ``` aztec validator-keys new [options] ``` **Common Options:** | Option | Description | Default | | ---------------------------- | -------------------------------------------------------------- | ------------------- | | `--fee-recipient
` | L2 fee recipient (required) | None | | `--mnemonic ` | 12 or 24 word mnemonic | Auto-generated | | `--count ` | Number of validators to create | `1` | | `--publisher-count ` | Publishers per validator | `0` | | `--staker-output` | Generate public keystore for staking | `false` | | `--gse-address
` | GSE contract address (required with --staker-output) | None | | `--l1-rpc-urls ` | L1 RPC endpoints (required with --staker-output) | None | | `--legacy` | Use 2.1.4 BLS derivation path (only for regenerating old keys) | `false` | | `--data-dir ` | Output directory | `~/.aztec/keystore` | | `--file ` | Keystore filename | `key1.json` | For the complete list: ``` aztec validator-keys new --help ``` ### validator-keys add[​](#validator-keys-add "Direct link to validator-keys add") Add validators to an existing keystore: ``` aztec validator-keys add [options] ``` ### validator-keys staker[​](#validator-keys-staker "Direct link to validator-keys staker") Generate staker output from an existing keystore: ``` aztec validator-keys staker \ --from \ --gse-address
\ --l1-rpc-urls \ --output ``` ## Getting Help[​](#getting-help "Direct link to Getting Help") If you encounter issues not covered here: * Review the [Operator FAQ](/operate/testnet/operators/operator-faq.md) for common questions * Join the [Aztec Discord](https://discord.gg/aztec) for operator support * Check the [CLI reference](/operate/testnet/operators/reference/cli-reference.md) for all available commands * Review node logs for specific error messages (redact private keys!) * When asking for help, provide: * Error messages (with private keys redacted) * Keystore structure (anonymized) * Node version and deployment environment ## Related Documentation[​](#related-documentation "Direct link to Related Documentation") * **[Creating Keystores](/operate/testnet/operators/keystore/creating_keystores.md)** - Main guide for generating keystores * **[Advanced Keystore Patterns](/operate/testnet/operators/keystore/advanced-patterns.md)** - Multiple validators, high availability, remote signers * **[Key Storage Methods](/operate/testnet/operators/keystore/storage-methods.md)** - Encrypted keystores, HSMs, key management systems * **[Sequencer Management](/operate/testnet/operators/setup/sequencer_management.md)** - Operational guidance for running sequencers --- # Monitoring and Observability ## Overview[​](#overview "Direct link to Overview") This guide shows you how to set up monitoring and observability for your Aztec node using OpenTelemetry, Prometheus, and Grafana. Monitoring helps you maintain healthy node operations, diagnose issues quickly, and track performance over time. Docker Compose Setup This monitoring setup is designed to work with Docker Compose deployments of Aztec nodes. ## Architecture[​](#architecture "Direct link to Architecture") The monitoring stack uses three components working together: * **OpenTelemetry Collector**: Receives metrics from your Aztec node via OTLP protocol * **Prometheus**: Stores and queries time-series metrics data * **Grafana**: Visualizes metrics with dashboards and alerts Your Aztec node exports metrics to the OpenTelemetry Collector, which processes and exposes them in a format Prometheus can scrape. Prometheus stores the metrics as time-series data, and Grafana queries Prometheus to create visualizations and alerts. ## Getting Started[​](#getting-started "Direct link to Getting Started") Follow these guides in order to set up your complete monitoring stack: 1. [OpenTelemetry Collector Setup](/operate/testnet/operators/monitoring/otel-setup.md) - Configure OTEL to receive metrics from your node 2. [Prometheus Setup](/operate/testnet/operators/monitoring/prometheus-setup.md) - Set up Prometheus to store and query metrics 3. [Grafana Setup](/operate/testnet/operators/monitoring/grafana-setup.md) - Configure Grafana for visualization and alerting 4. [Key Metrics Reference](/operate/testnet/operators/monitoring/metrics-reference.md) - Understand the metrics your node exposes and create custom dashboards 5. [Complete Example and Troubleshooting](/operate/testnet/operators/monitoring/troubleshooting.md) - Full Docker Compose configuration and troubleshooting help ## Available Metrics Overview[​](#available-metrics-overview "Direct link to Available Metrics Overview") Your Aztec node exposes metrics through OpenTelemetry to help you monitor performance and health. The metrics available depend on your node type (full node, sequencer, or prover) and version. ### Metric Categories[​](#metric-categories "Direct link to Metric Categories") Your node exposes metrics in these categories: * **Node Metrics**: Block height, sync status, peer count, and transaction processing * **Sequencer Metrics**: Attestation activity, block proposals, and committee participation (sequencer nodes only) * **Prover Metrics**: Job queue, proof generation, and agent utilization (prover nodes only) * **System Metrics**: CPU, memory, disk I/O, and network bandwidth For detailed information about each metric, PromQL queries, and dashboard creation, see the [Key Metrics Reference](/operate/testnet/operators/monitoring/metrics-reference.md). ## Next Steps[​](#next-steps "Direct link to Next Steps") Once your monitoring stack is running: * Review the [Key Metrics Reference](/operate/testnet/operators/monitoring/metrics-reference.md) to understand available metrics and PromQL queries * Set up alerting rules in Prometheus for critical conditions * Create custom dashboards tailored to your operational needs * Configure notification channels (Slack, PagerDuty, email) in Grafana * Join the [Aztec Discord](https://discord.gg/aztec) to share dashboards with the community For troubleshooting common monitoring issues, see the [Troubleshooting](/operate/testnet/operators/monitoring/troubleshooting.md) guide. --- # Grafana Setup ## Overview[​](#overview "Direct link to Overview") Grafana provides visualization and alerting for your metrics, allowing you to create custom dashboards and receive notifications when issues arise. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Completed [Prometheus Setup](/operate/testnet/operators/monitoring/prometheus-setup.md) * Prometheus running and accessible at `http://prometheus:9090` ## Setup Steps[​](#setup-steps "Direct link to Setup Steps") ### Step 1: Add Grafana to Docker Compose[​](#step-1-add-grafana-to-docker-compose "Direct link to Step 1: Add Grafana to Docker Compose") Add Grafana to your `docker-compose.yml`: ``` services: # ... existing services (otel-collector, prometheus, etc.) ... grafana: image: grafana/grafana:latest container_name: aztec-grafana ports: - 3000:3000 volumes: - grafana-data:/var/lib/grafana environment: - GF_SECURITY_ADMIN_PASSWORD=admin - GF_USERS_ALLOW_SIGN_UP=false networks: - aztec restart: always volumes: # ... existing volumes ... grafana-data: networks: aztec: name: aztec ``` Admin Password Security Change the default admin password (`GF_SECURITY_ADMIN_PASSWORD`) to a secure value for production deployments. ### Step 2: Start Grafana[​](#step-2-start-grafana "Direct link to Step 2: Start Grafana") ``` docker compose up -d grafana ``` ### Step 3: Access Grafana[​](#step-3-access-grafana "Direct link to Step 3: Access Grafana") 1. Navigate to `http://localhost:3000` 2. Login with username `admin` and the password you set (default: `admin`) 3. You'll be prompted to change the password on first login ### Step 4: Add Prometheus Data Source[​](#step-4-add-prometheus-data-source "Direct link to Step 4: Add Prometheus Data Source") 1. In the left sidebar, click **Connections** → **Data sources** 2. Click **Add data source** 3. Search for and select **Prometheus** 4. Configure: * **Name**: Aztec Prometheus * **URL**: `http://prometheus:9090` 5. Click **Save & Test** You should see a green success message confirming Grafana can connect to Prometheus. ## Creating Dashboards[​](#creating-dashboards "Direct link to Creating Dashboards") ### Option 1: Create a Basic Dashboard[​](#option-1-create-a-basic-dashboard "Direct link to Option 1: Create a Basic Dashboard") 1. In the left sidebar, click **Dashboards** 2. Click **New** → **New Dashboard** 3. Click **Add visualization** 4. Select your **Aztec Prometheus** data source 5. In the query editor, enter a metric (explore available metrics using the autocomplete) 6. Customize the visualization type and settings 7. Click **Apply** 8. Click **Save dashboard** icon (top right) 9. Give your dashboard a name and click **Save** ### Option 2: Import a Pre-built Dashboard[​](#option-2-import-a-pre-built-dashboard "Direct link to Option 2: Import a Pre-built Dashboard") If the Aztec community has created shared dashboards: 1. Click **+** → **Import** 2. Enter dashboard ID or upload JSON file 3. Select **Aztec Prometheus** as the data source 4. Click **Import** ### Recommended Dashboard Panels[​](#recommended-dashboard-panels "Direct link to Recommended Dashboard Panels") Example panels you can create (adjust metric names based on what's actually available): 1. **Block Height Over Time**: Line graph tracking block sync progress 2. **Sync Rate**: Line graph showing blocks synced over time window (use `increase()` function) 3. **Peer Count**: Gauge showing P2P connections 4. **Memory Usage**: Line graph of `process_resident_memory_bytes` 5. **CPU Usage**: Line graph of `rate(process_cpu_seconds_total[5m])` ## Setting Up Alerts[​](#setting-up-alerts "Direct link to Setting Up Alerts") Configure alerts to notify you of issues: ### Step 1: Create an Alert Rule[​](#step-1-create-an-alert-rule "Direct link to Step 1: Create an Alert Rule") 1. In the left sidebar, click **Alerting** (bell icon) 2. Click **Alert rules** → **New alert rule** 3. Configure your alert: * **Query**: Select your Prometheus data source and metric (e.g., `aztec_archiver_block_height`) * **Condition**: Define the threshold (e.g., `increase(aztec_archiver_block_height[15m]) == 0` to alert if no blocks in 15 minutes) * **Evaluation interval**: How often to check (e.g., 1m) 4. Click **Save** ### Step 2: Configure Contact Points[​](#step-2-configure-contact-points "Direct link to Step 2: Configure Contact Points") 1. Under **Alerting**, click **Contact points** 2. Click **Add contact point** 3. Choose your notification method: * **Email**: Configure SMTP settings * **Slack**: Add webhook URL * **PagerDuty**: Add integration key * **Webhook**: Custom HTTP endpoint 4. Click **Save** ### Step 3: Create Notification Policies[​](#step-3-create-notification-policies "Direct link to Step 3: Create Notification Policies") 1. Under **Alerting**, click **Notification policies** 2. Click **New notification policy** 3. Define routing rules to send alerts to specific contact points 4. Click **Save** ## Example Alert Rules[​](#example-alert-rules "Direct link to Example Alert Rules") ### Node Sync Alert[​](#node-sync-alert "Direct link to Node Sync Alert") Alert if the node stops syncing blocks: * **Query**: `increase(aztec_archiver_block_height[15m])` * **Condition**: `== 0` * **Description**: Node has not synced any blocks in the last 15 minutes ### High Memory Usage Alert[​](#high-memory-usage-alert "Direct link to High Memory Usage Alert") Alert if memory usage exceeds threshold: * **Query**: `process_resident_memory_bytes` * **Condition**: `> 8000000000` (8GB) * **Description**: Node memory usage exceeds 8GB ### Peer Connection Alert[​](#peer-connection-alert "Direct link to Peer Connection Alert") Alert if peer count drops too low: * **Query**: `aztec_peer_manager_peer_count_peers` * **Condition**: `< 5` * **Description**: Node has fewer than 5 peer connections ## Next Steps[​](#next-steps "Direct link to Next Steps") * Explore the [Monitoring Overview](/operate/testnet/operators/monitoring.md) for troubleshooting and metrics reference * Join the [Aztec Discord](https://discord.gg/aztec) to share dashboards with the community * Configure additional notification channels for your alerts --- # Key Metrics Reference ## Overview[​](#overview "Direct link to Overview") Your Aztec node exposes metrics through OpenTelemetry to help you monitor performance, health, and operational status. This guide covers key metrics across node types and how to use them effectively. Discovering Metrics Once your monitoring stack is running, you can discover available metrics in the Prometheus UI at `http://localhost:9090/graph`. Start typing in the query box to see autocomplete suggestions for metrics exposed by your node. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Complete monitoring stack setup following the [Monitoring Overview](/operate/testnet/operators/monitoring.md) * Ensure Prometheus is running and scraping metrics from your OTEL collector * Verify access to Prometheus UI at `http://localhost:9090` Metric Names May Vary The exact metric names and labels in this guide depend on your node type, version, and configuration. Always verify the actual metrics exposed by your node using the Prometheus UI metrics explorer at `http://localhost:9090/graph`. Common prefixes: `aztec_archiver_*`, `aztec_sequencer_*`, `aztec_prover_*`, `process_*`. ## Querying with PromQL[​](#querying-with-promql "Direct link to Querying with PromQL") Use Prometheus Query Language (PromQL) to query and analyze your metrics. Understanding these basics will help you read the alert rules throughout this guide. ### Basic Queries[​](#basic-queries "Direct link to Basic Queries") ``` # Instant vector - current value aztec_archiver_block_height # Range vector - values over time aztec_archiver_block_height[5m] ``` ### Rate and Increase[​](#rate-and-increase "Direct link to Rate and Increase") ``` # Rate of change per second (for counters) rate(process_cpu_seconds_total[5m]) # Blocks synced over time window (for gauges) increase(aztec_archiver_block_height[1h]) # Derivative - per-second change rate of gauges deriv(process_resident_memory_bytes[30m]) ``` ### Arithmetic Operations[​](#arithmetic-operations "Direct link to Arithmetic Operations") Calculate derived metrics using basic math operators: ``` # Calculate percentage (block proposal failure rate) (increase(aztec_sequencer_slot_count[15m]) - increase(aztec_sequencer_slot_filled_count[15m])) / increase(aztec_sequencer_slot_count[15m]) # Convert to percentage scale rate(process_cpu_seconds_total[5m]) * 100 ``` ### Comparison Operators[​](#comparison-operators "Direct link to Comparison Operators") Filter and alert based on thresholds: ``` # Greater than rate(process_cpu_seconds_total[5m]) > 2.8 # Less than aztec_peer_manager_peer_count_peers < 5 # Equal to increase(aztec_archiver_block_height[15m]) == 0 # Not equal to aztec_sequencer_current_state != 1 ``` ### Time Windows[​](#time-windows "Direct link to Time Windows") Choose time windows based on metric behavior and alert sensitivity: * **Short windows** (`[5m]`, `[10m]`) - Detect immediate issues, sensitive to spikes * **Medium windows** (`[15m]`, `[30m]`) - Balance between responsiveness and stability, recommended for most alerts * **Long windows** (`[1h]`, `[2h]`) - Trend analysis, capacity planning, smooth out temporary fluctuations Example: `increase(aztec_archiver_block_height[15m])` checks if blocks were processed in the last 15 minutes - long enough to avoid false alarms from brief delays, short enough to catch real problems quickly. ## Core Node Metrics[​](#core-node-metrics "Direct link to Core Node Metrics") Your node exposes these foundational metrics for monitoring blockchain synchronization and network health. Configure immediate alerting for these metrics in all deployments. ### L2 Block Height Progress[​](#l2-block-height-progress "Direct link to L2 Block Height Progress") Track whether your node is actively processing new L2 blocks: * **Metric**: `aztec_archiver_block_height` * **Description**: Current L2 block number the node has synced to **Alert rule**: ``` - alert: L2BlockHeightNotIncreasing expr: increase(aztec_archiver_block_height{aztec_status=""}[15m]) == 0 for: 5m labels: severity: critical annotations: summary: "Aztec node not processing L2 blocks" description: "No L2 blocks processed in the last 15 minutes. Node may be stuck or out of sync." ``` ### Peer Connectivity[​](#peer-connectivity "Direct link to Peer Connectivity") Track the number of active P2P peers connected to your node: * **Metric**: `aztec_peer_manager_peer_count_peers` * **Description**: Number of outbound peers currently connected to the node **Alert rule**: ``` - alert: LowPeerCount expr: aztec_peer_manager_peer_count_peers < 5 for: 10m labels: severity: warning annotations: summary: "Low peer count detected" description: "Node has only {{ $value }} peers connected. Risk of network isolation." ``` ### L1 Block Height Progress[​](#l1-block-height-progress "Direct link to L1 Block Height Progress") Monitor whether your node is seeing new L1 blocks: * **Metric**: `aztec_l1_block_height` * **Description**: Latest L1 (Ethereum) block number seen by the node **Alert rule**: ``` - alert: L1BlockHeightNotIncreasing expr: increase(aztec_l1_block_height[15m]) == 0 for: 10m labels: severity: warning annotations: summary: "Node not seeing new L1 blocks" description: "No L1 block updates in 15 minutes. Check L1 RPC connection." ``` ## Sequencer Metrics[​](#sequencer-metrics "Direct link to Sequencer Metrics") If you're running a sequencer node, monitor these metrics for consensus participation, block production, and L1 publishing. Configure alerting for critical operations. ### L1 Publisher ETH Balance[​](#l1-publisher-eth-balance "Direct link to L1 Publisher ETH Balance") Monitor the ETH balance used for publishing to L1 to prevent transaction failures: * **Metric**: `aztec_l1_publisher_balance_eth` * **Description**: Current ETH balance of the L1 publisher account **Alert rule**: ``` - alert: LowL1PublisherBalance expr: aztec_l1_publisher_balance_eth < 0.5 for: 5m labels: severity: critical annotations: summary: "L1 publisher ETH balance critically low" description: "Publisher balance is {{ $value }} ETH. Refill immediately to avoid transaction failures." ``` ### Sequencer State[​](#sequencer-state "Direct link to Sequencer State") Monitor the operational state of the sequencer module: * **Metric**: `aztec_sequencer_current_state` * **Description**: Current state of the sequencer module (1 = OK/running, 0 = stopped/error) **Alert rule**: ``` - alert: SequencerNotHealthy expr: aztec_sequencer_current_state != 1 for: 2m labels: severity: critical annotations: summary: "Sequencer module not in healthy state" description: "Sequencer state is {{ $value }} (expected 1). Check sequencer logs immediately." ``` ### Block Proposal Failures[​](#block-proposal-failures "Direct link to Block Proposal Failures") Track failed block proposals by comparing slots to filled slots: * **Metrics**: `aztec_sequencer_slot_count` and `aztec_sequencer_slot_filled_count` * **Description**: Tracks slots assigned to your sequencer versus slots successfully filled. Alert triggers when the failure rate exceeds 5% over 15 minutes. **Alert rule**: ``` - alert: HighBlockProposalFailureRate expr: | (increase(aztec_sequencer_slot_count[15m]) - increase(aztec_sequencer_slot_filled_count[15m])) / increase(aztec_sequencer_slot_count[15m]) > 0.05 for: 5m labels: severity: warning annotations: summary: "High block proposal failure rate" description: "{{ $value | humanizePercentage }} of block proposals are failing in the last 15 minutes." ``` ### Blob Publishing Failures[​](#blob-publishing-failures "Direct link to Blob Publishing Failures") Track failures when publishing blobs to L1: * **Metric**: `aztec_l1_publisher_blob_tx_failure` * **Description**: Number of failed blob transaction submissions to L1 **Alert rule**: ``` - alert: BlobPublishingFailures expr: increase(aztec_l1_publisher_blob_tx_failure[15m]) > 0 for: 5m labels: severity: warning annotations: summary: "Blob publishing failures detected" description: "{{ $value }} blob transaction failures in the last 15 minutes. Check L1 gas prices and publisher balance." ``` ### Attestation Activity[​](#attestation-activity "Direct link to Attestation Activity") Track your sequencer's participation in the consensus protocol: * **Metrics**: Attestations submitted, attestation success rate, attestation timing * **Use cases**: * Verify your sequencer is actively participating * Monitor attestation success rate * Detect missed attestation opportunities ### Block Proposals[​](#block-proposals "Direct link to Block Proposals") Monitor block proposal activity and success: * **Metrics**: Blocks proposed, proposal success rate, proposal timing * **Use cases**: * Track block production performance * Identify proposal failures and causes * Monitor proposal timing relative to slot schedule ### Committee Participation[​](#committee-participation "Direct link to Committee Participation") Track your sequencer's involvement in consensus committees: * **Metrics**: Committee assignments, participation rate, duty execution * **Use cases**: * Verify your sequencer is assigned to committees * Monitor duty execution completion rate * Track committee participation over time ### Performance Metrics[​](#performance-metrics "Direct link to Performance Metrics") Measure block production efficiency: * **Metrics**: Block production time, validation latency, processing throughput * **Use cases**: * Optimize block production pipeline * Identify performance bottlenecks * Compare performance against network averages ## Prover Metrics[​](#prover-metrics "Direct link to Prover Metrics") If you're running a prover node, track these metrics for proof generation workload and resource utilization. ### Job Queue[​](#job-queue "Direct link to Job Queue") Monitor pending proof generation work: * **Metrics**: Queue depth, queue wait time, job age * **Use cases**: * Detect proof generation backlogs * Capacity planning for prover resources * Monitor job distribution across agents ### Proof Generation[​](#proof-generation "Direct link to Proof Generation") Track proof completion metrics: * **Metrics**: Proofs completed, completion time, success rate, failure reasons * **Use cases**: * Monitor proof generation throughput * Identify failing proof types * Track generation time trends ### Agent Utilization[​](#agent-utilization "Direct link to Agent Utilization") Monitor resource usage per proof agent: * **Metrics**: CPU usage per agent, memory allocation, GPU utilization (if applicable) * **Use cases**: * Optimize agent allocation * Detect resource constraints * Load balancing across agents ### Throughput[​](#throughput "Direct link to Throughput") Measure proof generation capacity: * **Metrics**: Jobs completed per time period, proofs per second, utilization rate * **Use cases**: * Capacity planning * Performance optimization * SLA monitoring ## System Metrics[​](#system-metrics "Direct link to System Metrics") Your node exposes standard infrastructure metrics through OpenTelemetry and the runtime environment. ### CPU Usage[​](#cpu-usage "Direct link to CPU Usage") Monitor process and system CPU utilization: * **Metric**: `process_cpu_seconds_total` * **Description**: Cumulative CPU time consumed by the process in seconds **Alert rules**: ``` # Note: Adjust thresholds based on your system's CPU core count. # Example below assumes a 4-core system (70% = 2.8 cores, 85% = 3.4 cores) - alert: HighCPUUsage expr: rate(process_cpu_seconds_total[5m]) > 2.8 for: 10m labels: severity: warning annotations: summary: "High CPU usage detected" description: "Node using {{ $value }} CPU cores (above 2.8 threshold). Consider scaling resources." ``` ### Memory Usage[​](#memory-usage "Direct link to Memory Usage") Track RAM consumption: * **Metric**: `process_resident_memory_bytes` * **Description**: Resident memory size in bytes **Alert rules**: ``` - alert: HighMemoryUsage expr: process_resident_memory_bytes > 8000000000 for: 5m labels: severity: warning annotations: summary: "High memory usage detected" description: "Memory usage is {{ $value | humanize1024 }}B. Consider increasing available RAM or investigating memory leaks." ``` **Additional monitoring**: * Track memory growth rate to detect leaks * Monitor garbage collection metrics for runtime efficiency ### Disk I/O[​](#disk-io "Direct link to Disk I/O") Monitor storage operations: * **Metrics**: Disk read/write rates, I/O latency, disk utilization * **Use cases**: * Identify I/O bottlenecks * Plan storage upgrades * Detect disk performance degradation ### Network Bandwidth[​](#network-bandwidth "Direct link to Network Bandwidth") Track network throughput: * **Metrics**: Bytes sent/received, packet rates, connection counts * **Use cases**: * Monitor P2P bandwidth usage * Capacity planning for network resources * Detect unusual traffic patterns ## Creating Dashboards in Grafana[​](#creating-dashboards-in-grafana "Direct link to Creating Dashboards in Grafana") Organize your Grafana dashboards by operational focus to make monitoring efficient and actionable. For specific panel configurations and queries, see the [Grafana Setup](/operate/testnet/operators/monitoring/grafana-setup.md) guide. ### Dashboard Organization Strategy[​](#dashboard-organization-strategy "Direct link to Dashboard Organization Strategy") **Overview Dashboard** - At-a-glance health check * L2 and L1 block height progression * Peer connectivity status * Critical alerts summary * Resource utilization (CPU, memory) * Use stat panels and gauges for current values * Include time-series graphs for trends **Performance Dashboard** - Deep-dive into operational metrics * Block processing rates and latencies * Transaction throughput * Network bandwidth utilization * Query response times * Use percentile graphs (p50, p95, p99) for latency metrics * Compare current performance against historical baselines **Resource Dashboard** - Infrastructure monitoring * CPU usage per core * Memory allocation and garbage collection * Disk I/O rates and latency * Network packet rates * Set threshold warning lines at 70-80% utilization * Include growth trend projections **Role-Specific Dashboards** - Specialized metrics by node type * **Sequencer Dashboard**: Block proposals, attestations, committee participation, L1 publisher balance * **Prover Dashboard**: Job queue depth, proof generation rates, agent utilization, success rates * Focus on metrics unique to the role's responsibilities * Include SLA tracking and performance benchmarks ## Best Practices[​](#best-practices "Direct link to Best Practices") ### Metric Collection[​](#metric-collection "Direct link to Metric Collection") 1. **Appropriate Scrape Intervals**: Balance data granularity against storage costs * Standard: 15s for most metrics * High-frequency: 5s for critical real-time metrics * Low-frequency: 60s for slow-changing metrics 2. **Retention Policy**: Configure based on operational needs * Short-term: 7-15 days for detailed troubleshooting * Long-term: 30-90 days for trend analysis * Archive: Consider downsampling for longer retention 3. **Label Cardinality**: Avoid high-cardinality labels that explode metric storage * Good: `instance`, `node_type`, `region` * Avoid: `user_id`, `transaction_hash`, `timestamp` ### Monitoring Strategy[​](#monitoring-strategy "Direct link to Monitoring Strategy") 1. **Layered Monitoring**: Monitor at multiple levels * Infrastructure: CPU, memory, disk, network * Application: Block height, peers, throughput * Business: Transaction success rate, user activity 2. **Proactive Alerts**: Set alerts before problems become critical * Use warning and critical thresholds * Alert on trends, not just absolute values * Reduce alert fatigue with proper tuning 3. **Dashboard Discipline**: Keep dashboards focused and actionable * Separate dashboards by role and concern * Include relevant context in panel titles * Add threshold lines and annotations ## Next Steps[​](#next-steps "Direct link to Next Steps") * Explore advanced PromQL queries in the [Prometheus documentation](https://prometheus.io/docs/prometheus/latest/querying/basics/) * Set up alerting rules following the [Prometheus alerting guide](https://prometheus.io/docs/alerting/latest/overview/) * Configure notification channels in [Grafana](/operate/testnet/operators/monitoring/grafana-setup.md) * Return to [Monitoring Overview](/operate/testnet/operators/monitoring.md) * Join the [Aztec Discord](https://discord.gg/aztec) to share dashboards with the community --- # OpenTelemetry Collector Setup ## Overview[​](#overview "Direct link to Overview") The OpenTelemetry Collector receives metrics from your Aztec node and exports them to Prometheus for storage and analysis. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * A running Aztec node with Docker Compose * Basic understanding of Docker networking ## Setup Steps[​](#setup-steps "Direct link to Setup Steps") ### Step 1: Create Configuration File[​](#step-1-create-configuration-file "Direct link to Step 1: Create Configuration File") Create an `otel-collector-config.yml` file in the same directory as your existing `docker-compose.yml`: ``` receivers: otlp: protocols: http: endpoint: 0.0.0.0:4318 grpc: endpoint: 0.0.0.0:4317 exporters: prometheus: endpoint: "0.0.0.0:8889" metric_expiration: 5m processors: batch: service: pipelines: metrics: receivers: [otlp] exporters: - prometheus ``` This configuration: * Receives metrics via OTLP (OpenTelemetry Protocol) on ports 4317 (gRPC) and 4318 (HTTP) * Exports metrics to Prometheus format on port 8889 * Uses batch processing for efficiency ### Step 2: Add OTEL Collector to Docker Compose[​](#step-2-add-otel-collector-to-docker-compose "Direct link to Step 2: Add OTEL Collector to Docker Compose") Add the following to your existing `docker-compose.yml` file: ``` services: # ... existing services ... otel-collector: image: otel/opentelemetry-collector container_name: aztec-otel ports: - 8888:8888 # OTEL collector metrics endpoint - 8889:8889 # Prometheus exporter endpoint - 4317:4317 # OTLP gRPC receiver - 4318:4318 # OTLP HTTP receiver volumes: - ./otel-collector-config.yml:/etc/otel-collector-config.yml command: >- --config=/etc/otel-collector-config.yml networks: - aztec restart: always ``` ### Step 3: Configure Your Node to Export Metrics[​](#step-3-configure-your-node-to-export-metrics "Direct link to Step 3: Configure Your Node to Export Metrics") Configure your Aztec node to export metrics to the OTEL collector. **Step 3a: Add to .env file** Add these variables to your `.env` file: ``` OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://otel-collector:4318/v1/metrics ``` **Step 3b: Update docker-compose.yml** Add these environment variables to your node's service in `docker-compose.yml`: ``` services: aztec-node: # or aztec-sequencer, prover-node, etc. # ... existing configuration ... environment: # ... existing environment variables ... OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: ${OTEL_EXPORTER_OTLP_METRICS_ENDPOINT} ``` **Network configuration:** Since your node and OTEL collector share the same Docker Compose file and `aztec` network, use the service name `otel-collector` in the endpoint URL as shown above. ### Step 4: Start Services[​](#step-4-start-services "Direct link to Step 4: Start Services") ``` # Start or restart all services docker compose up -d ``` ### Step 5: Verify Metrics Collection[​](#step-5-verify-metrics-collection "Direct link to Step 5: Verify Metrics Collection") Check that metrics are being collected: ``` # View OTEL collector logs docker compose logs -f otel-collector # Query Prometheus endpoint curl http://localhost:8889/metrics ``` You should see metrics in Prometheus format. ## Next Steps[​](#next-steps "Direct link to Next Steps") * Proceed to [Prometheus Setup](/operate/testnet/operators/monitoring/prometheus-setup.md) to configure metric storage and querying * Return to [Monitoring Overview](/operate/testnet/operators/monitoring.md) --- # Prometheus Setup ## Overview[​](#overview "Direct link to Overview") Prometheus scrapes and stores the metrics exposed by the OTEL collector, providing a time-series database for querying and analysis. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Completed [OpenTelemetry Collector Setup](/operate/testnet/operators/monitoring/otel-setup.md) * OTEL collector running and exposing metrics on port 8889 ## Setup Steps[​](#setup-steps "Direct link to Setup Steps") ### Step 1: Create Prometheus Configuration[​](#step-1-create-prometheus-configuration "Direct link to Step 1: Create Prometheus Configuration") Create a `prometheus.yml` file: ``` global: scrape_interval: 15s evaluation_interval: 15s scrape_configs: - job_name: 'aztec-node' static_configs: - targets: ['otel-collector:8889'] labels: instance: 'aztec-node-1' ``` If you're running multiple nodes, adjust the `instance` label to uniquely identify each node. ### Step 2: Add Prometheus to Docker Compose[​](#step-2-add-prometheus-to-docker-compose "Direct link to Step 2: Add Prometheus to Docker Compose") Add Prometheus to your `docker-compose.yml`: ``` services: # ... existing services (otel-collector, etc.) ... prometheus: image: prom/prometheus:latest container_name: aztec-prometheus ports: - 9090:9090 volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml - prometheus-data:/prometheus command: - '--config.file=/etc/prometheus/prometheus.yml' - '--storage.tsdb.path=/prometheus' - '--storage.tsdb.retention.time=30d' networks: - aztec restart: always volumes: prometheus-data: ``` ### Step 3: Start Prometheus[​](#step-3-start-prometheus "Direct link to Step 3: Start Prometheus") ``` docker compose up -d ``` ### Step 4: Verify Prometheus[​](#step-4-verify-prometheus "Direct link to Step 4: Verify Prometheus") Access Prometheus UI at `http://localhost:9090` and verify: 1. Go to **Status → Targets** to check that the `aztec-node` target is up 2. Go to **Graph** and query a metric (e.g., `aztec_archiver_block_height`) ## Using Prometheus[​](#using-prometheus "Direct link to Using Prometheus") ### Query Metrics[​](#query-metrics "Direct link to Query Metrics") Use the Prometheus UI to explore and query metrics: 1. Navigate to `http://localhost:9090/graph` 2. Enter a metric name in the query box (use autocomplete to discover available metrics) 3. Click **Execute** to see the results 4. Switch between **Table** and **Graph** views ### Example Queries[​](#example-queries "Direct link to Example Queries") ``` # Current block height aztec_archiver_block_height # Blocks synced over time window increase(aztec_archiver_block_height[5m]) # Memory usage process_resident_memory_bytes # CPU usage rate rate(process_cpu_seconds_total[5m]) ``` ## Next Steps[​](#next-steps "Direct link to Next Steps") * Proceed to [Grafana Setup](/operate/testnet/operators/monitoring/grafana-setup.md) to configure visualization and alerting * Return to [Monitoring Overview](/operate/testnet/operators/monitoring.md) --- # Complete Example and Troubleshooting ## Complete Docker Compose Example[​](#complete-docker-compose-example "Direct link to Complete Docker Compose Example") Here's a complete example with all monitoring components integrated with your Aztec node: ``` services: # Your Aztec node (example for full node) aztec-node: image: "aztecprotocol/aztec:5.0.0-rc.2" container_name: "aztec-node" ports: - ${AZTEC_PORT}:${AZTEC_PORT} - ${P2P_PORT}:${P2P_PORT} - ${P2P_PORT}:${P2P_PORT}/udp volumes: - ${DATA_DIRECTORY}:/var/lib/data environment: DATA_DIRECTORY: /var/lib/data LOG_LEVEL: ${LOG_LEVEL} ETHEREUM_HOSTS: ${ETHEREUM_HOSTS} L1_CONSENSUS_HOST_URLS: ${L1_CONSENSUS_HOST_URLS} P2P_IP: ${P2P_IP} P2P_PORT: ${P2P_PORT} AZTEC_PORT: ${AZTEC_PORT} OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: http://otel-collector:4318/v1/metrics entrypoint: >- node --no-warnings /usr/src/yarn-project/aztec/dest/bin/index.js start --node --network testnet networks: - aztec restart: always # OpenTelemetry Collector otel-collector: image: otel/opentelemetry-collector container_name: aztec-otel ports: - 8888:8888 - 8889:8889 - 4317:4317 - 4318:4318 volumes: - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml command: >- --config=/etc/otel-collector-config.yaml networks: - aztec restart: always # Prometheus prometheus: image: prom/prometheus:latest container_name: aztec-prometheus ports: - 9090:9090 volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml - prometheus-data:/prometheus command: - "--config.file=/etc/prometheus/prometheus.yml" - "--storage.tsdb.path=/prometheus" - "--storage.tsdb.retention.time=30d" networks: - aztec restart: always # Grafana grafana: image: grafana/grafana:latest container_name: aztec-grafana ports: - 3000:3000 volumes: - grafana-data:/var/lib/grafana environment: - GF_SECURITY_ADMIN_PASSWORD=your-secure-password - GF_USERS_ALLOW_SIGN_UP=false networks: - aztec restart: always volumes: prometheus-data: grafana-data: networks: aztec: name: aztec ``` This configuration includes: * Your Aztec node configured to export metrics to the OTEL collector * OpenTelemetry Collector to receive and process metrics * Prometheus to store time-series data with 30-day retention * Grafana for visualization and alerting * Persistent volumes for Prometheus and Grafana data * All services on the same Docker network for easy communication ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Metrics not appearing[​](#metrics-not-appearing "Direct link to Metrics not appearing") **Issue**: No metrics showing in Prometheus or Grafana. **Solutions**: * Verify OTEL collector is running: `docker compose ps otel-collector` * Check OTEL collector logs: `docker compose logs otel-collector` * Verify node is configured with correct OTEL endpoints * Test OTEL collector endpoint: `curl http://localhost:8889/metrics` * Ensure all containers are on the same Docker network ### Prometheus target down[​](#prometheus-target-down "Direct link to Prometheus target down") **Issue**: Prometheus shows target as "down" in Status → Targets. **Solutions**: * Verify OTEL collector is running and exposing port 8889 * Check Prometheus configuration in `prometheus.yml` * Ensure target address is correct (use service name if in same Docker network) * Review Prometheus logs: `docker compose logs prometheus` ### Grafana cannot connect to Prometheus[​](#grafana-cannot-connect-to-prometheus "Direct link to Grafana cannot connect to Prometheus") **Issue**: Grafana shows "Bad Gateway" or cannot query Prometheus. **Solutions**: * Verify Prometheus is running: `docker compose ps prometheus` * Check data source URL in Grafana (should be `http://prometheus:9090`) * Test Prometheus endpoint: `curl http://localhost:9090/api/v1/query?query=up` * Ensure Grafana and Prometheus are on the same Docker network ## Next Steps[​](#next-steps "Direct link to Next Steps") * Set up alerting rules in Prometheus for critical conditions * Create custom dashboards for your specific monitoring needs * Configure notification channels (Slack, PagerDuty, email) in Grafana * Explore advanced PromQL queries for deeper insights * Join the [Aztec Discord](https://discord.gg/aztec) to share dashboards with the community --- # FAQs & Common Issues ## Overview[​](#overview "Direct link to Overview") This guide addresses common issues node operators encounter when running Aztec nodes. Each entry includes the issue symptoms, possible causes, and step-by-step solutions. If your issue isn't listed here, visit the [Aztec Discord](https://discord.gg/aztec) in the `#operator-faq` channel for community support. ## Node Sync Issues[​](#node-sync-issues "Direct link to Node Sync Issues") ### SYNC\_BLOCK Failed Error[​](#sync_block-failed-error "Direct link to SYNC_BLOCK Failed Error") **Symptom**: You see this error in your node logs: ``` ERROR: world-state:database Call SYNC_BLOCK failed: Error: Can't synch block: block state does not match world state ``` **Cause**: Your local database state is corrupted or out of sync with the network. **Solution**: 1. Stop your node: ``` docker compose down ``` 2. Remove the archiver data directory: ``` rm -rf ~/.aztec/v5.0.0-rc.2/data/archiver ``` 3. Restart your node: ``` docker compose up -d ``` Data Loss and Resync This process removes local state and requires full resynchronization. Consider using snapshot sync mode (`SYNC_MODE=snapshot`) to speed up recovery. See the [syncing best practices guide](/operate/testnet/operators/setup/syncing_best_practices.md) for more information. ### Error Getting Slot Number[​](#error-getting-slot-number "Direct link to Error Getting Slot Number") **Symptom**: Your logs show "Error getting slot number" related to beacon or execution endpoints. **Cause**: * **Beacon-related errors**: Failed to connect to your L1 consensus (beacon) RPC endpoint * **Execution-related errors**: Failed to connect to your L1 execution RPC endpoint or reporting routine issue **Solutions**: 1. **Verify L1 endpoint configuration**: * Check your `L1_CONSENSUS_HOST_URLS` setting points to your beacon node * Check your `ETHEREUM_HOSTS` setting points to your execution client * Ensure URLs are formatted correctly (e.g., `http://localhost:5052` for beacon) 2. **Test endpoint connectivity**: ``` # Test beacon endpoint curl [YOUR_BEACON_ENDPOINT]/eth/v1/beacon/headers # Test execution endpoint curl -X POST -H "Content-Type: application/json" \ --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \ [YOUR_EXECUTION_ENDPOINT] ``` 3. **Verify L1 clients are synced**: * Check that your beacon node is fully synced * Check that your execution client is fully synced * Use `docker compose logs` or check L1 client logs for sync status 4. **Check for rate limiting** (if using third-party RPC): * See the "RPC and Rate Limiting" section below * Consider using your own L1 node for better reliability ## RPC and Rate Limiting[​](#rpc-and-rate-limiting "Direct link to RPC and Rate Limiting") ### RPC Rate Limit or Quota Exceeded[​](#rpc-rate-limit-or-quota-exceeded "Direct link to RPC Rate Limit or Quota Exceeded") **Symptom**: Your logs show errors like: ``` Error: quota limit exceeded Error: rate limit exceeded Error: too many requests ``` **Cause**: Your RPC provider is throttling requests due to rate limits or quota restrictions. **Solutions**: 1. **Register for an API key with your RPC provider**: * Most providers (Infura, Alchemy, QuickNode) offer higher limits with authenticated requests * Update your configuration to include the API key in your RPC URL * Example: `https://mainnet.infura.io/v3/YOUR_API_KEY` 2. **Use your own L1 node** (recommended for sequencers): * Running your own Ethereum node eliminates rate limits entirely * Provides better performance, reliability, and privacy * See [Eth Docker's guide](https://ethdocker.com/Usage/QuickStart) for setup instructions * Ensure you're running both execution and consensus clients 3. **Configure multiple RPC endpoints for failover**: * Aztec nodes support comma-separated RPC URLs * Example: `ETHEREUM_HOSTS=https://rpc1.example.com,https://rpc2.example.com` * The node will automatically fail over if one endpoint is unavailable Run Your Own L1 Infrastructure Sequencer operators should always run their own L1 infrastructure to ensure reliability, avoid rate limits, and maintain optimal performance. Third-party RPC providers are suitable for testing but not recommended for production sequencer operations. ### Blob Retrieval Errors[​](#blob-retrieval-errors "Direct link to Blob Retrieval Errors") **Symptom**: Your logs show errors like: ``` Error: No blob bodies found Error: Unable to get blob sidecar, Gateway Time-out (504) ``` **Cause**: Your beacon node endpoint is slow, overloaded, rate-limited, or not synced properly. **Solutions**: 1. **Verify beacon endpoint configuration**: ``` # Check L1_CONSENSUS_HOST_URLS in your configuration # Should point to your beacon node's API endpoint ``` 2. **Test beacon endpoint health**: ``` # Check if beacon node is responding curl [YOUR_BEACON_ENDPOINT]/eth/v1/node/health # Check sync status curl [YOUR_BEACON_ENDPOINT]/eth/v1/node/syncing ``` 3. **Ensure beacon node is fully synced**: * Check your beacon client logs * Verify the sync status shows as synced * Blob data is only available for recent blocks (typically 18 days) 4. **Run your own beacon node** (recommended): * Using a third-party beacon endpoint may have rate limits * Running your own provides better reliability and eliminates timeouts * See the [prerequisites guide](/operate/testnet/operators/prerequisites.md) for L1 infrastructure setup ## L1 Node Requirements[​](#l1-node-requirements "Direct link to L1 Node Requirements") ### Do I Need an L1 Archive Node?[​](#do-i-need-an-l1-archive-node "Direct link to Do I Need an L1 Archive Node?") No. You do not need an L1 archive node to run an Aztec node. Snapshot sync is the recommended approach and works with standard L1 full nodes. To use snapshot sync, set `SYNC_MODE=snapshot` in your configuration. ### Consensus RPC and Blob Availability (Testnet)[​](#consensus-rpc-and-blob-availability-testnet "Direct link to Consensus RPC and Blob Availability (Testnet)") On testnet, your L1 consensus (beacon) RPC endpoint must be able to serve **all blob data**. Standard beacon nodes only retain blobs for approximately 18 days (4096 epochs), which may not be sufficient. To meet this requirement, you need a consensus node configured as either a: * **Supernode**: Stores all 128/128 data columns regardless of validator staking weight. This provides full blob availability. * **Semi-supernode**: Stores enough data columns (typically 64/128) to reconstruct blobs. This is sufficient for testnet participation. This requirement is specific to testnet. On mainnet, standard beacon nodes with default blob retention are expected to be sufficient. ## Funding and Resources[​](#funding-and-resources "Direct link to Funding and Resources") ### Insufficient L1 Funds[​](#insufficient-l1-funds "Direct link to Insufficient L1 Funds") **Symptom**: Your sequencer cannot publish blocks, and logs show: ``` Error: Insufficient L1 funds Error: insufficient funds for gas * price + value ``` **Cause**: Your publisher address doesn't have enough Sepolia ETH to pay for L1 gas fees. **Solutions**: 1. **Get Sepolia ETH from a faucet**: * [Sepolia Faucet](https://sepoliafaucet.com/) * [Alchemy Sepolia Faucet](https://www.alchemy.com/faucets/ethereum-sepolia) * [Infura Sepolia Faucet](https://www.infura.io/faucet/sepolia) For Aztec testnet tokens (TST), use the **[Aztec Testnet Faucet](https://testnet.aztec.network/)**. 2. **Maintain sufficient balance**: * Keep at least **0.1 ETH** in your publisher account at all times * Monitor your balance regularly to avoid running out * Falling below the minimum balance may result in slashing 3. **Set up balance monitoring**: ``` # Check your publisher balance cast balance [YOUR_PUBLISHER_ADDRESS] --rpc-url [YOUR_RPC_URL] ``` 4. **Configure alerts**: * Set up monitoring to alert you when balance drops below 0.15 ETH * This gives you time to top up before hitting the critical threshold Slashing Risk Sequencers with insufficient funds in their publisher account risk being slashed. Always maintain at least 0.1 ETH to ensure uninterrupted operation and avoid penalties. ## Updates and Maintenance[​](#updates-and-maintenance "Direct link to Updates and Maintenance") #### Version-Specific Updates:[​](#version-specific-updates "Direct link to Version-Specific Updates:") To update to a specific version: ``` # Change the image tag from: image: "aztecprotocol/aztec:latest" # To: image: "aztecprotocol/aztec:5.0.0-rc.2" ``` Then run: ``` docker compose pull docker compose down docker compose up -d ``` Stay Informed About Updates Join the [Aztec Discord](https://discord.gg/aztec) and follow the announcements channel to stay informed about new releases and required updates. ## Network and Connectivity[​](#network-and-connectivity "Direct link to Network and Connectivity") ### Port Forwarding Not Working[​](#port-forwarding-not-working "Direct link to Port Forwarding Not Working") **Symptom**: Your node cannot discover peers or shows "0 peers connected" in logs. **Cause**: Firewall rules or router configuration are blocking P2P connections. **Solutions**: 1. **Verify your external IP address**: ``` curl ipv4.icanhazip.com ``` Confirm this matches your `P2P_IP` configuration. 2. **Test port connectivity**: ``` # From another machine, test if your P2P port is accessible nc -zv [YOUR_EXTERNAL_IP] 40400 ``` 3. **Configure router port forwarding**: * Log into your router's admin interface * Forward port 40400 (TCP and UDP) to your node's local IP address * Save and restart router if needed 4. **Check local firewall rules**: ``` # Linux: Allow P2P port through firewall sudo ufw allow 40400/tcp sudo ufw allow 40400/udp # Verify rules sudo ufw status ``` 5. **Verify Docker network settings**: * Ensure ports are properly mapped in docker-compose.yml * Check that `P2P_PORT` environment variable matches the exposed ports ## Other Common Issues[​](#other-common-issues "Direct link to Other Common Issues") ### CodeError: Stream Reset[​](#codeerror-stream-reset "Direct link to CodeError: Stream Reset") **Symptom**: You occasionally see this error in logs: ``` CodeError: stream reset ``` **Cause**: Temporary P2P connection disruption. This is normal network behavior and occurs when peer connections are interrupted. **Impact**: This is safe to ignore. Your node automatically reconnects to peers and maintains network connectivity. **Action Required**: None. This is expected behavior in P2P networks. ### Keystore Not Loading[​](#keystore-not-loading "Direct link to Keystore Not Loading") **Symptom**: Your sequencer fails to start with errors about invalid keys or missing keystore. **Cause**: Keystore file is improperly formatted, missing, or has incorrect permissions. **Solutions**: 1. **Verify keystore.json format**: ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "0xYOUR_ETH_PRIVATE_KEY_HERE", "bls": "0xYOUR_BLS_PRIVATE_KEY_HERE" }, "publisher": ["0xYOUR_PUBLISHER_KEY_HERE"], "coinbase": "0xYOUR_COINBASE_ADDRESS", "feeRecipient": "0xYOUR_AZTEC_ADDRESS" } ] } ``` 2. **Validate private key format**: * Keys should start with `0x` * Keys should be 64 hexadecimal characters (plus the `0x` prefix) * No spaces or extra characters * The attester must contain both `eth` and `bls` keys 3. **Check file permissions**: ``` # Ensure keystore is readable chmod 600 ~/.aztec/keys/keystore.json # Verify ownership ls -la ~/.aztec/keys/ ``` 4. **Verify keystore directory path**: * Ensure `KEY_STORE_DIRECTORY` environment variable is set in your `.env` file * Verify the volume mount in `docker-compose.yml` points to the correct directory For more information on keystore configuration and creation, see the [Creating Validator Keystores guide](/operate/testnet/operators/keystore/creating_keystores.md) and the [Advanced Keystore Usage guide](/operate/testnet/operators/keystore.md). ### Docker Container Won't Start[​](#docker-container-wont-start "Direct link to Docker Container Won't Start") **Symptom**: Docker container crashes immediately after starting or won't start at all. **Cause**: Various issues including configuration errors, insufficient resources, or port conflicts. **Solutions**: 1. **Check container logs**: ``` docker compose logs aztec-sequencer ``` Look for specific error messages that indicate the problem. 2. **Verify Docker resources**: * Ensure sufficient disk space: `df -h` * Check Docker has adequate memory allocated (16GB+ recommended) * Verify CPU resources are available 3. **Check environment file format**: ``` # Verify .env file exists and is properly formatted cat .env # No spaces around = signs # No quotes around values (unless necessary) ``` 4. **Verify port availability**: ``` # Check if ports are already in use lsof -i :8080 lsof -i :40400 ``` 5. **Update Docker and Docker Compose**: ``` # Check versions docker --version docker compose version # Update if needed sudo apt-get update && sudo apt-get upgrade docker-ce docker-compose-plugin ``` 6. **Try a clean restart**: ``` docker compose down docker compose pull docker compose up -d ``` ## Getting Additional Help[​](#getting-additional-help "Direct link to Getting Additional Help") If you've tried the solutions above and are still experiencing issues: 1. **Gather diagnostic information**: * Recent log output from your node * Your configuration (remove private keys!) * Aztec version you're running * Operating system and hardware specs 2. **Check existing issues**: * Browse the [Aztec GitHub issues](https://github.com/AztecProtocol/aztec-packages/issues) * Search for similar problems and solutions 3. **Ask for help**: * Join the [Aztec Discord](https://discord.gg/aztec) * Post in the `#operator-faq` or `#operator-support` channel * Include your diagnostic information * Be specific about what you've already tried ## Next Steps[​](#next-steps "Direct link to Next Steps") * Review [monitoring setup](/operate/testnet/operators/monitoring.md) to catch issues early with metrics and alerts * Check the [CLI reference](/operate/testnet/operators/reference/cli-reference.md) for all configuration options * Join the [Aztec Discord](https://discord.gg/aztec) for real-time operator support --- # Prerequisites ## Overview[​](#overview "Direct link to Overview") This guide covers the prerequisites and setup requirements for running nodes on the Aztec network. ## Common Prerequisites[​](#common-prerequisites "Direct link to Common Prerequisites") The following prerequisites apply to all node types. ### Operating System[​](#operating-system "Direct link to Operating System") The node software can be run on any Unix system released after 2020. * Linux (common flavors) * MacOS (ARM and intel) ### Docker and Docker Compose[​](#docker-and-docker-compose "Direct link to Docker and Docker Compose") Docker and Docker Compose are required for all node types. All Aztec nodes run in Docker containers managed by Docker Compose. **On Linux:** Install Docker Engine and Docker Compose separately: 1. Install Docker: ``` curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh ``` 2. Add your user to the docker group so `sudo` is not needed: ``` sudo groupadd docker sudo usermod -aG docker $USER newgrp docker # Test without sudo docker run hello-world ``` 3. Install Docker Compose by following the [Docker Compose installation guide](https://docs.docker.com/compose/install/). **On macOS:** Install [Docker Desktop](https://docs.docker.com/desktop/install/mac-install/), which includes both Docker and Docker Compose. ### Aztec Toolchain[​](#aztec-toolchain "Direct link to Aztec Toolchain") The Aztec toolchain provides CLI utilities for key generation, validator registration, and other operational tasks. While not required for running nodes (which use Docker Compose), it is needed for: * Generating validator keystores and creating staking registration data (`aztec validator-keys`) * Registering sequencers on L1 (`aztec add-l1-validator`) Install the Aztec toolchain using the official installer: ``` VERSION=5.0.0-rc.2 bash -i <(curl -sL https://install.aztec.network/5.0.0-rc.2) ``` macOS users macOS ships with an outdated version of Bash (v3.2) that is known to cause issues with the installer. Install a modern version with [Homebrew](https://brew.sh/): ``` brew install bash ``` Even if you use zsh as your default shell, the installer explicitly invokes `bash`. If the installer still picks up the old version, add the Homebrew `bash` to your `$PATH` or [set it as your default shell](https://support.apple.com/en-gb/guide/terminal/trml113/mac). ### L1 Ethereum Node Access[​](#l1-ethereum-node-access "Direct link to L1 Ethereum Node Access") All Aztec nodes require access to Ethereum L1 node endpoints: * **Execution client endpoint** (e.g., Geth, Nethermind, Besu, Erigon) * **Consensus client endpoint** (e.g., Prysm, Lighthouse, Teku, Nimbus) **Options:** 1. **Run your own L1 node** (recommended for best performance): * Better performance and lower latency * No rate limiting or request throttling * Greater reliability and uptime control * Enhanced privacy for your node operations * See [Eth Docker's guide](https://ethdocker.com/Usage/QuickStart) for setup instructions 2. **Use a third-party RPC provider**: * Easier to set up initially * May have rate limits and throttling * Ensure the provider supports beacon apis High Throughput Required Your L1 endpoints must support high throughput to avoid degraded node performance. ### Port Forwarding and Connectivity[​](#port-forwarding-and-connectivity "Direct link to Port Forwarding and Connectivity") For nodes participating in the P2P network (full nodes, sequencers, provers), proper port configuration is essential: **Required steps:** 1. Configure your router to forward both UDP and TCP traffic on your P2P port (default: 40400) to your node's local IP address 2. Ensure your firewall allows traffic on the required ports: * P2P port: 40400 (default, both TCP and UDP) * HTTP API port: 8080 (default) 3. Set the `P2P_IP` environment variable to your external IP address 4. Verify the P2P port is accessible from the internet **Find your public IP address:** ``` curl ipv4.icanhazip.com ``` **Verify port connectivity:** ``` # For TCP traffic on port 40400 nc -zv [YOUR_EXTERNAL_IP] 40400 # For UDP traffic on port 40400 nc -zuv [YOUR_EXTERNAL_IP] 40400 ``` Port Forwarding Required If port forwarding isn't properly configured, your node may not be able to participate in P2P duties. ## Next Steps[​](#next-steps "Direct link to Next Steps") Once you have met the prerequisites, proceed to set up your desired node type: * [Run a Full Node →](/operate/testnet/operators/setup/running_a_node.md) * [Run a Sequencer Node →](/operate/testnet/operators/setup/sequencer_management.md) * [Run a Prover Node →](/operate/testnet/operators/setup/running_a_prover.md) --- # Changelog ## Overview[​](#overview "Direct link to Overview") This changelog documents all configuration changes, new features, and breaking changes across Aztec node versions. Each version has a dedicated page with detailed migration instructions. ## Version history[​](#version-history "Direct link to Version history") ### [v4.2.0](/operate/testnet/operators/reference/changelog/v4.2.md)[​](#v420 "Direct link to v420") New features and configuration options for node operators. **Key changes:** * Blob retrieval improvements with unified retry loop **Migration difficulty**: Low [View full changelog →](/operate/testnet/operators/reference/changelog/v4.2.md) *** ### [v4.x (Upgrade from Ignition)](/operate/testnet/operators/reference/changelog/v4.md)[​](#v4x-upgrade-from-ignition "Direct link to v4x-upgrade-from-ignition") Major upgrade from Ignition (v2.x) to Alpha (v4.x) with significant architectural changes. **Key changes:** * Checkpoint-based block architecture (multiple L2 blocks per slot) * Blob-only data publication (EIP-4844), calldata fallback removed * Double signing slashing infrastructure * HA signing with PostgreSQL for redundant sequencer nodes * Admin API key authentication * Sequencer environment variable renames * Withdrawal delay increase (7 to 30 days) **Migration difficulty**: High [View full changelog →](/operate/testnet/operators/reference/changelog/v4.md) *** ### [v2.0.2 (from v1.2.1)](/operate/testnet/operators/reference/changelog/v2.0.2.md)[​](#v202-from-v121 "Direct link to v202-from-v121") Major release with significant configuration simplification, keystore integration, and feature updates. **Key changes:** * Simplified L1 contract address configuration (registry-only) * Integrated keystore system for key management * Removed component-specific settings in favor of global configuration * Enhanced P2P transaction collection capabilities * New invalidation controls for sequencers **Migration difficulty**: Moderate to High [View full changelog →](/operate/testnet/operators/reference/changelog/v2.0.2.md) *** ## Migration guides[​](#migration-guides "Direct link to Migration guides") When upgrading between versions: 1. Review the version-specific changelog for breaking changes 2. Follow the migration checklist for your node type 3. Test in a non-production environment first 4. Check the troubleshooting section for common upgrade issues 5. Join [Aztec Discord](https://discord.gg/aztec) for upgrade support ## Related resources[​](#related-resources "Direct link to Related resources") * [CLI Reference](/operate/testnet/operators/reference/cli-reference.md) - Current command-line options * [Node API Reference](/operate/testnet/operators/reference/node_api_reference.md) - API documentation * [Ethereum RPC Reference](/operate/testnet/operators/reference/ethereum_rpc_reference.md) - L1 RPC usage --- # v2.0.2 (from v1.2.1) ## Overview[​](#overview "Direct link to Overview") Version 2.0.2 introduces significant configuration simplification, an integrated keystore system, and enhanced P2P capabilities. This release includes breaking changes that require migration from v1.2.1. **Migration difficulty**: Moderate to High ## Breaking changes[​](#breaking-changes "Direct link to Breaking changes") ### L1 contract addresses[​](#l1-contract-addresses "Direct link to L1 contract addresses") **v1.2.1:** ``` --rollup-address ($ROLLUP_CONTRACT_ADDRESS) --inbox-address ($INBOX_CONTRACT_ADDRESS) --outbox-address ($OUTBOX_CONTRACT_ADDRESS) --fee-juice-address ($FEE_JUICE_CONTRACT_ADDRESS) --staking-asset-address ($STAKING_ASSET_CONTRACT_ADDRESS) --fee-juice-portal-address ($FEE_JUICE_PORTAL_CONTRACT_ADDRESS) --registry-address ($REGISTRY_CONTRACT_ADDRESS) ``` **v2.0.2:** ``` --registry-address ($REGISTRY_CONTRACT_ADDRESS) --rollup-version ($ROLLUP_VERSION) # Default: canonical ``` **Migration**: Only registry address is required. All other contract addresses are derived automatically. ### Keystore integration[​](#keystore-integration "Direct link to Keystore integration") **v1.2.1:** ``` --sequencer.publisherPrivateKey ($SEQ_PUBLISHER_PRIVATE_KEY) --proverNode.publisherPrivateKey ($PROVER_PUBLISHER_PRIVATE_KEY) ``` **v2.0.2:** ``` --proverNode.keyStoreDirectory ($KEY_STORE_DIRECTORY) # Multiple publishers supported --sequencer.publisherPrivateKeys ($SEQ_PUBLISHER_PRIVATE_KEYS) --sequencer.publisherAddresses ($SEQ_PUBLISHER_ADDRESSES) --proverNode.publisherPrivateKeys ($PROVER_PUBLISHER_PRIVATE_KEYS) --proverNode.publisherAddresses ($PROVER_PUBLISHER_ADDRESSES) ``` **Migration**: Create keystore directory, change singular to plural. Use `*_ADDRESSES` for remote signers. See [Advanced Keystore Guide](/operate/testnet/operators/keystore.md). ### Validator configuration[​](#validator-configuration "Direct link to Validator configuration") **v1.2.1:** ``` --sequencer.validatorPrivateKeys ($VALIDATOR_PRIVATE_KEYS) ``` **v2.0.2:** ``` --sequencer.validatorPrivateKeys ($VALIDATOR_PRIVATE_KEYS) --sequencer.validatorAddresses ($VALIDATOR_ADDRESSES) # For remote signers --sequencer.disabledValidators # Temporarily disable ``` ### Sync mode relocated[​](#sync-mode-relocated "Direct link to Sync mode relocated") **v1.2.1:** Component-specific ``` --node.syncMode ($SYNC_MODE) --node.snapshotsUrl ($SYNC_SNAPSHOTS_URL) --proverNode.syncMode ($SYNC_MODE) --proverNode.snapshotsUrl ($SYNC_SNAPSHOTS_URL) ``` **v2.0.2:** Global setting ``` --sync-mode ($SYNC_MODE) # Options: full, snapshot, force-snapshot --snapshots-url ($SYNC_SNAPSHOTS_URL) ``` ### World state separation[​](#world-state-separation "Direct link to World state separation") **v1.2.1:** Prover-node-specific ``` --proverNode.worldStateBlockCheckIntervalMS ($WS_BLOCK_CHECK_INTERVAL_MS) --proverNode.worldStateProvenBlocksOnly ($WS_PROVEN_BLOCKS_ONLY) --proverNode.worldStateBlockRequestBatchSize ($WS_BLOCK_REQUEST_BATCH_SIZE) --proverNode.worldStateDbMapSizeKb ($WS_DB_MAP_SIZE_KB) --proverNode.archiveTreeMapSizeKb ($ARCHIVE_TREE_MAP_SIZE_KB) --proverNode.nullifierTreeMapSizeKb ($NULLIFIER_TREE_MAP_SIZE_KB) --proverNode.noteHashTreeMapSizeKb ($NOTE_HASH_TREE_MAP_SIZE_KB) --proverNode.messageTreeMapSizeKb ($MESSAGE_TREE_MAP_SIZE_KB) --proverNode.publicDataTreeMapSizeKb ($PUBLIC_DATA_TREE_MAP_SIZE_KB) --proverNode.worldStateDataDirectory ($WS_DATA_DIRECTORY) --proverNode.worldStateBlockHistory ($WS_NUM_HISTORIC_BLOCKS) ``` **v2.0.2:** Global settings only ``` --world-state-data-directory ($WS_DATA_DIRECTORY) --world-state-db-map-size-kb ($WS_DB_MAP_SIZE_KB) --world-state-block-history ($WS_NUM_HISTORIC_BLOCKS) ``` **Migration**: Move to global WORLD STATE section. Tree-specific map sizes and other world state settings removed. ## Removed features[​](#removed-features "Direct link to Removed features") ### Faucet service[​](#faucet-service "Direct link to Faucet service") ``` # All removed in v2.0.2 --faucet --faucet.apiServer --faucet.apiServerPort ($FAUCET_API_SERVER_PORT) --faucet.viemPollingIntervalMS ($L1_READER_VIEM_POLLING_INTERVAL_MS) --faucet.l1Mnemonic ($MNEMONIC) --faucet.mnemonicAddressIndex ($FAUCET_MNEMONIC_ADDRESS_INDEX) --faucet.interval ($FAUCET_INTERVAL_MS) --faucet.ethAmount ($FAUCET_ETH_AMOUNT) --faucet.l1Assets ($FAUCET_L1_ASSETS) ``` ### L1 transaction monitoring[​](#l1-transaction-monitoring "Direct link to L1 transaction monitoring") All removed from archiver and sequencer: ``` --archiver.gasLimitBufferPercentage ($L1_GAS_LIMIT_BUFFER_PERCENTAGE) --archiver.maxGwei ($L1_GAS_PRICE_MAX) --archiver.maxBlobGwei ($L1_BLOB_FEE_PER_GAS_MAX) --archiver.priorityFeeBumpPercentage ($L1_PRIORITY_FEE_BUMP_PERCENTAGE) --archiver.priorityFeeRetryBumpPercentage ($L1_PRIORITY_FEE_RETRY_BUMP_PERCENTAGE) --archiver.fixedPriorityFeePerGas ($L1_FIXED_PRIORITY_FEE_PER_GAS) --archiver.maxAttempts ($L1_TX_MONITOR_MAX_ATTEMPTS) --archiver.checkIntervalMs ($L1_TX_MONITOR_CHECK_INTERVAL_MS) --archiver.stallTimeMs ($L1_TX_MONITOR_STALL_TIME_MS) --archiver.txTimeoutMs ($L1_TX_MONITOR_TX_TIMEOUT_MS) --archiver.txPropagationMaxQueryAttempts ($L1_TX_PROPAGATION_MAX_QUERY_ATTEMPTS) --archiver.cancelTxOnTimeout ($L1_TX_MONITOR_CANCEL_TX_ON_TIMEOUT) # Same settings removed from --sequencer.* ``` **Migration**: L1 transaction management now uses optimized internal defaults. ### Rollup constants from archiver[​](#rollup-constants-from-archiver "Direct link to Rollup constants from archiver") All rollup constants now derived from L1 contracts: ``` # All removed in v2.0.2 --archiver.ethereumSlotDuration ($ETHEREUM_SLOT_DURATION) --archiver.aztecSlotDuration ($AZTEC_SLOT_DURATION) --archiver.aztecEpochDuration ($AZTEC_EPOCH_DURATION) --archiver.aztecTargetCommitteeSize ($AZTEC_TARGET_COMMITTEE_SIZE) --archiver.aztecProofSubmissionEpochs ($AZTEC_PROOF_SUBMISSION_EPOCHS) --archiver.depositAmount ($AZTEC_DEPOSIT_AMOUNT) --archiver.minimumStake ($AZTEC_MINIMUM_STAKE) --archiver.slashingQuorum ($AZTEC_SLASHING_QUORUM) --archiver.slashingRoundSize ($AZTEC_SLASHING_ROUND_SIZE) --archiver.governanceProposerQuorum ($AZTEC_GOVERNANCE_PROPOSER_QUORUM) --archiver.governanceProposerRoundSize ($AZTEC_GOVERNANCE_PROPOSER_ROUND_SIZE) --archiver.manaTarget ($AZTEC_MANA_TARGET) --archiver.provingCostPerMana ($AZTEC_PROVING_COST_PER_MANA) --archiver.exitDelaySeconds ($AZTEC_EXIT_DELAY_SECONDS) # Same settings removed from --sequencer.* ``` ### Node deployment options[​](#node-deployment-options "Direct link to Node deployment options") ``` # All removed in v2.0.2 (moved to sandbox only) --node.deployAztecContracts ($DEPLOY_AZTEC_CONTRACTS) --node.deployAztecContractsSalt ($DEPLOY_AZTEC_CONTRACTS_SALT) --node.assumeProvenThroughBlockNumber ($ASSUME_PROVEN_THROUGH_BLOCK_NUMBER) --node.publisherPrivateKey ($L1_PRIVATE_KEY) ``` **Migration**: Contract deployment now sandbox-only via `--sandbox.deployAztecContractsSalt`. For production, deploy contracts separately. ### Other removed settings[​](#other-removed-settings "Direct link to Other removed settings") ``` # Prover coordination --proverNode.proverCoordinationNodeUrls ($PROVER_COORDINATION_NODE_URLS) # Custom forwarder --sequencer.customForwarderContractAddress ($CUSTOM_FORWARDER_CONTRACT_ADDRESS) --proverNode.customForwarderContractAddress ($CUSTOM_FORWARDER_CONTRACT_ADDRESS) # Component-specific settings now global --archiver.viemPollingIntervalMS ($ARCHIVER_VIEM_POLLING_INTERVAL_MS) --sequencer.viemPollingIntervalMS ($L1_READER_VIEM_POLLING_INTERVAL_MS) --blobSink.viemPollingIntervalMS ($L1_READER_VIEM_POLLING_INTERVAL_MS) --proverBroker.viemPollingIntervalMS ($L1_READER_VIEM_POLLING_INTERVAL_MS) --archiver.rollupVersion ($ROLLUP_VERSION) --sequencer.rollupVersion ($ROLLUP_VERSION) --blobSink.rollupVersion ($ROLLUP_VERSION) --proverBroker.rollupVersion ($ROLLUP_VERSION) --pxe.rollupVersion ($ROLLUP_VERSION) --archiver.dataStoreMapSizeKB ($DATA_STORE_MAP_SIZE_KB) --pxe.dataStoreMapSizeKB ($DATA_STORE_MAP_SIZE_KB) --blobSink.dataStoreMapSizeKB ($DATA_STORE_MAP_SIZE_KB) --proverBroker.dataStoreMapSizeKB ($DATA_STORE_MAP_SIZE_KB) --p2pBootstrap.dataStoreMapSizeKB ($DATA_STORE_MAP_SIZE_KB) # Aztec node specific --node.worldStateBlockCheckIntervalMS ($WS_BLOCK_CHECK_INTERVAL_MS) --node.archiverUrl ($ARCHIVER_URL) ``` ## New features[​](#new-features "Direct link to New features") ### P2P transaction collection[​](#p2p-transaction-collection "Direct link to P2P transaction collection") ``` --p2p.txCollectionNodeRpcUrls ($TX_COLLECTION_NODE_RPC_URLS) --p2p.txCollectionFastNodeIntervalMs ($TX_COLLECTION_FAST_NODE_INTERVAL_MS) --p2p.txCollectionFastMaxParallelRequestsPerNode ($TX_COLLECTION_FAST_MAX_PARALLEL_REQUESTS_PER_NODE) --p2p.txCollectionNodeRpcMaxBatchSize ($TX_COLLECTION_NODE_RPC_MAX_BATCH_SIZE) --p2p.txCollectionFastNodesTimeoutBeforeReqRespMs ($TX_COLLECTION_FAST_NODES_TIMEOUT_BEFORE_REQ_RESP_MS) --p2p.txCollectionSlowNodesIntervalMs ($TX_COLLECTION_SLOW_NODES_INTERVAL_MS) --p2p.txCollectionSlowReqRespIntervalMs ($TX_COLLECTION_SLOW_REQ_RESP_INTERVAL_MS) --p2p.txCollectionSlowReqRespTimeoutMs ($TX_COLLECTION_SLOW_REQ_RESP_TIMEOUT_MS) --p2p.txCollectionReconcileIntervalMs ($TX_COLLECTION_RECONCILE_INTERVAL_MS) --p2p.txCollectionDisableSlowDuringFastRequests ($TX_COLLECTION_DISABLE_SLOW_DURING_FAST_REQUESTS) ``` ### P2P security and testing[​](#p2p-security-and-testing "Direct link to P2P security and testing") ``` # Discovery and security --p2p.p2pDiscoveryDisabled ($P2P_DISCOVERY_DISABLED) --p2p.p2pAllowOnlyValidators ($P2P_ALLOW_ONLY_VALIDATORS) --p2p.p2pMaxFailedAuthAttemptsAllowed ($P2P_MAX_AUTH_FAILED_ATTEMPTS_ALLOWED) # Testing features --p2p.dropTransactions ($P2P_DROP_TX) --p2p.dropTransactionsProbability ($P2P_DROP_TX_CHANCE) # Transaction handling --p2p.disableTransactions ($TRANSACTIONS_DISABLED) --p2p.txPoolDeleteTxsAfterReorg ($P2P_TX_POOL_DELETE_TXS_AFTER_REORG) # Preferred peers --p2p.preferredPeers ($P2P_PREFERRED_PEERS) ``` ### Sequencer invalidation controls[​](#sequencer-invalidation-controls "Direct link to Sequencer invalidation controls") ``` --sequencer.attestationPropagationTime ($SEQ_ATTESTATION_PROPAGATION_TIME) --sequencer.secondsBeforeInvalidatingBlockAsCommitteeMember ($SEQ_SECONDS_BEFORE_INVALIDATING_BLOCK_AS_COMMITTEE_MEMBER) --sequencer.secondsBeforeInvalidatingBlockAsNonCommitteeMember ($SEQ_SECONDS_BEFORE_INVALIDATING_BLOCK_AS_NON_COMMITTEE_MEMBER) ``` ### Other new features[​](#other-new-features "Direct link to Other new features") ``` # Archiver - skip validation (testing only) --archiver.skipValidateBlockAttestations # Prover - transaction gathering timeout --proverNode.txGatheringTimeoutMs ($PROVER_NODE_TX_GATHERING_TIMEOUT_MS) ``` ## Changed defaults[​](#changed-defaults "Direct link to Changed defaults") | Flag | Environment Variable | v1.2.1 | v2.0.2 | | ----------------------------------------- | -------------------------------------------- | ----------- | ----------------- | | `--p2p.overallRequestTimeoutMs` | `$P2P_REQRESP_OVERALL_REQUEST_TIMEOUT_MS` | 4000 | **10000** | | `--p2p.individualRequestTimeoutMs` | `$P2P_REQRESP_INDIVIDUAL_REQUEST_TIMEOUT_MS` | 2000 | **10000** | | `--p2p.dialTimeoutMs` | `$P2P_REQRESP_DIAL_TIMEOUT_MS` | 1000 | **5000** | | `--proverAgent.proverAgentPollIntervalMs` | `$PROVER_AGENT_POLL_INTERVAL_MS` | 100 | **1000** | | `--bot.l1ToL2MessageTimeoutSeconds` | `$BOT_L1_TO_L2_TIMEOUT_SECONDS` | 60 | **3600** | | `--bot.recipientEncryptionSecret` | `$BOT_RECIPIENT_ENCRYPTION_SECRET` | \[Redacted] | **0x...cafecafe** | ## Migration checklist[​](#migration-checklist "Direct link to Migration checklist") ### All nodes[​](#all-nodes "Direct link to All nodes") * Update to `--registry-address` only (remove all other contract addresses) * Add `--rollup-version canonical` if needed * Move `--sync-mode` and `--snapshots-url` to global config * Remove component-specific `--*.rollupVersion`, `--*.dataStoreMapSizeKB` * Set global `--data-store-map-size-kb` if needed (default: 134217728 KB) ### Sequencer nodes[​](#sequencer-nodes "Direct link to Sequencer nodes") * Create and configure `--sequencer.keyStoreDirectory` (actually `--proverNode.keyStoreDirectory`) * Change `--sequencer.publisherPrivateKey` → `--sequencer.publisherPrivateKeys` * Update `--sequencer.validatorPrivateKeys` or add `--sequencer.validatorAddresses` * Remove all `--sequencer.gasLimitBufferPercentage` and related L1 settings * Remove `--sequencer.customForwarderContractAddress`, `--sequencer.viemPollingIntervalMS` * Consider using `--sequencer.disabledValidators` for temporary disabling ### Prover nodes[​](#prover-nodes "Direct link to Prover nodes") * Create and configure `--proverNode.keyStoreDirectory` * Change `--proverNode.publisherPrivateKey` → `--proverNode.publisherPrivateKeys` * Move world state settings to global WORLD STATE section * Remove `--proverNode.archiveTreeMapSizeKb` and other tree-specific sizes * Remove `--proverNode.proverCoordinationNodeUrls`, `--proverNode.customForwarderContractAddress` * Set `--proverNode.txGatheringTimeoutMs` if needed ### Archiver nodes[​](#archiver-nodes "Direct link to Archiver nodes") * Remove all `--archiver.gasLimitBufferPercentage` and related L1 settings * Remove `--archiver.ethereumSlotDuration`, `--archiver.aztecSlotDuration`, etc. * Remove `--archiver.viemPollingIntervalMS` ### P2P configuration[​](#p2p-configuration "Direct link to P2P configuration") * Configure `--p2p.txCollectionNodeRpcUrls` if using external nodes * Review `--p2p.p2pAllowOnlyValidators` security settings * Consider using `--p2p.preferredPeers` ### Sandbox/development[​](#sandboxdevelopment "Direct link to Sandbox/development") * Move deployment to `--sandbox.deployAztecContractsSalt` * Configure `--sandbox.l1Mnemonic` if needed * Remove all faucet flags ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Node fails with contract address errors[​](#node-fails-with-contract-address-errors "Direct link to Node fails with contract address errors") **Solution**: Remove all individual contract addresses, keep only `--registry-address`, add `--rollup-version canonical` ### Publisher key not found[​](#publisher-key-not-found "Direct link to Publisher key not found") **Solution**: * Check `--proverNode.keyStoreDirectory` ($KEY\_STORE\_DIRECTORY) is set * Change `--*.publisherPrivateKey` to `--*.publisherPrivateKeys` (plural) * See [Advanced Keystore Guide](/operate/testnet/operators/keystore.md) ### World state sync failures (prover)[​](#world-state-sync-failures-prover "Direct link to World state sync failures (prover)") **Solution**: * Move `--proverNode.worldStateDataDirectory` to `--world-state-data-directory` * Remove prover-specific world state settings * Use global `--world-state-db-map-size-kb` ### Slow P2P after upgrade[​](#slow-p2p-after-upgrade "Direct link to Slow P2P after upgrade") **Solution**: * Configure `--p2p.txCollectionNodeRpcUrls` ($TX\_COLLECTION\_NODE\_RPC\_URLS) * Adjust `--p2p.txCollectionFastNodeIntervalMs` ### Validator not attesting[​](#validator-not-attesting "Direct link to Validator not attesting") **Solution**: * Check not in `--sequencer.disabledValidators` list * Verify `--sequencer.validatorPrivateKeys` or `--sequencer.validatorAddresses` * Check keystore permissions ### Missing sync snapshots[​](#missing-sync-snapshots "Direct link to Missing sync snapshots") **Solution**: * Move `--node.syncMode` to `--sync-mode` (global) * Set `--snapshots-url` at global level ## Next steps[​](#next-steps "Direct link to Next steps") * [How to Run a Sequencer Node](/operate/testnet/operators/setup/sequencer_management.md) - Updated setup instructions * [Advanced Keystore Usage](/operate/testnet/operators/keystore.md) - Keystore configuration * [Ethereum RPC Calls Reference](/operate/testnet/operators/reference/ethereum_rpc_reference.md) - Infrastructure requirements * [Aztec Discord](https://discord.gg/aztec) - Upgrade support --- # v4.x (Upgrade from Ignition) ## Overview[​](#overview "Direct link to Overview") **Migration difficulty**: High ## Breaking changes[​](#breaking-changes "Direct link to Breaking changes") ### Node.js upgraded to v24[​](#nodejs-upgraded-to-v24 "Direct link to Node.js upgraded to v24") Node.js minimum version changed from v22 to v24.12.0. ### Bot fee padding configuration renamed[​](#bot-fee-padding-configuration-renamed "Direct link to Bot fee padding configuration renamed") The bot configuration for fee padding has been renamed from "base fee" to "min fee". **v3.x:** ``` --bot.baseFeePadding ($BOT_BASE_FEE_PADDING) ``` **v4.0.0:** ``` --bot.minFeePadding ($BOT_MIN_FEE_PADDING) ``` **Migration**: Update your configuration to use the new flag name and environment variable. ### L2Tips API restructured with checkpoint information[​](#l2tips-api-restructured-with-checkpoint-information "Direct link to L2Tips API restructured with checkpoint information") The `getL2Tips()` RPC endpoint now returns a restructured response with additional checkpoint tracking. **v3.x response:** ``` { "latest": { "number": 100, "hash": "0x..." }, "proven": { "number": 98, "hash": "0x..." }, "finalized": { "number": 95, "hash": "0x..." } } ``` **v4.0.0 response:** ``` { "proposed": { "number": 100, "hash": "0x..." }, "checkpointed": { "block": { "number": 99, "hash": "0x..." }, "checkpoint": { "number": 10, "hash": "0x..." } }, "proven": { "block": { "number": 98, "hash": "0x..." }, "checkpoint": { "number": 9, "hash": "0x..." } }, "finalized": { "block": { "number": 95, "hash": "0x..." }, "checkpoint": { "number": 8, "hash": "0x..." } } } ``` **Migration**: * Replace `tips.latest` with `tips.proposed` * For `checkpointed`, `proven`, and `finalized` tips, access block info via `.block` (e.g., `tips.proven.block.number`) ### Block gas limits reworked[​](#block-gas-limits-reworked "Direct link to Block gas limits reworked") The byte-based block size limit has been removed and replaced with field-based blob limits and automatic gas budget computation from L1 rollup limits. **Removed:** ``` --maxBlockSizeInBytes ($SEQ_MAX_BLOCK_SIZE_IN_BYTES) ``` **Changed to optional (now auto-computed from L1 if not set):** ``` --maxL2BlockGas ($SEQ_MAX_L2_BLOCK_GAS) --maxDABlockGas ($SEQ_MAX_DA_BLOCK_GAS) ``` **New (proposer):** ``` --perBlockAllocationMultiplier ($SEQ_PER_BLOCK_ALLOCATION_MULTIPLIER) --maxTxsPerCheckpoint ($SEQ_MAX_TX_PER_CHECKPOINT) ``` **New (validator):** ``` --validateMaxL2BlockGas ($VALIDATOR_MAX_L2_BLOCK_GAS) --validateMaxDABlockGas ($VALIDATOR_MAX_DA_BLOCK_GAS) --validateMaxTxsPerBlock ($VALIDATOR_MAX_TX_PER_BLOCK) --validateMaxTxsPerCheckpoint ($VALIDATOR_MAX_TX_PER_CHECKPOINT) ``` **Migration**: Remove `SEQ_MAX_BLOCK_SIZE_IN_BYTES` from your configuration. Per-block L2 and DA gas budgets are now derived automatically as `(checkpointLimit / maxBlocks) * multiplier`, where the multiplier defaults to 2. You can still override `SEQ_MAX_L2_BLOCK_GAS` and `SEQ_MAX_DA_BLOCK_GAS` explicitly, but they will be capped at the checkpoint-level limits. Validators can now set independent per-block and per-checkpoint limits via the `VALIDATOR_` env vars; when not set, only checkpoint-level protocol limits are enforced. ### Setup phase allow list requires function selectors[​](#setup-phase-allow-list-requires-function-selectors "Direct link to Setup phase allow list requires function selectors") The transaction setup phase allow list now enforces function selectors, restricting which specific functions can run during setup on whitelisted contracts. Previously, any public function on a whitelisted contract or class was permitted. The semantics of the environment variable `TX_PUBLIC_SETUP_ALLOWLIST` have changed: **v3.x:** ``` --txPublicSetupAllowList ($TX_PUBLIC_SETUP_ALLOWLIST) ``` The variable fully **replaced** the hardcoded defaults. Format allowed entries without selectors: `I:address`, `C:classId`. **v4.0.0:** ``` --txPublicSetupAllowListExtend ($TX_PUBLIC_SETUP_ALLOWLIST) ``` The variable now **extends** the hardcoded defaults (which are always present). Selectors are now mandatory. An optional flags segment can be appended for additional validation: ``` I:address:selector[:flags] C:classId:selector[:flags] ``` Where `flags` is a `+`-separated list of: * `os` — `onlySelf`: only allow calls where msg\_sender == contract address * `rn` — `rejectNullMsgSender`: reject calls with a null msg\_sender * `cl=N` — `calldataLength`: enforce exact calldata length of N fields Example: `C:0xabc:0x1234:os+cl=4` **Migration**: If you were using `TX_PUBLIC_SETUP_ALLOWLIST`, ensure all entries include function selectors. Note the variable now adds to defaults rather than replacing them. If you were not setting this variable, no action is needed — the hardcoded defaults now include the correct selectors automatically. ### Token removed from default setup allowlist[​](#token-removed-from-default-setup-allowlist "Direct link to Token removed from default setup allowlist") Token class-based entries (`_increase_public_balance` and `transfer_in_public`) have been removed from the default public setup allowlist. FPC-based fee payments using custom tokens no longer work out of the box. This change was made because Token class IDs change with aztec-nr releases, making the allowlist impossible to keep up to date with new library releases. In addition, `transfer_in_public` requires complex additional logic to be built into the node to prevent mass transaction invalidation attacks. **FPC-based fee payment with custom tokens won't work on mainnet alpha**. **Migration**: Node operators who need FPC support must manually add Token entries via `TX_PUBLIC_SETUP_ALLOWLIST`. Example: ``` TX_PUBLIC_SETUP_ALLOWLIST="C:::os+cl=3,C:::cl=5" ``` Replace `` with the deployed Token contract class ID and ``/`` with the respective function selectors. Keep in mind that this will only work on local network setups, since even if you as an operator add these entries, other nodes will not have them and will not pick up these transactions. ### Sequencer environment variable renames[​](#sequencer-environment-variable-renames "Direct link to Sequencer environment variable renames") Several sequencer environment variables have been renamed: | Old variable | New variable | | ---------------------------------------- | --------------------------------------------------------------------- | | `SEQ_TX_POLLING_INTERVAL_MS` | `SEQ_POLLING_INTERVAL_MS` | | `SEQ_MAX_L1_TX_INCLUSION_TIME_INTO_SLOT` | `SEQ_L1_PUBLISHING_TIME_ALLOWANCE_IN_SLOT` | | `SEQ_MAX_TX_PER_BLOCK` | `SEQ_MAX_TX_PER_CHECKPOINT` | | `SEQ_MAX_BLOCK_SIZE_IN_BYTES` | Removed (see [Block gas limits reworked](#block-gas-limits-reworked)) | **Migration**: Search your configuration for the old variable names and replace them. The node will not recognize the old names. ### Double signing slashing[​](#double-signing-slashing "Direct link to Double signing slashing") New slashable offenses have been introduced for duplicate proposals and duplicate attestations. Penalty amounts are currently set to 0, but the detection infrastructure is active. If you run redundant sequencer nodes, you **must** enable high-availability signing with PostgreSQL to prevent accidental double signing: ``` VALIDATOR_HA_SIGNING_ENABLED=true VALIDATOR_HA_DATABASE_URL=postgresql://:@:/ VALIDATOR_HA_NODE_ID= ``` Run the database migration before starting your nodes: ``` aztec migrate-ha-db up --database-url ``` **Migration**: If you run a single node, no action is required. If you run redundant nodes for high availability, configure HA signing immediately. See the [High Availability Sequencers](/operate/testnet/operators/setup/high_availability_sequencers.md) guide for details. ### Blob-only data publication[​](#blob-only-data-publication "Direct link to Blob-only data publication") Transaction data is now published entirely via EIP-4844 blobs. The calldata fallback has been removed. Your consensus client (e.g., Lighthouse, Prysm) must run as a **supernode** or **semi-supernode** to make blobs available for retrieval. Standard pruning configurations will not retain blobs long enough. You should also configure blob file stores for redundancy: ``` BLOB_FILE_STORE_URLS= BLOB_FILE_STORE_UPLOAD_URL= BLOB_ARCHIVE_API_URL= ``` **Migration**: Ensure your consensus client is configured as a supernode. If you previously relied on calldata for data availability, switch to blob-based retrieval. See the [Blob Storage](/operate/testnet/operators/setup/blob_storage.md) guide for configuration details. ### Withdrawal delay increase[​](#withdrawal-delay-increase "Direct link to Withdrawal delay increase") The governance execution delay has increased from 7 days to 30 days. This extends the time required for staker withdrawals from approximately 15 days to approximately 38 days. **Migration**: No configuration changes needed. Be aware that withdrawal processing will take longer after the upgrade. ### Prover architecture change[​](#prover-architecture-change "Direct link to Prover architecture change") The prover now runs as a node subsystem rather than a separate standalone process. Start it alongside your node using the `--prover-node` flag: ``` aztec start --node --prover-node ``` **Migration**: If you were running the prover as a separate process, update your deployment to run it as part of the node with `--prover-node`. ## Removed features[​](#removed-features "Direct link to Removed features") ## New features[​](#new-features "Direct link to New features") ### Initial ETH per fee asset configuration[​](#initial-eth-per-fee-asset-configuration "Direct link to Initial ETH per fee asset configuration") A new environment variable `AZTEC_INITIAL_ETH_PER_FEE_ASSET` has been added to configure the initial exchange rate between ETH and the fee asset (AZTEC) at contract deployment. This value uses 1e12 precision. **Default**: `10000000` (0.00001 ETH per AZTEC) **Configuration:** ``` --initialEthPerFeeAsset ($AZTEC_INITIAL_ETH_PER_FEE_ASSET) ``` This replaces the previous hardcoded default and allows network operators to set the starting price point for the fee asset. ### `reloadKeystore` admin RPC endpoint[​](#reloadkeystore-admin-rpc-endpoint "Direct link to reloadkeystore-admin-rpc-endpoint") Node operators can now update validator attester keys, coinbase, and fee recipient without restarting the node by calling the new `reloadKeystore` admin RPC endpoint. What is updated on reload: * Validator attester keys (add, remove, or replace) * Coinbase and fee recipient per validator * Publisher-to-validator mapping What is NOT updated (requires restart): * L1 publisher signers * Prover keys * HA signer connections New validators must use a publisher key already initialized at startup. Reload is rejected with a clear error if validation fails. ### Admin API key authentication[​](#admin-api-key-authentication "Direct link to Admin API key authentication") The admin JSON-RPC endpoint now supports auto-generated API key authentication. **Behavior:** * A cryptographically secure API key is auto-generated at first startup and displayed once via stdout * Only the SHA-256 hash is persisted to `/admin/api_key_hash` * The key is reused across restarts when `--data-directory` is set * Supports both `x-api-key` and `Authorization: Bearer ` headers * Health check endpoint (`GET /status`) is excluded from auth (for k8s probes) **Configuration:** ``` --admin-api-key-hash ($AZTEC_ADMIN_API_KEY_HASH) # Use a pre-generated SHA-256 key hash --disable-admin-api-key ($AZTEC_DISABLE_ADMIN_API_KEY) # Disable auth entirely --reset-admin-api-key ($AZTEC_RESET_ADMIN_API_KEY) # Force key regeneration ``` **Helm charts**: Admin API key auth is disabled by default (`disableAdminApiKey: true`). Set to `false` in production values to enable. **Migration**: No action required — auth is opt-out. To enable, ensure `--disable-admin-api-key` is not set and note the key printed at startup. ### Transaction pool error codes for RPC callers[​](#transaction-pool-error-codes-for-rpc-callers "Direct link to Transaction pool error codes for RPC callers") Transaction submission via RPC now returns structured rejection codes when a transaction is rejected by the mempool: * `LOW_PRIORITY_FEE` — tx priority fee is too low * `INSUFFICIENT_FEE_PAYER_BALANCE` — fee payer doesn't have enough balance * `NULLIFIER_CONFLICT` — conflicting nullifier already in pool **Impact**: Improved developer experience — callers can now programmatically handle specific rejection reasons. ### RPC transaction replacement price bump[​](#rpc-transaction-replacement-price-bump "Direct link to RPC transaction replacement price bump") Transactions submitted via RPC that clash on nullifiers with existing pool transactions must now pay at least X% more in priority fee to replace them. The same bump applies when the pool is full and the incoming tx needs to evict the lowest-priority tx. P2P gossip behavior is unchanged. **Configuration:** ``` P2P_RPC_PRICE_BUMP_PERCENTAGE=10 # default: 10 (percent) ``` Set to `0` to disable the percentage-based bump (still requires strictly higher fee). ### Validator-specific block limits[​](#validator-specific-block-limits "Direct link to Validator-specific block limits") Validators can now enforce per-block and per-checkpoint limits independently from the sequencer (proposer) limits. This allows operators to accept proposals that exceed their own proposer settings, or to reject proposals that are too large even if the proposer's limits allow them. **Configuration:** ``` VALIDATOR_MAX_L2_BLOCK_GAS= # Max L2 gas per block for validation VALIDATOR_MAX_DA_BLOCK_GAS= # Max DA gas per block for validation VALIDATOR_MAX_TX_PER_BLOCK= # Max txs per block for validation VALIDATOR_MAX_TX_PER_CHECKPOINT= # Max txs per checkpoint for validation ``` When not set, no per-block limit is enforced for that dimension — only checkpoint-level protocol limits apply. These do not fall back to the `SEQ_` values. ### Setup allow list extendable via network config[​](#setup-allow-list-extendable-via-network-config "Direct link to Setup allow list extendable via network config") The setup phase allow list can now be extended via the network configuration JSON (`txPublicSetupAllowListExtend` field). This allows network operators to distribute additional allowed setup functions to all nodes without requiring code changes. The local environment variable takes precedence over the network-json value. ## Changed defaults[​](#changed-defaults "Direct link to Changed defaults") ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ## Next steps[​](#next-steps "Direct link to Next steps") * [How to Run a Sequencer Node](/operate/testnet/operators/setup/sequencer_management.md) - Updated setup instructions * [Advanced Keystore Usage](/operate/testnet/operators/keystore.md) - Keystore configuration * [Ethereum RPC Calls Reference](/operate/testnet/operators/reference/ethereum_rpc_reference.md) - Infrastructure requirements * [Aztec Discord](https://discord.gg/aztec) - Upgrade support --- # v4.2.0 ## Overview[​](#overview "Direct link to Overview") **Migration difficulty**: TODO ## Breaking changes[​](#breaking-changes "Direct link to Breaking changes") ## New features[​](#new-features "Direct link to New features") ### Blob retrieval improvements[​](#blob-retrieval-improvements "Direct link to Blob retrieval improvements") Blob retrieval now uses a unified retry loop that alternates between consensus clients and file stores, replacing the previous multi-phase approach. This reduces retrieval latency from \~12s to \~1.5-3s when blobs aren't immediately available in file stores. Non-supernode consensus hosts are automatically detected at startup and skipped during blob fetching, avoiding wasted requests. **Configuration:** ``` BLOB_PREFER_FILESTORES=false # Try file stores before consensus (default: false) BLOB_FILE_STORE_TIMEOUT_MS=10000 # HTTP timeout for blob file store requests in ms (default: 10000) ``` Set `BLOB_PREFER_FILESTORES=true` if your file stores are faster or more reliable than your consensus clients. ## Changed defaults[​](#changed-defaults "Direct link to Changed defaults") ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") --- # v4.3.0 ## Overview[​](#overview "Direct link to Overview") **Migration difficulty:** low. The only change likely to require operator action is the renaming of bundled binaries under an `aztec-` prefix. ## Breaking changes[​](#breaking-changes "Direct link to Breaking changes") ### Bundled binaries renamed under `aztec-` prefix on `PATH`[​](#bundled-binaries-renamed-under-aztec--prefix-on-path "Direct link to bundled-binaries-renamed-under-aztec--prefix-on-path") `aztec-up` previously placed bundled tooling directly into `$HOME/.aztec/current/bin` under bare names (`forge`, `cast`, `nargo`, `bb`, `pxe`, `txe`, `validator-client`, `blob-client`, ...). Operators with their own `forge` or `nargo` install elsewhere on `PATH` could end up silently using the wrong binary depending on resolution order, and the bundle could shadow unrelated projects. In v4.3.0, every bundled binary is exposed **only** under its `aztec-`-prefixed name. Bare names are no longer placed on `PATH` by `aztec-up`. | Was on `PATH` | Now | | ------------------ | ------------------------ | | `forge` | `aztec-forge` | | `cast` | `aztec-cast` | | `anvil` | `aztec-anvil` | | `chisel` | `aztec-chisel` | | `nargo` | `aztec-nargo` | | `noir-profiler` | `aztec-noir-profiler` | | `bb` | `aztec-bb` | | `bb-cli` | `aztec-bb-cli` | | `pxe` | `aztec-pxe` | | `txe` | `aztec-txe` | | `validator-client` | `aztec-validator-client` | | `blob-client` | `aztec-blob-client` | `aztec`, `aztec-wallet`, and `aztec-up` keep their existing names. **Operator action:** any operator scripts, systemd units, dockerfiles, or run-books that invoke `forge`, `cast`, `anvil`, `nargo`, `bb`, `pxe`, `txe`, `validator-client`, or `blob-client` directly from the bundle path must switch to the `aztec-` prefixed names. References to `aztec`, `aztec-wallet`, and `aztec-up` are unaffected. References: [#22902](https://github.com/AztecProtocol/aztec-packages/pull/22902), [#22709](https://github.com/AztecProtocol/aztec-packages/pull/22709). ## Other notable changes[​](#other-notable-changes "Direct link to Other notable changes") These items do not require operator action but are called out in the [v4.3.0 release notes](https://github.com/AztecProtocol/aztec-packages/releases/tag/v4.3.0): * **Sequencer signs the last block before archiver sync** ([#22117](https://github.com/AztecProtocol/aztec-packages/pull/22117)) — correctness and ordering improvement around block signing. * **Release image stamps `stdlib/package.json` with the release version** ([#23393](https://github.com/AztecProtocol/aztec-packages/pull/23393)) — fixes a published-artifact metadata mismatch that affected downstream consumers of the stdlib package. * **macOS `aztec-up` install ergonomics** ([#23310](https://github.com/AztecProtocol/aztec-packages/pull/23310), [#23335](https://github.com/AztecProtocol/aztec-packages/pull/23335)) — `aztec-up` now falls back to no-timeout when `/usr/bin/timeout` is absent, and `add_crate.sh` uses `perl -i` instead of GNU-specific `sed -i`. Affects operators who bootstrap nodes via `aztec-up` on macOS. ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") If a previously-working operator command suddenly errors with `command not found: forge` (or `cast`, `nargo`, `bb`, etc.) after upgrading to v4.3.0, switch the call site to the `aztec-` prefixed binary or install the standalone tool separately. --- # Cli Reference **Configuration notes:** * The environment variable name corresponding to each flag is shown as $ENV\_VAR on the right hand side. * If two subsystems can contain the same configuration option, only one needs to be provided. For example, `--archiver.blobSinkUrl` and `--sequencer.blobSinkUrl` point to the same value. ``` MISC --network ($NETWORK) Network to run Aztec on --enable-auto-shutdown (default: false) ($ENABLE_AUTO_SHUTDOWN) Soft-shutdown the node when the canonical rollup is no longer compatible (protocol constants diverge), keeping the health server up so K8s probes keep passing. Only applies to nodes following the canonical rollup. --sync-mode (default: snapshot) ($SYNC_MODE) Set sync mode to `full` to always sync via L1, `snapshot` to download a snapshot if there is no local data, `force-snapshot` to download even if there is local data. --snapshots-urls (default: ) ($SYNC_SNAPSHOTS_URLS) Base URLs for snapshots index, comma-separated. --fisherman-mode ($FISHERMAN_MODE) Whether to run in fisherman mode. LOCAL_NETWORK --local-network Starts Aztec Local Network --local-network.l1Mnemonic (default: test test test test test test test test test test test junk)($MNEMONIC) Mnemonic for L1 accounts. Will be used --local-network.testAccounts (default: true) ($TEST_ACCOUNTS) Deploy test accounts on local network start API --port (default: 8080) ($AZTEC_PORT) Port to run the Aztec Services on --admin-port (default: 8880) ($AZTEC_ADMIN_PORT) Port to run admin APIs of Aztec Services on --admin-api-key-hash ($AZTEC_ADMIN_API_KEY_HASH) SHA-256 hex hash of a pre-generated admin API key. When set, the node uses this hash for authentication instead of auto-generating a key. --disable-admin-api-key ($AZTEC_DISABLE_ADMIN_API_KEY) Disable API key authentication on the admin RPC endpoint. By default, a key is auto-generated, displayed once, and its hash is persisted. --reset-admin-api-key ($AZTEC_RESET_ADMIN_API_KEY) Force-generate a new admin API key, replacing any previously persisted key hash. The new key is displayed once at startup. --node-debug ($AZTEC_NODE_DEBUG) Expose debug endpoints (e.g. mineBlock) on the main RPC port --api-prefix ($API_PREFIX) Prefix for API routes on any service that is started --rpcMaxBatchSize (default: 100) ($RPC_MAX_BATCH_SIZE) Maximum allowed batch size for JSON RPC batch requests. --rpcMaxBodySize (default: 1mb) ($RPC_MAX_BODY_SIZE) Maximum allowed batch size for JSON RPC batch requests. ETHEREUM --l1-chain-id ($L1_CHAIN_ID) The chain ID of the ethereum host. --l1-rpc-urls ($ETHEREUM_HOSTS) List of URLs of Ethereum RPC nodes that services will connect to (comma separated). --l1-consensus-host-urls ($L1_CONSENSUS_HOST_URLS) List of URLs of the Ethereum consensus nodes that services will connect to (comma separated) --l1-consensus-host-api-keys ($L1_CONSENSUS_HOST_API_KEYS) List of API keys for the corresponding L1 consensus clients, if needed. Added to the end of the corresponding URL as "?key=" unless a header is defined --l1-consensus-host-api-key-headers ($L1_CONSENSUS_HOST_API_KEY_HEADERS) List of header names for the corresponding L1 consensus client API keys, if needed. Added to the corresponding request as ": " L1 CONTRACTS --registry-address ($REGISTRY_CONTRACT_ADDRESS) The deployed L1 registry contract address. --rollup-version ($ROLLUP_VERSION) The version of the rollup. STORAGE --data-directory ($DATA_DIRECTORY) Optional dir to store data. If omitted will store in memory. --data-store-map-size-kb (default: 134217728) ($DATA_STORE_MAP_SIZE_KB) The maximum possible size of a data store DB in KB. Can be overridden by component-specific options. WORLD STATE --world-state-data-directory ($WS_DATA_DIRECTORY) Optional directory for the world state database --world-state-db-map-size-kb ($WS_DB_MAP_SIZE_KB) The maximum possible size of the world state DB in KB. Overwrites the general dataStoreMapSizeKb. --world-state-checkpoint-history (default: 64) ($WS_NUM_HISTORIC_CHECKPOINTS) The number of historic checkpoints worth of blocks to maintain. Values less than 1 mean all history is maintained AZTEC NODE --node Starts Aztec Node with options ARCHIVER --archiver Starts Aztec Archiver with options --archiver.blobSinkMapSizeKb ($BLOB_SINK_MAP_SIZE_KB) The maximum possible size of the blob sink DB in KB. Overwrites the general dataStoreMapSizeKb. --archiver.blobAllowEmptySources ($BLOB_ALLOW_EMPTY_SOURCES) Whether to allow having no blob sources configured during startup --archiver.blobFileStoreUrls ($BLOB_FILE_STORE_URLS) URLs for filestore blob archive, comma-separated. Tried in order until blobs are found. --archiver.blobFileStoreUploadUrl ($BLOB_FILE_STORE_UPLOAD_URL) URL for uploading blobs to filestore (s3://, gs://, file://) --archiver.blobHealthcheckUploadIntervalMinutes ($BLOB_HEALTHCHECK_UPLOAD_INTERVAL_MINUTES) Interval in minutes for uploading healthcheck file to file store (default: 60 = 1 hour) --archiver.archiveApiUrl ($BLOB_ARCHIVE_API_URL) The URL of the archive API --archiver.archiverPollingIntervalMS (default: 500) ($ARCHIVER_POLLING_INTERVAL_MS) The polling interval in ms for retrieving new L2 blocks and encrypted logs. --archiver.archiverBatchSize (default: 100) ($ARCHIVER_BATCH_SIZE) The number of L2 blocks the archiver will attempt to download at a time. --archiver.maxLogs (default: 1000) ($ARCHIVER_MAX_LOGS) The max number of logs that can be obtained in 1 "getPublicLogs" call. --archiver.archiverStoreMapSizeKb ($ARCHIVER_STORE_MAP_SIZE_KB) The maximum possible size of the archiver DB in KB. Overwrites the general dataStoreMapSizeKb. --archiver.skipValidateCheckpointAttestations Skip validating checkpoint attestations (for testing purposes only) --archiver.maxAllowedEthClientDriftSeconds (default: 300) ($MAX_ALLOWED_ETH_CLIENT_DRIFT_SECONDS) Maximum allowed drift in seconds between the Ethereum client and current time. --archiver.ethereumAllowNoDebugHosts (default: true) ($ETHEREUM_ALLOW_NO_DEBUG_HOSTS) Whether to allow starting the archiver without debug/trace method support on Ethereum hosts SEQUENCER --sequencer Starts Aztec Sequencer with options --sequencer.validatorPrivateKeys (default: [Redacted]) ($VALIDATOR_PRIVATE_KEYS) List of private keys of the validators participating in attestation duties --sequencer.validatorAddresses (default: ) ($VALIDATOR_ADDRESSES) List of addresses of the validators to use with remote signers --sequencer.disableValidator ($VALIDATOR_DISABLED) Do not run the validator --sequencer.disabledValidators (default: ) Temporarily disable these specific validator addresses --sequencer.attestationPollingIntervalMs (default: 200) ($VALIDATOR_ATTESTATIONS_POLLING_INTERVAL_MS) Interval between polling for new attestations --sequencer.validatorReexecute (default: true) ($VALIDATOR_REEXECUTE) Re-execute transactions before attesting --sequencer.alwaysReexecuteBlockProposals (default: true) Whether to always reexecute block proposals, even for non-validator nodes (useful for monitoring network status). --sequencer.skipCheckpointProposalValidation Skip checkpoint proposal validation and always attest (default: false) --sequencer.skipPushProposedBlocksToArchiver Skip pushing proposed blocks to archiver (default: true) --sequencer.attestToEquivocatedProposals Agree to attest to equivocated checkpoint proposals (for testing purposes only) --sequencer.validateMaxL2BlockGas ($VALIDATOR_MAX_L2_BLOCK_GAS) Maximum L2 block gas for validation. Proposals exceeding this limit are rejected. --sequencer.validateMaxDABlockGas ($VALIDATOR_MAX_DA_BLOCK_GAS) Maximum DA block gas for validation. Proposals exceeding this limit are rejected. --sequencer.validateMaxTxsPerBlock ($VALIDATOR_MAX_TX_PER_BLOCK) Maximum transactions per block for validation. Proposals exceeding this limit are rejected. --sequencer.validateMaxTxsPerCheckpoint ($VALIDATOR_MAX_TX_PER_CHECKPOINT) Maximum transactions per checkpoint for validation. Proposals exceeding this limit are rejected. --sequencer.haSigningEnabled ($VALIDATOR_HA_SIGNING_ENABLED) Whether HA signing / slashing protection is enabled --sequencer.nodeId ($VALIDATOR_HA_NODE_ID) The unique identifier for this node --sequencer.pollingIntervalMs (default: 100) ($VALIDATOR_HA_POLLING_INTERVAL_MS) The number of ms to wait between polls when a duty is being signed --sequencer.signingTimeoutMs (default: 3000) ($VALIDATOR_HA_SIGNING_TIMEOUT_MS) The maximum time to wait for a duty being signed to complete --sequencer.maxStuckDutiesAgeMs ($VALIDATOR_HA_MAX_STUCK_DUTIES_AGE_MS) The maximum age of a stuck duty in ms (defaults to 2x Aztec slot duration) --sequencer.cleanupOldDutiesAfterHours ($VALIDATOR_HA_OLD_DUTIES_MAX_AGE_H) Optional: clean up old duties after this many hours (disabled if not set) --sequencer.databaseUrl ($VALIDATOR_HA_DATABASE_URL) PostgreSQL connection string for validator HA signer (format: postgresql://user:password@host:port/database) --sequencer.poolMaxCount (default: 10) ($VALIDATOR_HA_POOL_MAX) Maximum number of clients in the pool --sequencer.poolMinCount ($VALIDATOR_HA_POOL_MIN) Minimum number of clients in the pool --sequencer.poolIdleTimeoutMs (default: 10000) ($VALIDATOR_HA_POOL_IDLE_TIMEOUT_MS) Idle timeout in milliseconds --sequencer.poolConnectionTimeoutMs ($VALIDATOR_HA_POOL_CONNECTION_TIMEOUT_MS) Connection timeout in milliseconds (0 means no timeout) --sequencer.sequencerPollingIntervalMS (default: 500) ($SEQ_POLLING_INTERVAL_MS) The number of ms to wait between polling for checking to build on the next slot. --sequencer.maxTxsPerCheckpoint ($SEQ_MAX_TX_PER_CHECKPOINT) The maximum number of txs across all blocks in a checkpoint. --sequencer.minTxsPerBlock (default: 1) ($SEQ_MIN_TX_PER_BLOCK) The minimum number of txs to include in a block. --sequencer.minValidTxsPerBlock The minimum number of valid txs (after execution) to include in a block. If not set, falls back to minTxsPerBlock. --sequencer.publishTxsWithProposals ($SEQ_PUBLISH_TXS_WITH_PROPOSALS) Whether to publish txs with proposals. --sequencer.maxL2BlockGas ($SEQ_MAX_L2_BLOCK_GAS) The maximum L2 block gas. --sequencer.maxDABlockGas ($SEQ_MAX_DA_BLOCK_GAS) The maximum DA block gas. --sequencer.perBlockAllocationMultiplier (default: 1.2) ($SEQ_PER_BLOCK_ALLOCATION_MULTIPLIER) Per-block gas budget multiplier for both L2 and DA gas. Budget per block is (checkpointLimit / maxBlocks) * multiplier. Values greater than one allow early blocks to use more than their even share, relying on checkpoint-level capping for later blocks. --sequencer.redistributeCheckpointBudget (default: true) ($SEQ_REDISTRIBUTE_CHECKPOINT_BUDGET) Redistribute remaining checkpoint budget evenly across remaining blocks instead of allowing a single block to consume the entire remaining budget. --sequencer.coinbase ($COINBASE) Recipient of block reward. --sequencer.feeRecipient ($FEE_RECIPIENT) Address to receive fees. --sequencer.acvmWorkingDirectory ($ACVM_WORKING_DIRECTORY) The working directory to use for simulation/proving --sequencer.acvmBinaryPath ($ACVM_BINARY_PATH) The path to the ACVM binary --sequencer.enforceTimeTable (default: true) ($SEQ_ENFORCE_TIME_TABLE) Whether to enforce the time table when building blocks --sequencer.governanceProposerPayload ($GOVERNANCE_PROPOSER_PAYLOAD_ADDRESS) The address of the payload for the governanceProposer --sequencer.l1PublishingTime ($SEQ_L1_PUBLISHING_TIME_ALLOWANCE_IN_SLOT) How much time (in seconds) we allow in the slot for publishing the L1 tx (defaults to 1 L1 slot). --sequencer.attestationPropagationTime (default: 2) ($SEQ_ATTESTATION_PROPAGATION_TIME) How many seconds it takes for proposals and attestations to travel across the p2p layer (one-way) --sequencer.secondsBeforeInvalidatingBlockAsCommitteeMember (default: 144) ($SEQ_SECONDS_BEFORE_INVALIDATING_BLOCK_AS_COMMITTEE_MEMBER) How many seconds to wait before trying to invalidate a block from the pending chain as a committee member (zero to never invalidate). The next proposer is expected to invalidate, so the committee acts as a fallback. --sequencer.secondsBeforeInvalidatingBlockAsNonCommitteeMember (default: 432) ($SEQ_SECONDS_BEFORE_INVALIDATING_BLOCK_AS_NON_COMMITTEE_MEMBER) How many seconds to wait before trying to invalidate a block from the pending chain as a non-committee member (zero to never invalidate). The next proposer is expected to invalidate, then the committee, so other sequencers act as a fallback. --sequencer.broadcastInvalidBlockProposal Broadcast invalid block proposals with corrupted state (for testing only) --sequencer.injectFakeAttestation Inject a fake attestation (for testing only) --sequencer.injectHighSValueAttestation Inject a malleable attestation with a high-s value (for testing only) --sequencer.injectUnrecoverableSignatureAttestation Inject an attestation with an unrecoverable signature (for testing only) --sequencer.shuffleAttestationOrdering Shuffle attestation ordering to create invalid ordering (for testing only) --sequencer.blockDurationMs ($SEQ_BLOCK_DURATION_MS) Duration per block in milliseconds when building multiple blocks per slot. If undefined (default), builds a single block per slot using the full slot duration. --sequencer.expectedBlockProposalsPerSlot ($SEQ_EXPECTED_BLOCK_PROPOSALS_PER_SLOT) Expected number of block proposals per slot for P2P peer scoring. 0 (default) disables block proposal scoring. Set to a positive value to enable. --sequencer.maxTxsPerBlock ($SEQ_MAX_TX_PER_BLOCK) The maximum number of txs to include in a block. --sequencer.buildCheckpointIfEmpty ($SEQ_BUILD_CHECKPOINT_IF_EMPTY) Have sequencer build and publish an empty checkpoint if there are no txs --sequencer.minBlocksForCheckpoint Minimum number of blocks required for a checkpoint proposal (test only) --sequencer.skipPublishingCheckpointsPercent ($SEQ_SKIP_CHECKPOINT_PUBLISH_PERCENT) Percent probability (0 - 100) of sequencer skipping checkpoint publishing (testing only) --sequencer.txPublicSetupAllowListExtend ($TX_PUBLIC_SETUP_ALLOWLIST) Additional entries to extend the default setup allow list. Format: I:address:selector[:flags],C:classId:selector[:flags]. Flags: os (onlySelf), rn (rejectNullMsgSender), cl=N (calldataLength), joined with +. --sequencer.keyStoreDirectory ($KEY_STORE_DIRECTORY) Location of key store directory --sequencer.sequencerPublisherPrivateKeys (default: ) ($SEQ_PUBLISHER_PRIVATE_KEYS) The private keys to be used by the sequencer publisher. --sequencer.sequencerPublisherAddresses (default: ) ($SEQ_PUBLISHER_ADDRESSES) The addresses of the publishers to use with remote signers --sequencer.blobAllowEmptySources ($BLOB_ALLOW_EMPTY_SOURCES) Whether to allow having no blob sources configured during startup --sequencer.blobFileStoreUrls ($BLOB_FILE_STORE_URLS) URLs for filestore blob archive, comma-separated. Tried in order until blobs are found. --sequencer.blobFileStoreUploadUrl ($BLOB_FILE_STORE_UPLOAD_URL) URL for uploading blobs to filestore (s3://, gs://, file://) --sequencer.blobHealthcheckUploadIntervalMinutes ($BLOB_HEALTHCHECK_UPLOAD_INTERVAL_MINUTES) Interval in minutes for uploading healthcheck file to file store (default: 60 = 1 hour) --sequencer.archiveApiUrl ($BLOB_ARCHIVE_API_URL) The URL of the archive API --sequencer.sequencerPublisherAllowInvalidStates (default: true) ($SEQ_PUBLISHER_ALLOW_INVALID_STATES) True to use publishers in invalid states (timed out, cancelled, etc) if no other is available --sequencer.sequencerPublisherForwarderAddress ($SEQ_PUBLISHER_FORWARDER_ADDRESS) Address of the forwarder contract to wrap all L1 transactions through (for testing purposes only) PROVER NODE --prover-node Starts Aztec Prover Node with options --proverNode.keyStoreDirectory ($KEY_STORE_DIRECTORY) Location of key store directory --proverNode.acvmWorkingDirectory ($ACVM_WORKING_DIRECTORY) The working directory to use for simulation/proving --proverNode.acvmBinaryPath ($ACVM_BINARY_PATH) The path to the ACVM binary --proverNode.bbWorkingDirectory ($BB_WORKING_DIRECTORY) The working directory to use for proving --proverNode.bbBinaryPath ($BB_BINARY_PATH) The path to the bb binary --proverNode.bbSkipCleanup ($BB_SKIP_CLEANUP) Whether to skip cleanup of bb temporary files --proverNode.numConcurrentIVCVerifiers (default: 8) ($BB_NUM_IVC_VERIFIERS) Max number of chonk verifiers to run concurrently --proverNode.bbIVCConcurrency (default: 1) ($BB_IVC_CONCURRENCY) Number of threads to use for IVC verification --proverNode.nodeUrl ($AZTEC_NODE_URL) The URL to the Aztec node to take proving jobs from --proverNode.proverId ($PROVER_ID) Hex value that identifies the prover. Defaults to the address used for submitting proofs if not set. --proverNode.failedProofStore ($PROVER_FAILED_PROOF_STORE) Store for failed proof inputs. Google cloud storage is only supported at the moment. Set this value as gs://bucket-name/path/to/store. --proverNode.enqueueConcurrency (default: 50) ($PROVER_ENQUEUE_CONCURRENCY) Max concurrent jobs the orchestrator serializes and enqueues to the broker. --proverNode.blobSinkMapSizeKb ($BLOB_SINK_MAP_SIZE_KB) The maximum possible size of the blob sink DB in KB. Overwrites the general dataStoreMapSizeKb. --proverNode.blobAllowEmptySources ($BLOB_ALLOW_EMPTY_SOURCES) Whether to allow having no blob sources configured during startup --proverNode.blobFileStoreUrls ($BLOB_FILE_STORE_URLS) URLs for filestore blob archive, comma-separated. Tried in order until blobs are found. --proverNode.blobFileStoreUploadUrl ($BLOB_FILE_STORE_UPLOAD_URL) URL for uploading blobs to filestore (s3://, gs://, file://) --proverNode.blobHealthcheckUploadIntervalMinutes ($BLOB_HEALTHCHECK_UPLOAD_INTERVAL_MINUTES) Interval in minutes for uploading healthcheck file to file store (default: 60 = 1 hour) --proverNode.archiveApiUrl ($BLOB_ARCHIVE_API_URL) The URL of the archive API --proverNode.proverPublisherAllowInvalidStates (default: true) ($PROVER_PUBLISHER_ALLOW_INVALID_STATES) True to use publishers in invalid states (timed out, cancelled, etc) if no other is available --proverNode.proverPublisherForwarderAddress ($PROVER_PUBLISHER_FORWARDER_ADDRESS) Address of the forwarder contract to wrap all L1 transactions through (for testing purposes only) --proverNode.proverPublisherPrivateKeys (default: ) ($PROVER_PUBLISHER_PRIVATE_KEYS) The private keys to be used by the prover publisher. --proverNode.proverPublisherAddresses (default: ) ($PROVER_PUBLISHER_ADDRESSES) The addresses of the publishers to use with remote signers --proverNode.proverNodeMaxPendingJobs (default: 10) ($PROVER_NODE_MAX_PENDING_JOBS) The maximum number of pending jobs for the prover node --proverNode.proverNodePollingIntervalMs (default: 1000) ($PROVER_NODE_POLLING_INTERVAL_MS) The interval in milliseconds to poll for new jobs --proverNode.proverNodeMaxParallelBlocksPerEpoch ($PROVER_NODE_MAX_PARALLEL_BLOCKS_PER_EPOCH) The Maximum number of blocks to process in parallel while proving an epoch --proverNode.proverNodeFailedEpochStore ($PROVER_NODE_FAILED_EPOCH_STORE) File store where to upload node state when an epoch fails to be proven --proverNode.proverNodeEpochProvingDelayMs Optional delay in milliseconds to wait before proving a new epoch --proverNode.txGatheringIntervalMs (default: 1000) ($PROVER_NODE_TX_GATHERING_INTERVAL_MS) How often to check that tx data is available --proverNode.txGatheringBatchSize (default: 10) ($PROVER_NODE_TX_GATHERING_BATCH_SIZE) How many transactions to gather from a node in a single request --proverNode.txGatheringMaxParallelRequestsPerNode (default: 100) ($PROVER_NODE_TX_GATHERING_MAX_PARALLEL_REQUESTS_PER_NODE) How many tx requests to make in parallel to each node --proverNode.txGatheringTimeoutMs (default: 120000) ($PROVER_NODE_TX_GATHERING_TIMEOUT_MS) How long to wait for tx data to be available before giving up --proverNode.proverNodeDisableProofPublish ($PROVER_NODE_DISABLE_PROOF_PUBLISH) Whether the prover node skips publishing proofs to L1 --proverNode.web3SignerUrl ($WEB3_SIGNER_URL) URL of the Web3Signer instance PROVER BROKER --prover-broker Starts Aztec proving job broker --proverBroker.proverBrokerJobTimeoutMs (default: 30000) ($PROVER_BROKER_JOB_TIMEOUT_MS) Jobs are retried if not kept alive for this long --proverBroker.proverBrokerPollIntervalMs (default: 1000) ($PROVER_BROKER_POLL_INTERVAL_MS) The interval to check job health status --proverBroker.proverBrokerJobMaxRetries (default: 3) ($PROVER_BROKER_JOB_MAX_RETRIES) If starting a prover broker locally, the max number of retries per proving job --proverBroker.proverBrokerBatchSize (default: 100) ($PROVER_BROKER_BATCH_SIZE) The prover broker writes jobs to disk in batches --proverBroker.proverBrokerBatchIntervalMs (default: 50) ($PROVER_BROKER_BATCH_INTERVAL_MS) How often to flush batches to disk --proverBroker.proverBrokerMaxEpochsToKeepResultsFor (default: 1) ($PROVER_BROKER_MAX_EPOCHS_TO_KEEP_RESULTS_FOR) The maximum number of epochs to keep results for --proverBroker.proverBrokerStoreMapSizeKb ($PROVER_BROKER_STORE_MAP_SIZE_KB) The size of the prover broker's database. Will override the dataStoreMapSizeKb if set. --proverBroker.proverBrokerDebugReplayEnabled ($PROVER_BROKER_DEBUG_REPLAY_ENABLED) Enable debug replay mode for replaying proving jobs from stored inputs PROVER AGENT --prover-agent Starts Aztec Prover Agent with options --proverAgent.proverAgentCount (default: 1) ($PROVER_AGENT_COUNT) Whether this prover has a local prover agent --proverAgent.proverAgentPollIntervalMs (default: 1000) ($PROVER_AGENT_POLL_INTERVAL_MS) The interval agents poll for jobs at --proverAgent.proverAgentProofTypes ($PROVER_AGENT_PROOF_TYPES) The types of proofs the prover agent can generate --proverAgent.proverBrokerUrl ($PROVER_BROKER_HOST) The URL where this agent takes jobs from --proverAgent.realProofs (default: true) ($PROVER_REAL_PROOFS) Whether to construct real proofs --proverAgent.proverTestDelayType (default: fixed) ($PROVER_TEST_DELAY_TYPE) The type of artificial delay to introduce --proverAgent.proverTestDelayMs ($PROVER_TEST_DELAY_MS) Artificial delay to introduce to all operations to the test prover. --proverAgent.proverTestDelayFactor (default: 1) ($PROVER_TEST_DELAY_FACTOR) If using realistic delays, what percentage of realistic times to apply. --proverAgent.proverTestVerificationDelayMs (default: 10) ($PROVER_TEST_VERIFICATION_DELAY_MS) The delay (ms) to inject during fake proof verification --proverAgent.cancelJobsOnStop ($PROVER_CANCEL_JOBS_ON_STOP) Whether to abort pending proving jobs when the orchestrator is cancelled. When false (default), jobs remain in the broker queue and can be reused on restart/reorg. --proverAgent.proofStore ($PROVER_PROOF_STORE) Optional proof input store for the prover P2P SUBSYSTEM --p2p-enabled [value] ($P2P_ENABLED) Enable P2P subsystem --p2p.validateMaxTxsPerBlock ($VALIDATOR_MAX_TX_PER_BLOCK) Maximum transactions per block for validation. Overrides maxTxsPerBlock for gossip validation when set. --p2p.validateMaxTxsPerCheckpoint ($VALIDATOR_MAX_TX_PER_CHECKPOINT) Maximum transactions per checkpoint for validation. Used as fallback for maxTxsPerBlock when that is not set. --p2p.validateMaxL2BlockGas ($VALIDATOR_MAX_L2_BLOCK_GAS) Maximum L2 gas per block for validation. When set, txs exceeding this limit are rejected. --p2p.validateMaxDABlockGas ($VALIDATOR_MAX_DA_BLOCK_GAS) Maximum DA gas per block for validation. When set, txs exceeding this limit are rejected. --p2p.p2pDiscoveryDisabled ($P2P_DISCOVERY_DISABLED) A flag dictating whether the P2P discovery system should be disabled. --p2p.blockCheckIntervalMS (default: 100) ($P2P_BLOCK_CHECK_INTERVAL_MS) The frequency in which to check for new L2 blocks. --p2p.slotCheckIntervalMS (default: 1000) ($P2P_SLOT_CHECK_INTERVAL_MS) The frequency in which to check for new L2 slots. --p2p.debugDisableColocationPenalty ($DEBUG_P2P_DISABLE_COLOCATION_PENALTY) DEBUG: Disable colocation penalty - NEVER set to true in production --p2p.peerCheckIntervalMS (default: 30000) ($P2P_PEER_CHECK_INTERVAL_MS) The frequency in which to check for new peers. --p2p.l2QueueSize (default: 1000) ($P2P_L2_QUEUE_SIZE) Size of queue of L2 blocks to store. --p2p.listenAddress (default: 0.0.0.0) ($P2P_LISTEN_ADDR) The listen address. ipv4 address. --p2p.p2pPort (default: 40400) ($P2P_PORT) The port for the P2P service. Defaults to 40400 --p2p.p2pBroadcastPort ($P2P_BROADCAST_PORT) The port to broadcast the P2P service on (included in the node's ENR). Defaults to P2P_PORT. --p2p.p2pIp ($P2P_IP) The IP address for the P2P service. ipv4 address. --p2p.peerIdPrivateKey ($PEER_ID_PRIVATE_KEY) An optional peer id private key. If blank, will generate a random key. --p2p.peerIdPrivateKeyPath ($PEER_ID_PRIVATE_KEY_PATH) An optional path to store generated peer id private keys. If blank, will default to storing any generated keys in the root of the data directory. --p2p.bootstrapNodes (default: ) ($BOOTSTRAP_NODES) A list of bootstrap peer ENRs to connect to. Separated by commas. --p2p.bootstrapNodeEnrVersionCheck ($P2P_BOOTSTRAP_NODE_ENR_VERSION_CHECK) Whether to check the version of the bootstrap node ENR. --p2p.bootstrapNodesAsFullPeers ($P2P_BOOTSTRAP_NODES_AS_FULL_PEERS) Whether to consider our configured bootnodes as full peers --p2p.maxPeerCount (default: 100) ($P2P_MAX_PEERS) The maximum number of peers to connect to. --p2p.queryForIp ($P2P_QUERY_FOR_IP) If announceUdpAddress or announceTcpAddress are not provided, query for the IP address of the machine. Default is false. --p2p.gossipsubInterval (default: 700) ($P2P_GOSSIPSUB_INTERVAL_MS) The interval of the gossipsub heartbeat to perform maintenance tasks. --p2p.gossipsubD (default: 8) ($P2P_GOSSIPSUB_D) The D parameter for the gossipsub protocol. --p2p.gossipsubDlo (default: 4) ($P2P_GOSSIPSUB_DLO) The Dlo parameter for the gossipsub protocol. --p2p.gossipsubDhi (default: 12) ($P2P_GOSSIPSUB_DHI) The Dhi parameter for the gossipsub protocol. --p2p.gossipsubDLazy (default: 8) ($P2P_GOSSIPSUB_DLAZY) The Dlazy parameter for the gossipsub protocol. --p2p.gossipsubFloodPublish ($P2P_GOSSIPSUB_FLOOD_PUBLISH) Whether to flood publish messages. - For testing purposes only --p2p.gossipsubMcacheLength (default: 6) ($P2P_GOSSIPSUB_MCACHE_LENGTH) The number of gossipsub interval message cache windows to keep. --p2p.gossipsubMcacheGossip (default: 3) ($P2P_GOSSIPSUB_MCACHE_GOSSIP) How many message cache windows to include when gossiping with other peers. --p2p.gossipsubSeenTTL (default: 1200000) ($P2P_GOSSIPSUB_SEEN_TTL) How long to keep message IDs in the seen cache. --p2p.gossipsubTxTopicWeight (default: 1) ($P2P_GOSSIPSUB_TX_TOPIC_WEIGHT) The weight of the tx topic for the gossipsub protocol. --p2p.gossipsubTxInvalidMessageDeliveriesWeight (default: -20) ($P2P_GOSSIPSUB_TX_INVALID_MESSAGE_DELIVERIES_WEIGHT) The weight of the tx invalid message deliveries for the gossipsub protocol. --p2p.gossipsubTxInvalidMessageDeliveriesDecay (default: 0.5) ($P2P_GOSSIPSUB_TX_INVALID_MESSAGE_DELIVERIES_DECAY) Determines how quickly the penalty for invalid message deliveries decays over time. Between 0 and 1. --p2p.peerPenaltyValues (default: 2,10,50) ($P2P_PEER_PENALTY_VALUES) The values for the peer scoring system. Passed as a comma separated list of values in order: low, mid, high tolerance errors. --p2p.doubleSpendSeverePeerPenaltyWindow (default: 30) ($P2P_DOUBLE_SPEND_SEVERE_PEER_PENALTY_WINDOW) The "age" (in L2 blocks) of a tx after which we heavily penalize a peer for sending it. --p2p.blockRequestBatchSize (default: 20) ($P2P_BLOCK_REQUEST_BATCH_SIZE) The number of blocks to fetch in a single batch. --p2p.archivedTxLimit ($P2P_ARCHIVED_TX_LIMIT) The number of transactions that will be archived. If the limit is set to 0 then archiving will be disabled. --p2p.trustedPeers (default: ) ($P2P_TRUSTED_PEERS) A list of trusted peer ENRs that will always be persisted. Separated by commas. --p2p.privatePeers (default: ) ($P2P_PRIVATE_PEERS) A list of private peer ENRs that will always be persisted and not be used for discovery. Separated by commas. --p2p.preferredPeers (default: ) ($P2P_PREFERRED_PEERS) A list of preferred peer ENRs that will always be persisted and not be used for discovery. Separated by commas. --p2p.p2pStoreMapSizeKb ($P2P_STORE_MAP_SIZE_KB) The maximum possible size of the P2P DB in KB. Overwrites the general dataStoreMapSizeKb. --p2p.txPublicSetupAllowListExtend ($TX_PUBLIC_SETUP_ALLOWLIST) Additional entries to extend the default setup allow list. Format: I:address:selector[:flags],C:classId:selector[:flags]. Flags: os (onlySelf), rn (rejectNullMsgSender), cl=N (calldataLength), joined with +. --p2p.maxPendingTxCount (default: 1000) ($P2P_MAX_PENDING_TX_COUNT) The maximum number of pending txs before evicting lower priority txs. --p2p.seenMessageCacheSize (default: 100000) ($P2P_SEEN_MSG_CACHE_SIZE) The number of messages to keep in the seen message cache --p2p.p2pDisableStatusHandshake ($P2P_DISABLE_STATUS_HANDSHAKE) True to disable the status handshake on peer connected. --p2p.p2pAllowOnlyValidators ($P2P_ALLOW_ONLY_VALIDATORS) True to only permit validators to connect. --p2p.p2pMaxFailedAuthAttemptsAllowed (default: 3) ($P2P_MAX_AUTH_FAILED_ATTEMPTS_ALLOWED) Number of auth attempts to allow before peer is banned. Number is inclusive --p2p.dropTransactions ($P2P_DROP_TX) True to simulate discarding transactions. - For testing purposes only --p2p.dropTransactionsProbability ($P2P_DROP_TX_CHANCE) The probability that a transaction is discarded (0 - 1). - For testing purposes only --p2p.disableTransactions ($TRANSACTIONS_DISABLED) Whether transactions are disabled for this node. This means transactions will be rejected at the RPC and P2P layers. --p2p.txPoolDeleteTxsAfterReorg ($P2P_TX_POOL_DELETE_TXS_AFTER_REORG) Whether to delete transactions from the pool after a reorg instead of moving them back to pending. --p2p.debugP2PInstrumentMessages ($DEBUG_P2P_INSTRUMENT_MESSAGES) Alters the format of p2p messages to include things like broadcast timestamp FOR TESTING ONLY --p2p.broadcastEquivocatedProposals Broadcast block proposals even when a conflicting proposal for the same slot already exists in the pool (for testing purposes only). --p2p.minTxPoolAgeMs (default: 2000) ($P2P_MIN_TX_POOL_AGE_MS) Minimum age (ms) a transaction must have been in the pool before it is eligible for block building. --p2p.priceBumpPercentage (default: 10) ($P2P_RPC_PRICE_BUMP_PERCENTAGE) Minimum percentage fee increase required to replace an existing tx via RPC. Even at 0%, replacement still requires paying at least 1 unit more. --p2p.blockDurationMs ($SEQ_BLOCK_DURATION_MS) Duration per block in milliseconds when building multiple blocks per slot. If undefined (default), builds a single block per slot using the full slot duration. --p2p.expectedBlockProposalsPerSlot ($SEQ_EXPECTED_BLOCK_PROPOSALS_PER_SLOT) Expected number of block proposals per slot for P2P peer scoring. 0 (default) disables block proposal scoring. Set to a positive value to enable. --p2p.maxTxsPerBlock ($SEQ_MAX_TX_PER_BLOCK) The maximum number of txs to include in a block. --p2p.overallRequestTimeoutMs (default: 10000) ($P2P_REQRESP_OVERALL_REQUEST_TIMEOUT_MS) The overall timeout for a request response operation. --p2p.individualRequestTimeoutMs (default: 10000) ($P2P_REQRESP_INDIVIDUAL_REQUEST_TIMEOUT_MS) The timeout for an individual request response peer interaction. --p2p.dialTimeoutMs (default: 5000) ($P2P_REQRESP_DIAL_TIMEOUT_MS) How long to wait for the dial protocol to establish a connection --p2p.p2pOptimisticNegotiation ($P2P_REQRESP_OPTIMISTIC_NEGOTIATION) Whether to use optimistic protocol negotiation when dialing to another peer (opposite of `negotiateFully`). --p2p.batchTxRequesterSmartParallelWorkerCount (default: 10) ($P2P_BATCH_TX_REQUESTER_SMART_PARALLEL_WORKER_COUNT) Max concurrent requests to smart peers for batch tx requester. --p2p.batchTxRequesterDumbParallelWorkerCount (default: 10) ($P2P_BATCH_TX_REQUESTER_DUMB_PARALLEL_WORKER_COUNT) Max concurrent requests to dumb peers for batch tx requester. --p2p.batchTxRequesterTxBatchSize (default: 8) ($P2P_BATCH_TX_REQUESTER_TX_BATCH_SIZE) Max transactions per request / chunk size for batch tx requester. --p2p.batchTxRequesterBadPeerThreshold (default: 2) ($P2P_BATCH_TX_REQUESTER_BAD_PEER_THRESHOLD) Failures before a peer is considered bad (see > threshold logic). --p2p.txCollectionFastNodesTimeoutBeforeReqRespMs (default: 200) ($TX_COLLECTION_FAST_NODES_TIMEOUT_BEFORE_REQ_RESP_MS) How long to wait before starting reqresp for fast collection --p2p.txCollectionSlowNodesIntervalMs (default: 12000) ($TX_COLLECTION_SLOW_NODES_INTERVAL_MS) How often to collect from configured nodes in the slow collection loop --p2p.txCollectionSlowReqRespIntervalMs (default: 12000) ($TX_COLLECTION_SLOW_REQ_RESP_INTERVAL_MS) How often to collect from peers via reqresp in the slow collection loop --p2p.txCollectionSlowReqRespTimeoutMs (default: 20000) ($TX_COLLECTION_SLOW_REQ_RESP_TIMEOUT_MS) How long to wait for a reqresp response during slow collection --p2p.txCollectionReconcileIntervalMs (default: 60000) ($TX_COLLECTION_RECONCILE_INTERVAL_MS) How often to reconcile found txs from the tx pool --p2p.txCollectionDisableSlowDuringFastRequests (default: true) ($TX_COLLECTION_DISABLE_SLOW_DURING_FAST_REQUESTS) Whether to disable the slow collection loop if we are dealing with any immediate requests --p2p.txCollectionFastNodeIntervalMs (default: 500) ($TX_COLLECTION_FAST_NODE_INTERVAL_MS) How many ms to wait between retried request to a node via RPC during fast collection --p2p.txCollectionNodeRpcUrls (default: ) ($TX_COLLECTION_NODE_RPC_URLS) A comma-separated list of Aztec node RPC URLs to use for tx collection --p2p.txCollectionFastMaxParallelRequestsPerNode (default: 4) ($TX_COLLECTION_FAST_MAX_PARALLEL_REQUESTS_PER_NODE) Maximum number of parallel requests to make to a node during fast collection --p2p.txCollectionNodeRpcMaxBatchSize (default: 50) ($TX_COLLECTION_NODE_RPC_MAX_BATCH_SIZE) Maximum number of transactions to request from a node in a single batch --p2p.txCollectionMissingTxsCollectorType (default: new) ($TX_COLLECTION_MISSING_TXS_COLLECTOR_TYPE) Which collector implementation to use for missing txs collection (new or old) --p2p.txCollectionFileStoreUrls (default: ) ($TX_COLLECTION_FILE_STORE_URLS) A comma-separated list of file store URLs (s3://, gs://, file://, http://) for tx collection --p2p.txCollectionFileStoreSlowDelayMs (default: 24000) ($TX_COLLECTION_FILE_STORE_SLOW_DELAY_MS) Delay before file store collection starts after slow collection --p2p.txCollectionFileStoreFastDelayMs (default: 2000) ($TX_COLLECTION_FILE_STORE_FAST_DELAY_MS) Delay before file store collection starts after fast collection --p2p.txCollectionFileStoreFastWorkerCount (default: 5) ($TX_COLLECTION_FILE_STORE_FAST_WORKER_COUNT) Number of concurrent workers for fast file store collection --p2p.txCollectionFileStoreSlowWorkerCount (default: 2) ($TX_COLLECTION_FILE_STORE_SLOW_WORKER_COUNT) Number of concurrent workers for slow file store collection --p2p.txCollectionFileStoreFastBackoffBaseMs (default: 1000) ($TX_COLLECTION_FILE_STORE_FAST_BACKOFF_BASE_MS) Base backoff time in ms for fast file store collection retries --p2p.txCollectionFileStoreSlowBackoffBaseMs (default: 5000) ($TX_COLLECTION_FILE_STORE_SLOW_BACKOFF_BASE_MS) Base backoff time in ms for slow file store collection retries --p2p.txCollectionFileStoreFastBackoffMaxMs (default: 5000) ($TX_COLLECTION_FILE_STORE_FAST_BACKOFF_MAX_MS) Max backoff time in ms for fast file store collection retries --p2p.txCollectionFileStoreSlowBackoffMaxMs (default: 30000) ($TX_COLLECTION_FILE_STORE_SLOW_BACKOFF_MAX_MS) Max backoff time in ms for slow file store collection retries --p2p.txFileStoreUrl ($TX_FILE_STORE_URL) URL for uploading txs to file storage (s3://, gs://, file://) --p2p.txFileStoreUploadConcurrency (default: 10) ($TX_FILE_STORE_UPLOAD_CONCURRENCY) Maximum number of concurrent tx uploads --p2p.txFileStoreMaxQueueSize (default: 1000) ($TX_FILE_STORE_MAX_QUEUE_SIZE) Maximum queue size for pending uploads (oldest dropped when exceeded) --p2p.txFileStoreEnabled ($TX_FILE_STORE_ENABLED) Enable uploading transactions to file storage P2P BOOTSTRAP --p2p-bootstrap Starts Aztec P2P Bootstrap with options --p2pBootstrap.p2pBroadcastPort ($P2P_BROADCAST_PORT) The port to broadcast the P2P service on (included in the node's ENR). Defaults to P2P_PORT. --p2pBootstrap.peerIdPrivateKeyPath ($PEER_ID_PRIVATE_KEY_PATH) An optional path to store generated peer id private keys. If blank, will default to storing any generated keys in the root of the data directory. --p2pBootstrap.queryForIp ($P2P_QUERY_FOR_IP) If announceUdpAddress or announceTcpAddress are not provided, query for the IP address of the machine. Default is false. TELEMETRY --tel.metricsCollectorUrl ($OTEL_EXPORTER_OTLP_METRICS_ENDPOINT) The URL of the telemetry collector for metrics --tel.tracesCollectorUrl ($OTEL_EXPORTER_OTLP_TRACES_ENDPOINT) The URL of the telemetry collector for traces --tel.logsCollectorUrl ($OTEL_EXPORTER_OTLP_LOGS_ENDPOINT) The URL of the telemetry collector for logs --tel.otelCollectIntervalMs (default: 60000) ($OTEL_COLLECT_INTERVAL_MS) The interval at which to collect metrics --tel.otelExportTimeoutMs (default: 30000) ($OTEL_EXPORT_TIMEOUT_MS) The timeout for exporting metrics --tel.otelExcludeMetrics (default: ) ($OTEL_EXCLUDE_METRICS) A list of metric prefixes to exclude from export --tel.otelIncludeMetrics (default: ) ($OTEL_INCLUDE_METRICS) A list of metric prefixes to include in export (ignored if OTEL_EXCLUDE_METRICS is set) --tel.publicMetricsCollectorUrl ($PUBLIC_OTEL_EXPORTER_OTLP_METRICS_ENDPOINT) A URL to publish a subset of metrics for public consumption --tel.publicMetricsCollectFrom (default: ) ($PUBLIC_OTEL_COLLECT_FROM) The role types to collect metrics from --tel.publicIncludeMetrics (default: ) ($PUBLIC_OTEL_INCLUDE_METRICS) A list of metric prefixes to publicly export --tel.publicMetricsOptOut (default: true) ($PUBLIC_OTEL_OPT_OUT) Whether to opt out of sharing optional telemetry BOT --bot Starts Aztec Bot with options --bot.nodeUrl ($AZTEC_NODE_URL) The URL to the Aztec node to check for tx pool status. --bot.nodeAdminUrl ($AZTEC_NODE_ADMIN_URL) The URL to the Aztec node admin API to force-flush txs if configured. --bot.l1Mnemonic ($BOT_L1_MNEMONIC) The mnemonic for the account to bridge fee juice from L1. --bot.l1PrivateKey ($BOT_L1_PRIVATE_KEY) The private key for the account to bridge fee juice from L1. --bot.l1ToL2MessageTimeoutSeconds (default: 3600) ($BOT_L1_TO_L2_TIMEOUT_SECONDS) How long to wait for L1 to L2 messages to become available on L2 --bot.senderPrivateKey ($BOT_PRIVATE_KEY) Signing private key for the sender account. --bot.senderSalt ($BOT_ACCOUNT_SALT) The salt to use to deploy the sender account. --bot.tokenSalt (default: 0x0000000000000000000000000000000000000000000000000000000000000001)($BOT_TOKEN_SALT) The salt to use to deploy the token contract. --bot.txIntervalSeconds (default: 60) ($BOT_TX_INTERVAL_SECONDS) Every how many seconds should a new tx be sent. --bot.privateTransfersPerTx (default: 1) ($BOT_PRIVATE_TRANSFERS_PER_TX) How many private token transfers are executed per tx. --bot.publicTransfersPerTx (default: 1) ($BOT_PUBLIC_TRANSFERS_PER_TX) How many public token transfers are executed per tx. --bot.feePaymentMethod (default: fee_juice) ($BOT_FEE_PAYMENT_METHOD) How to handle fee payments. (Options: fee_juice) --bot.minFeePadding (default: 3) ($BOT_MIN_FEE_PADDING) How much is the bot willing to overpay vs. the current base fee --bot.noStart ($BOT_NO_START) True to not automatically setup or start the bot on initialization. --bot.txMinedWaitSeconds (default: 180) ($BOT_TX_MINED_WAIT_SECONDS) How long to wait for a tx to be mined before reporting an error. --bot.followChain (default: NONE) ($BOT_FOLLOW_CHAIN) Which chain the bot follows --bot.maxPendingTxs (default: 128) ($BOT_MAX_PENDING_TXS) Do not send a tx if the node's tx pool already has this many pending txs. --bot.flushSetupTransactions ($BOT_FLUSH_SETUP_TRANSACTIONS) Make a request for the sequencer to build a block after each setup transaction. --bot.l2GasLimit ($BOT_L2_GAS_LIMIT) L2 gas limit for the tx (empty to let the bot's wallet estimate). --bot.daGasLimit ($BOT_DA_GAS_LIMIT) DA gas limit for the tx (empty to let the bot's wallet estimate). --bot.contract (default: TokenContract) ($BOT_TOKEN_CONTRACT) Token contract to use --bot.maxConsecutiveErrors ($BOT_MAX_CONSECUTIVE_ERRORS) The maximum number of consecutive errors before the bot shuts down --bot.stopWhenUnhealthy ($BOT_STOP_WHEN_UNHEALTHY) Stops the bot if service becomes unhealthy --bot.botMode (default: transfer) ($BOT_MODE) Bot mode: transfer, amm, or crosschain --bot.l2ToL1MessagesPerTx (default: 1) ($BOT_L2_TO_L1_MESSAGES_PER_TX) Number of L2→L1 messages per tx (crosschain mode) --bot.l1ToL2SeedCount (default: 1) ($BOT_L1_TO_L2_SEED_COUNT) Max L1→L2 messages to keep in-flight (crosschain mode) PXE --pxe.l2BlockBatchSize (default: 50) ($PXE_L2_BLOCK_BATCH_SIZE) Maximum amount of blocks to pull from the stream in one request when synchronizing --pxe.proverEnabled (default: true) ($PXE_PROVER_ENABLED) Enable real proofs --pxe.syncChainTip (default: proposed) ($PXE_SYNC_CHAIN_TIP) Which chain tip to sync to (proposed, checkpointed, proven, finalized) --pxe.nodeUrl ($AZTEC_NODE_URL) Custom Aztec Node URL to connect to TXE --txe Starts Aztec TXE with options ``` --- # Ethereum RPC call reference This guide provides a comprehensive reference of Ethereum RPC calls used by different Aztec node components. Understanding these calls helps with infrastructure planning, monitoring, and debugging. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before proceeding, you should: * Understand how Aztec nodes interact with Ethereum L1 * Be familiar with Ethereum JSON-RPC API specifications * Have basic knowledge of the viem library (Aztec's Ethereum client library) ## Overview[​](#overview "Direct link to Overview") Aztec nodes interact with Ethereum L1 through the [viem](https://viem.sh) library, which provides a type-safe interface to Ethereum JSON-RPC methods. Different node components make different RPC calls based on their responsibilities: * **Archiver**: Monitors L1 for new blocks and events * **Sequencer**: Proposes blocks and submits them to L1 * **Prover**: Submits proofs to L1 * **Validator**: Reads L1 state for validation * **Slasher**: Monitors for misbehavior and submits slashing payloads ## RPC call mapping[​](#rpc-call-mapping "Direct link to RPC call mapping") This table shows the Ethereum JSON-RPC calls used by Aztec nodes: | Ethereum RPC Call | Description | | --------------------------- | ---------------------------- | | `eth_getBlockByNumber` | Retrieve block information | | `eth_blockNumber` | Get latest block number | | `eth_getTransactionByHash` | Get transaction details | | `eth_getTransactionReceipt` | Get transaction receipt | | `eth_getTransactionCount` | Get account nonce | | `eth_getLogs` | Retrieve event logs | | `eth_getBalance` | Get account ETH balance | | `eth_getCode` | Get contract bytecode | | `eth_getStorageAt` | Read contract storage slot | | `eth_chainId` | Get chain identifier | | `eth_estimateGas` | Estimate gas for transaction | | `eth_call` | Execute read-only call | | `eth_sendRawTransaction` | Broadcast signed transaction | | `eth_gasPrice` | Get current gas price | | `eth_maxPriorityFeePerGas` | Get priority fee (EIP-1559) | ## Archiver node[​](#archiver-node "Direct link to Archiver node") The archiver continuously monitors L1 for new blocks and retrieves historical data. ### Block retrieval[​](#block-retrieval "Direct link to Block retrieval") **Purpose**: Sync L2 block data published to L1 **RPC calls used**: * `eth_blockNumber` - Get latest L1 block number * `eth_getLogs` - Retrieve rollup contract events * `eth_getBlockByNumber` - Get block timestamps and metadata **Example RPC calls**: ``` // eth_blockNumber {"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1} // eth_getLogs {"jsonrpc":"2.0","method":"eth_getLogs","params":[{ "fromBlock":"0x100", "toBlock":"0x200", "address":"0x...", "topics":["0x..."] }],"id":2} // eth_getBlockByNumber {"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["0x100",false],"id":3} ``` ### L1 to L2 message retrieval[​](#l1-to-l2-message-retrieval "Direct link to L1 to L2 message retrieval") **Purpose**: Track messages sent from L1 to L2 **RPC calls used**: * `eth_getLogs` - Retrieve `MessageSent` events from Inbox contract ### Contract event monitoring[​](#contract-event-monitoring "Direct link to Contract event monitoring") **Purpose**: Monitor contract deployments and updates **RPC calls used**: * `eth_getLogs` - Retrieve events from ClassRegistry and InstanceRegistry **Events monitored**: * `ContractClassPublished` * `ContractInstancePublished` * `ContractInstanceUpdated` * `PrivateFunctionBroadcasted` * `UtilityFunctionBroadcasted` ## Sequencer node[​](#sequencer-node "Direct link to Sequencer node") Sequencers propose blocks and submit them to L1, they also read L1 state to validate blocks and participate in consensus. ### Transaction broadcasting[​](#transaction-broadcasting "Direct link to Transaction broadcasting") **Purpose**: Submit block proposals to L1 **RPC calls used**: * `eth_getTransactionCount` - Get nonce for sender account * `eth_estimateGas` - Estimate gas for proposal transaction * `eth_sendRawTransaction` - Broadcast signed transaction * `eth_getTransactionReceipt` - Verify transaction inclusion **Example RPC calls**: ``` // eth_getTransactionCount {"jsonrpc":"2.0","method":"eth_getTransactionCount","params":["0x...","latest"],"id":1} // eth_estimateGas {"jsonrpc":"2.0","method":"eth_estimateGas","params":[{ "from":"0x...", "to":"0x...", "data":"0x..." }],"id":2} // eth_sendRawTransaction {"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x..."],"id":3} // eth_getTransactionReceipt {"jsonrpc":"2.0","method":"eth_getTransactionReceipt","params":["0x..."],"id":4} ``` ### State reading[​](#state-reading "Direct link to State reading") **Purpose**: Read rollup state and validate proposals **RPC calls used**: * `eth_call` - Read contract state * `eth_getStorageAt` - Read specific storage slots * `eth_blockNumber` - Get current L1 block for validation context * `eth_getBlockByNumber` - Get block timestamps **Example RPC calls**: ``` // eth_call {"jsonrpc":"2.0","method":"eth_call","params":[{ "to":"0x...", "data":"0x..." },"latest"],"id":1} // eth_getStorageAt {"jsonrpc":"2.0","method":"eth_getStorageAt","params":["0x...","0x0","latest"],"id":2} ``` ### Gas management[​](#gas-management "Direct link to Gas management") **Purpose**: Monitor gas prices and publisher account balances **RPC calls used**: * `eth_getBalance` - Check publisher account balance * `eth_gasPrice` / `eth_maxPriorityFeePerGas` - Get current gas prices ### Block simulation[​](#block-simulation "Direct link to Block simulation") **Purpose**: Validate block proposals before submission **RPC calls used**: * `eth_call` - Simulate contract call to validate proposals ## Prover node[​](#prover-node "Direct link to Prover node") The prover submits validity proofs to L1. ### Proof submission[​](#proof-submission "Direct link to Proof submission") **Purpose**: Submit epoch proofs to the Rollup contract **RPC calls used**: * `eth_getTransactionCount` - Get nonce for prover publisher * `eth_estimateGas` - Estimate gas for proof submission * `eth_sendRawTransaction` - Broadcast proof transaction * `eth_getTransactionReceipt` - Confirm proof inclusion **Note**: Uses the same transaction flow as sequencer broadcasting ### Chain state monitoring[​](#chain-state-monitoring "Direct link to Chain state monitoring") **Purpose**: Track L1 state for attestation validation **RPC calls used**: * `eth_getBlockByNumber` - Get L1 timestamps for epoch calculations * `eth_chainId` - Verify connected to correct chain ## Slasher node[​](#slasher-node "Direct link to Slasher node") The slasher monitors for validator misbehavior and submits slashing payloads. ### Misbehavior detection[​](#misbehavior-detection "Direct link to Misbehavior detection") **Purpose**: Monitor for slashable offenses and create slash payloads **RPC calls used**: * `eth_getLogs` - Retrieve rollup events for analysis * `eth_getBlockByNumber` - Get block timestamps for slashing proofs * `eth_call` - Read validator state ### Slashing payload submission[​](#slashing-payload-submission "Direct link to Slashing payload submission") **Purpose**: Submit slash payloads to L1 **RPC calls used**: * `eth_getTransactionCount` - Get nonce for slasher account * `eth_sendRawTransaction` - Broadcast slashing transaction * `eth_getTransactionReceipt` - Verify slash transaction inclusion **Note**: Uses the same transaction flow as sequencer broadcasting ## Shared infrastructure[​](#shared-infrastructure "Direct link to Shared infrastructure") Aztec provides shared transaction management utilities for all components that submit to L1. ### Core functionality[​](#core-functionality "Direct link to Core functionality") **RPC calls used**: * `eth_getTransactionCount` - Nonce management * `eth_estimateGas` - Gas estimation * `eth_gasPrice` / `eth_maxPriorityFeePerGas` - Gas pricing (EIP-1559) * `eth_sendRawTransaction` - Transaction broadcasting * `eth_getTransactionReceipt` - Transaction status checking * `eth_getTransactionByHash` - Transaction lookup for replacement * `eth_getBlockByNumber` - Block timestamp for timeout checks * `eth_getBalance` - Publisher balance monitoring ### Transaction lifecycle[​](#transaction-lifecycle "Direct link to Transaction lifecycle") 1. **Preparation**: Estimate gas and get gas price 2. **Nonce management**: Get and track nonce via `NonceManager` 3. **Signing**: Sign transaction with keystore 4. **Broadcasting**: Send via `eth_sendRawTransaction` 5. **Monitoring**: Poll with `eth_getTransactionReceipt` 6. **Replacement**: Replace stuck transactions if needed 7. **Cancellation**: Send zero-value transaction to cancel ## RPC endpoint configuration[​](#rpc-endpoint-configuration "Direct link to RPC endpoint configuration") ### Environment variables[​](#environment-variables "Direct link to Environment variables") Configure L1 RPC endpoints using: ``` # Single endpoint ETHEREUM_HOSTS=https://eth-mainnet.example.com # Multiple endpoints (fallback) ETHEREUM_HOSTS=https://eth-mainnet-1.example.com,https://eth-mainnet-2.example.com # Consensus endpoints for archiver L1_CONSENSUS_HOST_URLS=https://beacon-node.example.com ``` ### Fallback configuration[​](#fallback-configuration "Direct link to Fallback configuration") Aztec automatically retries failed requests on alternative endpoints when multiple RPC URLs are configured. This provides reliability and redundancy for critical operations. ## Monitoring and debugging[​](#monitoring-and-debugging "Direct link to Monitoring and debugging") ### RPC call logging[​](#rpc-call-logging "Direct link to RPC call logging") Enable detailed RPC logging: ``` LOG_LEVEL="debug; info: json-rpc, simulator" # or verbose ``` Look for log entries related to: * Transaction lifecycle and nonce management * Block sync and event retrieval * Block proposal submissions * Contract interactions ### Common issues[​](#common-issues "Direct link to Common issues") **Issue**: `eth_getLogs` query exceeds limits **Solution**: * Reduce block range in queries * Use archive node with higher limits * Implement chunked log retrieval **Issue**: Transaction replacement failures **Solution**: * Ensure `eth_getTransactionCount` returns consistent nonces * Configure appropriate gas price bumps * Monitor transaction pool status **Issue**: Stale state reads **Solution**: * Use specific block tags (not `latest`) * Disable caching with `cacheTime: 0` * Ensure RPC node is fully synced ## Next steps[​](#next-steps "Direct link to Next steps") * Review [How to Run a Sequencer Node](/operate/testnet/operators/setup/sequencer_management.md) for operational guidance * Explore [Advanced Keystore Patterns](/operate/testnet/operators/keystore/advanced-patterns.md) for complex key management * Check [Useful Commands](/operate/testnet/operators/sequencer-management/useful-commands.md) for monitoring tools * Join the [Aztec Discord](https://discord.gg/aztec) for infrastructure support --- # Glossary This glossary defines key terms used throughout the Aztec network documentation. Terms are organized alphabetically with cross-references to related concepts. ## A[​](#a "Direct link to A") ### Agent[​](#agent "Direct link to Agent") See [Prover Agent](#prover-agent). ### Archiver[​](#archiver "Direct link to Archiver") A component that monitors Ethereum L1 for rollup events and synchronizes L2 state. The archiver retrieves block data, contract deployments, and L1-to-L2 messages from the data availability layer. ### Attestation[​](#attestation "Direct link to Attestation") A cryptographic signature from a sequencer committee member confirming the validity of a proposed block. Blocks require attestations from two-thirds of the committee plus one before submission to L1. ### Attester[​](#attester "Direct link to Attester") The identity of a sequencer node in the network. The Ethereum address derived from the attester private key uniquely identifies the sequencer and is used to sign block proposals and attestations. ## B[​](#b "Direct link to B") ### BIP44[​](#bip44 "Direct link to BIP44") Bitcoin Improvement Proposal 44 defines a standard derivation path for hierarchical deterministic wallets. Aztec uses BIP44 to derive multiple Ethereum addresses from a single mnemonic seed phrase. ### Block Proposal[​](#block-proposal "Direct link to Block Proposal") A candidate block assembled by a sequencer containing ordered transactions. Proposals must be validated by the sequencer committee before submission to L1. ### Bootnode[​](#bootnode "Direct link to Bootnode") A network node that facilitates peer discovery by maintaining lists of active peers. New nodes connect to bootnodes to discover and join the P2P network. ### Broker[​](#broker "Direct link to Broker") See [Prover Broker](#prover-broker). ## C[​](#c "Direct link to C") ### Coinbase[​](#coinbase "Direct link to Coinbase") The Ethereum address that receives L1 rewards and fees for a sequencer. If not specified in the keystore, defaults to the attester address. ### Committee[​](#committee "Direct link to Committee") See [Sequencer Committee](#sequencer-committee). ### Consensus[​](#consensus "Direct link to Consensus") The process by which sequencer nodes agree on the validity of proposed blocks through attestations and signatures. ### Contract Class[​](#contract-class "Direct link to Contract Class") A published smart contract definition containing bytecode and function signatures. Multiple contract instances can be deployed from a single contract class. ### Contract Instance[​](#contract-instance "Direct link to Contract Instance") A deployed instance of a contract class with a unique address and storage state. ## D[​](#d "Direct link to D") ### Data Availability[​](#data-availability "Direct link to Data Availability") The guarantee that block data is accessible to network participants. Aztec publishes data to Ethereum L1 to ensure data availability for state reconstruction. ### Derivation Path[​](#derivation-path "Direct link to Derivation Path") A hierarchical path used to derive cryptographic keys from a master seed. Follows the BIP44 standard for deterministic key generation. ## E[​](#e "Direct link to E") ### EIP-1559[​](#eip-1559 "Direct link to EIP-1559") Ethereum Improvement Proposal 1559 introduces a base fee mechanism for transaction pricing. Aztec nodes use EIP-1559 gas pricing when submitting transactions to L1. ### ENR (Ethereum Node Record)[​](#enr-ethereum-node-record "Direct link to ENR (Ethereum Node Record)") A signed record containing information about a network node, used for peer discovery in the P2P network. Bootnodes share their ENR for other nodes to connect. ### Epoch[​](#epoch "Direct link to Epoch") A period of multiple L2 blocks that are proven together. Prover nodes generate a single validity proof for an entire epoch and submit it to the rollup contract. ### Execution Layer[​](#execution-layer "Direct link to Execution Layer") The Ethereum L1 execution client (e.g., Geth, Nethermind) that processes transactions. Aztec nodes require access to an execution layer RPC endpoint. ## F[​](#f "Direct link to F") ### Fee Recipient[​](#fee-recipient "Direct link to Fee Recipient") The Aztec address that receives unburnt transaction fees from blocks produced by a sequencer. Must be a deployed Aztec account. ### Full Node[​](#full-node "Direct link to Full Node") A node that maintains a complete copy of the Aztec blockchain state and provides RPC interfaces for users to interact with the network without relying on third parties. ## G[​](#g "Direct link to G") ### Gas Estimation[​](#gas-estimation "Direct link to Gas Estimation") The process of calculating the expected gas cost for an Ethereum transaction before submission. Aztec nodes estimate gas for L1 transactions like block proposals and proof submissions. ## I[​](#i "Direct link to I") ### Inbox[​](#inbox "Direct link to Inbox") The L1 contract that receives messages sent from Ethereum to Aztec L2. The archiver monitors the Inbox for new L1-to-L2 messages. ## J[​](#j "Direct link to J") ### JSON V3 Keystore[​](#json-v3-keystore "Direct link to JSON V3 Keystore") An Ethereum standard for encrypted key storage using AES-128-CTR encryption and scrypt key derivation. Aztec supports JSON V3 keystores for secure key management. ## K[​](#k "Direct link to K") ### Keystore[​](#keystore "Direct link to Keystore") A configuration file or encrypted store containing private keys for sequencer operations. Keystores define attester keys, publisher keys, coinbase addresses, and fee recipients. ## L[​](#l "Direct link to L") ### L1 (Layer 1)[​](#l1-layer-1 "Direct link to L1 (Layer 1)") Ethereum mainnet or testnet, serving as the base layer for Aztec's rollup. L1 provides data availability, settlement, and consensus for the L2. ### L2 (Layer 2)[​](#l2-layer-2 "Direct link to L2 (Layer 2)") The Aztec network, a rollup scaling solution built on top of Ethereum L1. L2 processes transactions offchain and submits validity proofs to L1. ### L1 Sync[​](#l1-sync "Direct link to L1 Sync") A synchronization mode where nodes reconstruct state by querying the rollup contract and data availability layer on Ethereum L1 directly. ## M[​](#m "Direct link to M") ### Mempool[​](#mempool "Direct link to Mempool") The pool of unprocessed transactions waiting to be included in a block. Sequencers select transactions from the mempool when proposing blocks. ### Merkle Tree[​](#merkle-tree "Direct link to Merkle Tree") A cryptographic data structure that enables efficient verification of data integrity and membership. Aztec uses Merkle trees for state commitments, note storage, and nullifier tracking. ### Mnemonic[​](#mnemonic "Direct link to Mnemonic") A human-readable seed phrase (typically 12 or 24 words) used to generate deterministic cryptographic keys. Follows BIP39 standard for encoding. ## N[​](#n "Direct link to N") ### Node[​](#node "Direct link to Node") A participant in the Aztec network. See [Full Node](#full-node), [Sequencer Node](#sequencer-node), [Prover Node](#prover-node), or [Bootnode](#bootnode). ### Nonce[​](#nonce "Direct link to Nonce") A sequential number used to order transactions from an Ethereum account. Aztec nodes manage nonces when submitting transactions to L1. ### Note Tree[​](#note-tree "Direct link to Note Tree") A Merkle tree containing encrypted notes representing private state in Aztec contracts. ### Nullifier[​](#nullifier "Direct link to Nullifier") A unique value that marks a note as consumed, preventing double-spending. Nullifiers are published to L1 and tracked in the nullifier tree. ## O[​](#o "Direct link to O") ### Outbox[​](#outbox "Direct link to Outbox") The L1 contract that receives messages sent from Aztec L2 to Ethereum. Used for withdrawals and cross-chain communication. ## P[​](#p "Direct link to P") ### P2P (Peer-to-Peer)[​](#p2p-peer-to-peer "Direct link to P2P (Peer-to-Peer)") The network protocol used by Aztec nodes to discover peers, exchange transactions, and propagate blocks without central coordination. ### Proof-of-Stake[​](#proof-of-stake "Direct link to Proof-of-Stake") The consensus mechanism where sequencers lock collateral (stake) to participate in block production. Misbehavior results in stake slashing. ### Prover Agent[​](#prover-agent "Direct link to Prover Agent") A stateless worker that executes proof generation jobs. Multiple agents can run in parallel to distribute proving workload. ### Prover Broker[​](#prover-broker "Direct link to Prover Broker") A coordinator that manages the prover job queue, distributing work to agents and collecting results. ### Prover Node[​](#prover-node "Direct link to Prover Node") Infrastructure that generates validity proofs for epochs of L2 blocks. Consists of a prover node coordinator, broker, and one or more agents. ### Publisher[​](#publisher "Direct link to Publisher") The Ethereum account used by a sequencer to submit block proposals to L1. Must be funded with ETH to pay gas fees. If not specified, the attester key is used. ### PXE (Private Execution Environment)[​](#pxe-private-execution-environment "Direct link to PXE (Private Execution Environment)") The client-side component that executes private functions, manages user keys, and constructs privacy-preserving transactions. ## R[​](#r "Direct link to R") ### Registry[​](#registry "Direct link to Registry") The L1 contract that tracks deployed contract classes and instances. The archiver monitors Registry events to maintain a database of available contracts. ### Remote Signer[​](#remote-signer "Direct link to Remote Signer") An external service (e.g., Web3Signer) that stores private keys and signs transactions remotely. Used for enhanced security in production deployments. ### Rollup[​](#rollup "Direct link to Rollup") A scaling solution that processes transactions offchain and submits compressed data and validity proofs to L1. Aztec is a zkRollup with privacy features. ### RPC (Remote Procedure Call)[​](#rpc-remote-procedure-call "Direct link to RPC (Remote Procedure Call)") A protocol for remote communication. Aztec nodes expose JSON-RPC interfaces for client interaction and use RPC to communicate with Ethereum L1. ## S[​](#s "Direct link to S") ### Sequencer Committee[​](#sequencer-committee "Direct link to Sequencer Committee") A rotating group of validators responsible for validating proposed blocks through attestations during a specific time period. ### Sequencer Node[​](#sequencer-node "Direct link to Sequencer Node") A validator that assembles transactions into blocks, executes public functions, and participates in consensus through attestations. ### Slashing[​](#slashing "Direct link to Slashing") The penalty mechanism that reduces or confiscates a sequencer's stake for provable misbehavior such as double-signing or prolonged downtime. ### Slasher Node[​](#slasher-node "Direct link to Slasher Node") Infrastructure that monitors for validator misbehavior and submits slashing payloads to L1 when violations are detected. ### Snapshot[​](#snapshot "Direct link to Snapshot") A pre-built database containing blockchain state at a specific block height. Nodes can download snapshots for faster synchronization. ### Snapshot Sync[​](#snapshot-sync "Direct link to Snapshot Sync") A synchronization mode where nodes download pre-built state snapshots instead of reconstructing state from L1. Significantly faster than L1 sync. ### Stake[​](#stake "Direct link to Stake") Collateral locked by a sequencer to participate in block production. Higher stake increases selection probability as block proposer. ### State Tree[​](#state-tree "Direct link to State Tree") A Merkle tree representing the current world state of all Aztec contracts and accounts. ## T[​](#t "Direct link to T") ### Transaction Receipt[​](#transaction-receipt "Direct link to Transaction Receipt") A record of a transaction's execution on Ethereum, including status, gas used, and emitted events. Aztec nodes poll for receipts to confirm L1 transaction inclusion. ## V[​](#v "Direct link to V") ### Validator[​](#validator "Direct link to Validator") See [Sequencer Node](#sequencer-node). The terms are used interchangeably in Aztec documentation. ### Viem[​](#viem "Direct link to Viem") A TypeScript library providing type-safe interfaces to Ethereum JSON-RPC methods. Aztec nodes use viem for all L1 interactions. ## W[​](#w "Direct link to W") ### Web3Signer[​](#web3signer "Direct link to Web3Signer") An open-source remote signing service that stores keys securely and provides signing APIs. Commonly used for production sequencer deployments. ### World State[​](#world-state "Direct link to World State") The complete state of the Aztec network at a given block height, including all contract storage, notes, and nullifiers. ## Related Resources[​](#related-resources "Direct link to Related Resources") * [Node API Reference](/operate/testnet/operators/reference/node_api_reference.md) - Complete API documentation for node JSON-RPC methods * [Ethereum RPC Reference](/operate/testnet/operators/reference/ethereum_rpc_reference.md) - L1 RPC calls used by Aztec components * [Advanced Keystore Guide](/operate/testnet/operators/keystore.md) - Detailed keystore configuration options * [CLI Reference](/operate/testnet/operators/reference/cli-reference.md) - Complete command-line interface documentation --- # Node JSON RPC API reference This document provides a complete reference for the Aztec Node JSON RPC API. All methods are exposed via JSON RPC on the node's configured ports. ## API endpoint[​](#api-endpoint "Direct link to API endpoint") **Public RPC URL**: `http://localhost:8080` **Admin URL**: `http://localhost:8880` Note that the above ports are only defaults, and can be modified by setting `--port` and `--admin-port` flags upon startup. All methods use standard JSON RPC 2.0 format with methods prefixed by `aztec_` or `aztecAdmin_`. ## Block queries[​](#block-queries "Direct link to Block queries") ### aztec\_getBlockNumber[​](#aztec_getblocknumber "Direct link to aztec_getBlockNumber") Returns the block number at a given chain tip, or the latest proposed block number when `tip` is omitted. **Parameters**: 1. `tip` - `ChainTip | undefined` **Returns**: `number` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getBlockNumber","params":["0x1234..."],"id":1}' ``` ### aztec\_getCheckpointNumber[​](#aztec_getcheckpointnumber "Direct link to aztec_getCheckpointNumber") Returns the checkpoint number at a given chain tip, or the latest checkpoint number when `tip` is omitted. **Remarks**: **Semantic foot-gun**: block-side `'proposed'` means "latest proposed block" (chain head), but checkpoint-side `'proposed'` means "latest confirmed checkpoint" — pre-L1-confirm checkpoints are not exposed over RPC. `'checkpointed'` on the checkpoint side is equivalent. **Parameters**: 1. `tip` - `ChainTip | undefined` **Returns**: `number` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getCheckpointNumber","params":["0x1234..."],"id":1}' ``` ### aztec\_getChainTips[​](#aztec_getchaintips "Direct link to aztec_getChainTips") Returns the tips of the L2 chain. **Parameters**: None **Returns**: `ChainTips` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getChainTips","params":[],"id":1}' ``` ### aztec\_getL1Constants[​](#aztec_getl1constants "Direct link to aztec_getL1Constants") Returns the rollup constants for the current chain. **Parameters**: None **Returns**: `L1RollupConstants` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getL1Constants","params":[],"id":1}' ``` ### aztec\_getSyncedL2SlotNumber[​](#aztec_getsyncedl2slotnumber "Direct link to aztec_getSyncedL2SlotNumber") Returns the last L2 slot number for which the node has all L1 data needed to build the next checkpoint. **Parameters**: None **Returns**: `SlotNumber | undefined` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getSyncedL2SlotNumber","params":[],"id":1}' ``` ### aztec\_getSyncedL2EpochNumber[​](#aztec_getsyncedl2epochnumber "Direct link to aztec_getSyncedL2EpochNumber") Returns the last L2 epoch number that has been fully synchronized from L1. **Parameters**: None **Returns**: `number | undefined` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getSyncedL2EpochNumber","params":[],"id":1}' ``` ### aztec\_getSyncedL1Timestamp[​](#aztec_getsyncedl1timestamp "Direct link to aztec_getSyncedL1Timestamp") Returns the latest L1 timestamp according to the archiver's synced L1 view. **Parameters**: None **Returns**: `bigint | undefined` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getSyncedL1Timestamp","params":[],"id":1}' ``` ### aztec\_getBlock[​](#aztec_getblock "Direct link to aztec_getBlock") Unified block fetch. Returns the block identified by `param`, with optional fields controlled by `options`. **Parameters**: 1. `param` - `BlockHash | number | "latest"` - A block number, block hash, archive root, chain-tip name, or object variant. 2. `options` - `BlockIncludeOptions | undefined` - Narrowing options: `includeTransactions`, `includeL1PublishInfo`, `includeAttestations`. **Returns**: `BlockResponse | undefined` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getBlock","params":["latest","0x1234..."],"id":1}' ``` ### aztec\_getBlockData[​](#aztec_getblockdata "Direct link to aztec_getBlockData") Lightweight block-metadata fetch. Returns the block identified by `param` without transaction bodies or other optional context. Cheaper than `getBlock` for header-only access. **Parameters**: 1. `param` - `BlockHash | number | "latest"` - A block number, block hash, archive root, chain-tip name, or object variant. **Returns**: `BlockData | undefined` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getBlockData","params":["latest"],"id":1}' ``` ### aztec\_getBlocks[​](#aztec_getblocks "Direct link to aztec_getBlocks") Returns up to `limit` blocks starting from `from`, projected to the shape determined by `options`. **Parameters**: 1. `from` - `number` 2. `limit` - `number` 3. `options` - `BlocksIncludeOptions | undefined` **Returns**: `BlockResponse[]` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getBlocks","params":[1,100,"0x1234..."],"id":1}' ``` ### aztec\_getCheckpoint[​](#aztec_getcheckpoint "Direct link to aztec_getCheckpoint") Unified checkpoint fetch. Returns the checkpoint identified by `param`, with optional fields controlled by `options`. **Parameters**: 1. `param` - `CheckpointParameter` 2. `options` - `CheckpointIncludeOptions | undefined` **Returns**: `CheckpointResponse | undefined` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getCheckpoint","params":["0x1234...","0x1234..."],"id":1}' ``` ### aztec\_getCheckpoints[​](#aztec_getcheckpoints "Direct link to aztec_getCheckpoints") Returns up to `limit` checkpoints starting from `from`, projected to the shape determined by `options`. **Parameters**: 1. `from` - `number` 2. `limit` - `number` 3. `options` - `CheckpointIncludeOptions | undefined` **Returns**: `CheckpointResponse[]` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getCheckpoints","params":[1,100,"0x1234..."],"id":1}' ``` ### aztec\_getCheckpointsData[​](#aztec_getcheckpointsdata "Direct link to aztec_getCheckpointsData") Gets lightweight checkpoint metadata for a contiguous range or for an entire epoch. **Parameters**: 1. `query` - `CheckpointsQuery` - Either `{ from, limit }` or `{ epoch }`. **Returns**: `CheckpointData[]` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getCheckpointsData","params":["0x1234..."],"id":1}' ``` ## Transaction operations[​](#transaction-operations "Direct link to Transaction operations") ### aztec\_sendTx[​](#aztec_sendtx "Direct link to aztec_sendTx") Method to submit a transaction to the p2p pool. **Parameters**: 1. `tx` - `Tx` - The transaction to be submitted. **Returns**: `void` - Nothing. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_sendTx","params":[{"data":"0x..."}],"id":1}' ``` ### aztec\_getTxReceipt[​](#aztec_gettxreceipt "Direct link to aztec_getTxReceipt") Fetches a transaction receipt for a given transaction hash. Always resolves to one of the lifecycle variants of the union: a if the tx was included in a block, a if it's still in the mempool of the connected Aztec node, or a if not found. **Parameters**: 1. `txHash` - `TxHash` - The transaction hash. 2. `options` - `GetTxReceiptOptions | undefined` - Optional flags controlling which extra data is attached: `includeTxEffect` attaches the full to a mined receipt, `includePendingTx` attaches the pending to a pending receipt, and `includeProof` keeps the proof on that attached pending tx (only meaningful with `includePendingTx`). **Returns**: `TxReceipt` - A receipt of the transaction. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getTxReceipt","params":["0x1234...","0x1234..."],"id":1}' ``` ### aztec\_getTxEffect[​](#aztec_gettxeffect "Direct link to aztec_getTxEffect") Gets a tx effect. **Deprecated**: Use `getTxReceipt(txHash, { includeTxEffect: true })` and read the `.txEffect` field instead. **Parameters**: 1. `txHash` - `TxHash` - The hash of the tx corresponding to the tx effect. **Returns**: `IndexedTxEffect | undefined` - The requested tx effect with block info (or undefined if not found). **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getTxEffect","params":["0x1234..."],"id":1}' ``` ### aztec\_getTxByHash[​](#aztec_gettxbyhash "Direct link to aztec_getTxByHash") Method to retrieve a single pending tx. The tx's proof is stripped unless `includeProof` is set. **Parameters**: 1. `txHash` - `TxHash` - The transaction hash to return. 2. `options` - `GetTxByHashOptions | undefined` - Options for the returned tx (eg whether to include its proof). **Returns**: `Tx | undefined` - The pending tx if it exists. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getTxByHash","params":["0x1234...","0x1234..."],"id":1}' ``` ### aztec\_getTxsByHash[​](#aztec_gettxsbyhash "Direct link to aztec_getTxsByHash") Method to retrieve multiple pending txs. The txs' proofs are stripped unless `includeProof` is set. **Parameters**: 1. `txHashes` - `TxHash[]` 2. `options` - `GetTxByHashOptions | undefined` - Options for the returned txs (eg whether to include their proofs). **Returns**: `Tx[]` - The pending txs if exist. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getTxsByHash","params":[["0x1234..."],"0x1234..."],"id":1}' ``` ### aztec\_getPendingTxs[​](#aztec_getpendingtxs "Direct link to aztec_getPendingTxs") Method to retrieve pending txs. The txs' proofs are stripped unless `includeProof` is set. **Parameters**: 1. `limit` - `number | undefined` - The number of items to return. 2. `after` - `TxHash | undefined` - The last known pending tx. Used for pagination. 3. `options` - `GetTxByHashOptions | undefined` - Options for the returned txs (eg whether to include their proofs). **Returns**: `Tx[]` - The pending txs. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getPendingTxs","params":[100,"0x1234...","0x1234..."],"id":1}' ``` ### aztec\_getPendingTxCount[​](#aztec_getpendingtxcount "Direct link to aztec_getPendingTxCount") Retrieves the number of pending txs **Parameters**: None **Returns**: `number` - The number of pending txs. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getPendingTxCount","params":[],"id":1}' ``` ### aztec\_isValidTx[​](#aztec_isvalidtx "Direct link to aztec_isValidTx") Returns true if the transaction is valid for inclusion at the current state. Valid transactions can be made invalid by *other* transactions if e.g. they emit the same nullifiers, or come become invalid due to e.g. the expiration\_timestamp property. **Parameters**: 1. `tx` - `Tx` - The transaction to validate for correctness. 2. `options` - `object | undefined` **Returns**: `TxValidationResult` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_isValidTx","params":[{"data":"0x..."},{}],"id":1}' ``` ### aztec\_simulatePublicCalls[​](#aztec_simulatepubliccalls "Direct link to aztec_simulatePublicCalls") Simulates the public part of a transaction with the current state. This currently just checks that the transaction execution succeeds. **Parameters**: 1. `tx` - `Tx` - The transaction to simulate. 2. `skipFeeEnforcement` - `boolean | undefined` - If true, fee enforcement is skipped. 3. `overrides` - `SimulationOverrides | undefined` - Optional pre-simulation overrides applied to the ephemeral fork and contract DB (publicStorage writes, contract instance overrides). **Returns**: `PublicSimulationOutput` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_simulatePublicCalls","params":[{"data":"0x..."},true,"0x1234..."],"id":1}' ``` ## State queries[​](#state-queries "Direct link to State queries") ### aztec\_getPublicStorageAt[​](#aztec_getpublicstorageat "Direct link to aztec_getPublicStorageAt") Gets the storage value at the given contract storage slot. **Remarks**: The storage slot here refers to the slot as it is defined in Noir not the index in the merkle tree. Aztec's version of `eth_getStorageAt`. **Parameters**: 1. `referenceBlock` - `BlockHash | number | "latest"` - The block parameter (block number, block hash, or 'latest') at which to get the data. 2. `contract` - `AztecAddress` - Address of the contract to query. 3. `slot` - `Fr` - Slot to query. **Returns**: `Fr` - Storage value at the given contract slot. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getPublicStorageAt","params":["latest","0x1234...","0x1234..."],"id":1}' ``` ### aztec\_getWorldStateSyncStatus[​](#aztec_getworldstatesyncstatus "Direct link to aztec_getWorldStateSyncStatus") Returns the sync status of the node's world state **Parameters**: None **Returns**: `WorldStateSyncStatus` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getWorldStateSyncStatus","params":[],"id":1}' ``` ## Membership witnesses[​](#membership-witnesses "Direct link to Membership witnesses") ### aztec\_findLeavesIndexes[​](#aztec_findleavesindexes "Direct link to aztec_findLeavesIndexes") Find the indexes of the given leaves in the given tree along with a block metadata pointing to the block in which the leaves were inserted. **Parameters**: 1. `referenceBlock` - `BlockHash | number | "latest"` - The block parameter (block number, block hash, or 'latest') at which to get the data. 2. `treeId` - `MerkleTreeId` - The tree to search in. 3. `leafValues` - `Fr[]` - The values to search for. **Returns**: `(DataInBlock | undefined)[]` - The indices of leaves and the block metadata of a block in which the leaves were inserted. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_findLeavesIndexes","params":["latest",1,["0x1234..."]],"id":1}' ``` ### aztec\_getNullifierMembershipWitness[​](#aztec_getnullifiermembershipwitness "Direct link to aztec_getNullifierMembershipWitness") Returns a nullifier membership witness for a given nullifier at a given block. **Parameters**: 1. `referenceBlock` - `BlockHash | number | "latest"` - The block parameter (block number, block hash, or 'latest') at which to get the data. 2. `nullifier` - `Fr` - Nullifier we try to find witness for. **Returns**: `NullifierMembershipWitness | undefined` - The nullifier membership witness (if found). **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getNullifierMembershipWitness","params":["latest","0x1234..."],"id":1}' ``` ### aztec\_getLowNullifierMembershipWitness[​](#aztec_getlownullifiermembershipwitness "Direct link to aztec_getLowNullifierMembershipWitness") Returns a low nullifier membership witness for a given nullifier at a given block. **Remarks**: Low nullifier witness can be used to perform a nullifier non-inclusion proof by leveraging the "linked list structure" of leaves and proving that a lower nullifier is pointing to a bigger next value than the nullifier we are trying to prove non-inclusion for. **Parameters**: 1. `referenceBlock` - `BlockHash | number | "latest"` - The block parameter (block number, block hash, or 'latest') at which to get the data. 2. `nullifier` - `Fr` - Nullifier we try to find the low nullifier witness for. **Returns**: `NullifierMembershipWitness | undefined` - The low nullifier membership witness (if found). **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getLowNullifierMembershipWitness","params":["latest","0x1234..."],"id":1}' ``` ### aztec\_getPublicDataWitness[​](#aztec_getpublicdatawitness "Direct link to aztec_getPublicDataWitness") Returns a public data tree witness for a given leaf slot at a given block. **Remarks**: The witness can be used to compute the current value of the public data tree leaf. If the low leaf preimage corresponds to an "in range" slot, means that the slot doesn't exist and the value is 0. If the low leaf preimage corresponds to the exact slot, the current value is contained in the leaf preimage. **Parameters**: 1. `referenceBlock` - `BlockHash | number | "latest"` - The block parameter (block number, block hash, or 'latest') at which to get the data. 2. `leafSlot` - `Fr` - The leaf slot we try to find the witness for. **Returns**: `PublicDataWitness | undefined` - The public data witness (if found). **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getPublicDataWitness","params":["latest","0x1234..."],"id":1}' ``` ### aztec\_getBlockHashMembershipWitness[​](#aztec_getblockhashmembershipwitness "Direct link to aztec_getBlockHashMembershipWitness") Returns a membership witness for a given block hash in the archive tree. 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 to the archive tree. This method finds the membership witness (leaf index and sibling path) for a given block hash, which can be used to prove that a specific block exists in the chain's history. **Parameters**: 1. `referenceBlock` - `BlockHash | number | "latest"` - The block parameter (block number, block hash, or 'latest') at which to get the data (which contains the root of the archive tree in which we are searching for the block hash). 2. `blockHash` - `BlockHash` - The block hash to find in the archive tree. **Returns**: `MembershipWitness | undefined` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getBlockHashMembershipWitness","params":["latest","0x1234..."],"id":1}' ``` ### aztec\_getNoteHashMembershipWitness[​](#aztec_getnotehashmembershipwitness "Direct link to aztec_getNoteHashMembershipWitness") Returns a membership witness for a given note hash at a given block. **Parameters**: 1. `referenceBlock` - `BlockHash | number | "latest"` - The block parameter (block number, block hash, or 'latest') at which to get the data. 2. `noteHash` - `Fr` - The note hash we try to find the witness for. **Returns**: `MembershipWitness | undefined` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getNoteHashMembershipWitness","params":["latest","0x1234..."],"id":1}' ``` ## L1 to L2 messages[​](#l1-to-l2-messages "Direct link to L1 to L2 messages") ### aztec\_getL1ToL2MessageMembershipWitness[​](#aztec_getl1tol2messagemembershipwitness "Direct link to aztec_getL1ToL2MessageMembershipWitness") Returns the index and a sibling path for a leaf in the committed l1 to l2 data tree. **Parameters**: 1. `referenceBlock` - `BlockHash | number | "latest"` - The block parameter (block number, block hash, or 'latest') at which to get the data. 2. `l1ToL2Message` - `Fr` - The l1ToL2Message to get the index / sibling path for. **Returns**: `[bigint, SiblingPath] | undefined` - A tuple of the index and the sibling path of the L1ToL2Message (undefined if not found). **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getL1ToL2MessageMembershipWitness","params":["latest","0x1234..."],"id":1}' ``` ### aztec\_getL1ToL2MessageCheckpoint[​](#aztec_getl1tol2messagecheckpoint "Direct link to aztec_getL1ToL2MessageCheckpoint") Returns the L2 checkpoint number in which this L1 to L2 message becomes available, or undefined if not found. **Parameters**: 1. `l1ToL2Message` - `Fr` **Returns**: `number | undefined` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getL1ToL2MessageCheckpoint","params":["0x1234..."],"id":1}' ``` ### aztec\_getL2ToL1MembershipWitness[​](#aztec_getl2tol1membershipwitness "Direct link to aztec_getL2ToL1MembershipWitness") Returns the L2-to-L1 membership witness for `message` emitted by tx `txHash`. The node selects the smallest partial-proof root on the Outbox that covers the tx's checkpoint and builds the witness against it. The node reads the Outbox roots lazily, pinned to its synced L1 block, so the witness reflects the node's synced view. Returns `undefined` if the tx isn't yet in a block/epoch or no covering root has landed on L1 as of the synced block. Caveat: cached roots that are sealed and L1-finalized are not re-validated. A reorg deeper than L1 finality could leave the node serving a witness against a no-longer-canonical root. **Parameters**: 1. `txHash` - `TxHash` - The tx whose L2-to-L1 message we want a witness for. 2. `message` - `Fr` - The message hash to prove inclusion of. 3. `messageIndexInTx` - `object | undefined` - Optional index of the message within the tx's L2-to-L1 messages; pass this when the same message hash appears multiple times in the tx. **Returns**: `L2ToL1MembershipWitness | undefined` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getL2ToL1MembershipWitness","params":["0x1234...","0x1234...",{}],"id":1}' ``` ### aztec\_getL2ToL1Messages[​](#aztec_getl2tol1messages "Direct link to aztec_getL2ToL1Messages") Returns all the L2 to L1 messages in an epoch. **Deprecated**: Use to get an L2-to-L1 message witness directly. **Parameters**: 1. `epoch` - `number` - The epoch at which to get the data. **Returns**: `Fr[][][][]` - A nested array of the L2 to L1 messages in each tx of each block in each checkpoint in the epoch (empty array if the epoch is not found). **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getL2ToL1Messages","params":[12345],"id":1}' ``` ## Log queries[​](#log-queries "Direct link to Log queries") ### aztec\_getPrivateLogsByTags[​](#aztec_getprivatelogsbytags "Direct link to aztec_getPrivateLogsByTags") Gets private logs matching the given tags. Returns one inner array per element of `query.tags`, in input order. An empty inner array means no logs matched that tag. Set `query.includeEffects` to also receive the tx's note hashes and nullifiers. The return type is the widest shape — `noteHashes`/`nullifiers` are typed as optional even when `includeEffects: true` is set. JSON-RPC validation can't preserve a stricter narrowing across the wire. Callers that want a narrowed type at the call site should use the typed helpers in `pxe/src/tagging/get_all_logs_by_tags.ts`. **Parameters**: 1. `query` - `PrivateLogsQuery` **Returns**: `LogResult[][]` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getPrivateLogsByTags","params":["0x1234..."],"id":1}' ``` ### aztec\_getPublicLogsByTags[​](#aztec_getpubliclogsbytags "Direct link to aztec_getPublicLogsByTags") Gets public logs matching the given tags for the given contract. Returns one inner array per element of `query.tags`, in input order. An empty inner array means no logs matched that tag. Set `query.includeEffects` to also receive the tx's note hashes and nullifiers. The return type is the widest shape — see . **Parameters**: 1. `query` - `PublicLogsQuery` **Returns**: `LogResult[][]` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getPublicLogsByTags","params":["0x1234..."],"id":1}' ``` ## Contract queries[​](#contract-queries "Direct link to Contract queries") ### aztec\_getContractClass[​](#aztec_getcontractclass "Direct link to aztec_getContractClass") Returns a registered contract class given its id. **Parameters**: 1. `id` - `Fr` - Id of the contract class. **Returns**: `ContractClassPublic | undefined` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getContractClass","params":["0x1234..."],"id":1}' ``` ### aztec\_getContract[​](#aztec_getcontract "Direct link to aztec_getContract") Returns a publicly deployed contract instance given its address. **Parameters**: 1. `address` - `AztecAddress` - Address of the deployed contract. **Returns**: `ContractInstanceWithAddress | undefined` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getContract","params":["0x1234..."],"id":1}' ``` ## Fee queries[​](#fee-queries "Direct link to Fee queries") ### aztec\_getCurrentMinFees[​](#aztec_getcurrentminfees "Direct link to aztec_getCurrentMinFees") Method to fetch the current min fees. **Parameters**: None **Returns**: `GasFees` - The current min fees. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getCurrentMinFees","params":[],"id":1}' ``` ### aztec\_getPredictedMinFees[​](#aztec_getpredictedminfees "Direct link to aztec_getPredictedMinFees") Returns predicted min fees for the current slot and next N slots. Each entry accounts for the L1 gas oracle transition and congestion growth based on the given mana usage estimate. Defaults to target usage (steady state). **Parameters**: 1. `manaUsage` - `ManaUsageEstimate | undefined` - Expected mana usage per checkpoint (none, target, or limit). **Returns**: `GasFees[]` - An array of GasFees, one per slot in the prediction window. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getPredictedMinFees","params":["target"],"id":1}' ``` ### aztec\_getMaxPriorityFees[​](#aztec_getmaxpriorityfees "Direct link to aztec_getMaxPriorityFees") Method to fetch the current max priority fee of txs in the mempool. **Parameters**: None **Returns**: `GasFees` - The current max priority fees. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getMaxPriorityFees","params":[],"id":1}' ``` ## Node information[​](#node-information "Direct link to Node information") ### aztec\_isReady[​](#aztec_isready "Direct link to aztec_isReady") Method to determine if the node is ready to accept transactions. **Parameters**: None **Returns**: `boolean` - Flag indicating the readiness for tx submission. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_isReady","params":[],"id":1}' ``` ### aztec\_getNodeInfo[​](#aztec_getnodeinfo "Direct link to aztec_getNodeInfo") Returns the information about the server's node. Includes current Node version, compatible Noir version, L1 chain identifier, protocol version, and L1 address of the rollup contract. **Parameters**: None **Returns**: `NodeInfo` - The node information. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getNodeInfo","params":[],"id":1}' ``` ### aztec\_getNodeVersion[​](#aztec_getnodeversion "Direct link to aztec_getNodeVersion") Method to fetch the version of the package. **Parameters**: None **Returns**: `string` - The node package version **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getNodeVersion","params":[],"id":1}' ``` ### aztec\_getVersion[​](#aztec_getversion "Direct link to aztec_getVersion") Method to fetch the version of the rollup the node is connected to. **Parameters**: None **Returns**: `number` - The rollup version. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getVersion","params":[],"id":1}' ``` ### aztec\_getChainId[​](#aztec_getchainid "Direct link to aztec_getChainId") Method to fetch the chain id of the base-layer for the rollup. **Parameters**: None **Returns**: `number` - The chain id. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getChainId","params":[],"id":1}' ``` ### aztec\_getL1ContractAddresses[​](#aztec_getl1contractaddresses "Direct link to aztec_getL1ContractAddresses") Method to fetch the currently deployed l1 contract addresses. **Parameters**: None **Returns**: `L1ContractAddresses` - The deployed contract addresses. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getL1ContractAddresses","params":[],"id":1}' ``` ### aztec\_getProtocolContractAddresses[​](#aztec_getprotocolcontractaddresses "Direct link to aztec_getProtocolContractAddresses") Method to fetch the protocol contract addresses. **Parameters**: None **Returns**: `ProtocolContractAddresses` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getProtocolContractAddresses","params":[],"id":1}' ``` ### aztec\_getEncodedEnr[​](#aztec_getencodedenr "Direct link to aztec_getEncodedEnr") Returns the ENR of this node for peer discovery, if available. **Parameters**: None **Returns**: `string | undefined` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getEncodedEnr","params":[],"id":1}' ``` ## Validator queries[​](#validator-queries "Direct link to Validator queries") ### aztec\_getValidatorsStats[​](#aztec_getvalidatorsstats "Direct link to aztec_getValidatorsStats") Returns stats for validators if enabled. **Parameters**: None **Returns**: `ValidatorsStats` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getValidatorsStats","params":[],"id":1}' ``` ### aztec\_getValidatorStats[​](#aztec_getvalidatorstats "Direct link to aztec_getValidatorStats") Returns stats for a single validator if enabled. **Parameters**: 1. `validatorAddress` - `EthAddress` 2. `fromSlot` - `SlotNumber | undefined` 3. `toSlot` - `SlotNumber | undefined` **Returns**: `SingleValidatorStats | undefined` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getValidatorStats","params":["0x1234...","100","100"],"id":1}' ``` ## P2P queries[​](#p2p-queries "Direct link to P2P queries") ### aztec\_getPeers[​](#aztec_getpeers "Direct link to aztec_getPeers") Returns info for all connected, dialing, and cached peers. Only available when P2P is enabled. **Parameters**: 1. `includePending` - `boolean | undefined` - If true, also include peers in the pending state. **Returns**: `PeerInfo[]` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getPeers","params":[true],"id":1}' ``` ### aztec\_getCheckpointAttestationsForSlot[​](#aztec_getcheckpointattestationsforslot "Direct link to aztec_getCheckpointAttestationsForSlot") Queries the attestation pool for checkpoint attestations for the given slot. **Parameters**: 1. `slot` - `SlotNumber` - The slot to query. 2. `proposalPayloadHash` - `string | undefined` - Hex-encoded keccak256 of the target proposal's signed payload hash. When provided, only attestations whose payload hash matches are returned. When omitted, all attestations for the slot are returned. **Returns**: `CheckpointAttestation[]` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getCheckpointAttestationsForSlot","params":["100","0x1234..."],"id":1}' ``` ### aztec\_getProposalsForSlot[​](#aztec_getproposalsforslot "Direct link to aztec_getProposalsForSlot") Returns block and checkpoint proposals retained in the attestation pool for the given slot. Only available when P2P is enabled. **Parameters**: 1. `slot` - `SlotNumber` **Returns**: `ProposalsForSlot` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getProposalsForSlot","params":["100"],"id":1}' ``` ## Debug operations[​](#debug-operations "Direct link to Debug operations") ### aztec\_registerContractFunctionSignatures[​](#aztec_registercontractfunctionsignatures "Direct link to aztec_registerContractFunctionSignatures") Registers contract function signatures for debugging purposes. **Parameters**: 1. `functionSignatures` - `string[]` - An array of function signatures to register by selector. **Returns**: `void` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_registerContractFunctionSignatures","params":[["0x1234..."]],"id":1}' ``` ### aztec\_getAllowedPublicSetup[​](#aztec_getallowedpublicsetup "Direct link to aztec_getAllowedPublicSetup") Returns the list of allowed public setup elements configured for this node. **Parameters**: None **Returns**: `AllowedElement[]` - The list of allowed elements. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getAllowedPublicSetup","params":[],"id":1}' ``` ## Admin API[​](#admin-api "Direct link to Admin API") Administrative operations are exposed on port 8880 under the `aztecAdmin_` namespace. Security: Admin API Access For security reasons, the admin port (8880) should **not be exposed** to the host machine in Docker deployments. The examples below show both CLI and Docker methods: **CLI Method** (when running with `aztec start` directly): ``` curl -X POST http://localhost:8880 ... ``` **Docker Method** (when running with Docker Compose): ``` docker exec -it curl -X POST http://localhost:8880 ... ``` Replace `` with your container name (e.g., `aztec-node`, `aztec-sequencer`, `prover-node`). ### aztecAdmin\_getConfig[​](#aztecadmin_getconfig "Direct link to aztecAdmin_getConfig") Retrieves the configuration of this node. **Parameters**: None **Returns**: `AztecNodeAdminConfig` **Example (CLI)**: ``` curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztecAdmin_getConfig","params":[],"id":1}' ``` **Example (Docker)**: ``` docker exec -it aztec-node curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztecAdmin_getConfig","params":[],"id":1}' ``` ### aztecAdmin\_setConfig[​](#aztecadmin_setconfig "Direct link to aztecAdmin_setConfig") Updates the configuration of this node. **Parameters**: 1. `config` - `object` - Updated configuration to be merged with the current one. **Returns**: `void` **Example (CLI)**: ``` curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztecAdmin_setConfig","params":[{}],"id":1}' ``` **Example (Docker)**: ``` docker exec -it aztec-node curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztecAdmin_setConfig","params":[{}],"id":1}' ``` ### aztecAdmin\_pauseSync[​](#aztecadmin_pausesync "Direct link to aztecAdmin_pauseSync") Pauses archiver and world state syncing. **Parameters**: None **Returns**: `void` **Example (CLI)**: ``` curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztecAdmin_pauseSync","params":[],"id":1}' ``` **Example (Docker)**: ``` docker exec -it aztec-node curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztecAdmin_pauseSync","params":[],"id":1}' ``` ### aztecAdmin\_resumeSync[​](#aztecadmin_resumesync "Direct link to aztecAdmin_resumeSync") Resumes archiver and world state syncing. **Parameters**: None **Returns**: `void` **Example (CLI)**: ``` curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztecAdmin_resumeSync","params":[],"id":1}' ``` **Example (Docker)**: ``` docker exec -it aztec-node curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztecAdmin_resumeSync","params":[],"id":1}' ``` ### aztecAdmin\_pauseSequencer[​](#aztecadmin_pausesequencer "Direct link to aztecAdmin_pauseSequencer") Pauses block production. Pending txs remain in the mempool; no new blocks will be produced until is called. Throws if no sequencer is running. **Parameters**: None **Returns**: `void` **Example (CLI)**: ``` curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztecAdmin_pauseSequencer","params":[],"id":1}' ``` **Example (Docker)**: ``` docker exec -it aztec-node curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztecAdmin_pauseSequencer","params":[],"id":1}' ``` ### aztecAdmin\_resumeSequencer[​](#aztecadmin_resumesequencer "Direct link to aztecAdmin_resumeSequencer") Resumes block production previously paused via . **Parameters**: None **Returns**: `void` **Example (CLI)**: ``` curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztecAdmin_resumeSequencer","params":[],"id":1}' ``` **Example (Docker)**: ``` docker exec -it aztec-node curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztecAdmin_resumeSequencer","params":[],"id":1}' ``` ### aztecAdmin\_rollbackTo[​](#aztecadmin_rollbackto "Direct link to aztecAdmin_rollbackTo") Pauses syncing and rolls back the database to the target L2 block number. **Parameters**: 1. `targetBlockNumber` - `number` - The block number to roll back to. 2. `force` - `boolean | undefined` - If true, clears the world state db and p2p dbs if rolling back to behind the finalized block. 3. `resumeSync` - `boolean | undefined` - If true (default), resumes archiver and world state sync after rollback. **Returns**: `void` **Example (CLI)**: ``` curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztecAdmin_rollbackTo","params":[12345,true,true],"id":1}' ``` **Example (Docker)**: ``` docker exec -it aztec-node curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztecAdmin_rollbackTo","params":[12345,true,true],"id":1}' ``` ### aztecAdmin\_startSnapshotUpload[​](#aztecadmin_startsnapshotupload "Direct link to aztecAdmin_startSnapshotUpload") Pauses syncing, creates a backup of archiver and world-state databases, and uploads them. Returns immediately. **Parameters**: 1. `location` - `string` - The location to upload the snapshot to. **Returns**: `void` **Example (CLI)**: ``` curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztecAdmin_startSnapshotUpload","params":["0x1234..."],"id":1}' ``` **Example (Docker)**: ``` docker exec -it aztec-node curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztecAdmin_startSnapshotUpload","params":["0x1234..."],"id":1}' ``` ### aztecAdmin\_getSlashOffenses[​](#aztecadmin_getslashoffenses "Direct link to aztecAdmin_getSlashOffenses") Returns all offenses applicable for the given round. **Parameters**: 1. `round` - `bigint | 'all' | 'current'` **Returns**: `Offense[]` **Example (CLI)**: ``` curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztecAdmin_getSlashOffenses","params":["current"],"id":1}' ``` **Example (Docker)**: ``` docker exec -it aztec-node curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztecAdmin_getSlashOffenses","params":["current"],"id":1}' ``` ### aztecAdmin\_reloadKeystore[​](#aztecadmin_reloadkeystore "Direct link to aztecAdmin_reloadKeystore") Reloads keystore configuration from disk. What is updated: * Validator attester keys * Coinbase address per validator * Fee recipient address per validator What is NOT updated (requires node restart): * L1 publisher signers (the funded accounts that send L1 transactions) * Prover keys * HA signer PostgreSQL connections Notes: * New validators must use a publisher key that was already configured at node startup (or omit the publisher field to fall back to the attester key). A validator with an unknown publisher key will cause the reload to be rejected. **Parameters**: None **Returns**: `void` **Example (CLI)**: ``` curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztecAdmin_reloadKeystore","params":[],"id":1}' ``` **Example (Docker)**: ``` docker exec -it aztec-node curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztecAdmin_reloadKeystore","params":[],"id":1}' ``` ## Next steps[​](#next-steps "Direct link to Next steps") * [How to Run a Sequencer Node](/operate/testnet/operators/setup/sequencer_management.md) - Set up a node * [Ethereum RPC Calls Reference](/operate/testnet/operators/reference/ethereum_rpc_reference.md) - L1 RPC usage * [CLI Reference](/operate/testnet/operators/reference/cli-reference.md) - Command-line options * [Aztec Discord](https://discord.gg/aztec) - Developer support --- # Sequencer Management ## Overview[​](#overview "Direct link to Overview") Once your sequencer is running, you need to manage its ongoing operations. This guide covers sequencer management tasks including participating in governance, running with delegated stake, and querying contract state to monitor your sequencer's health and performance. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before proceeding, you should: * Have a running sequencer node (see [Sequencer Setup Guide](/operate/testnet/operators/setup/sequencer_management.md)) * Be familiar with basic sequencer operations * Have access to Foundry's `cast` tool for contract queries * Understand your sequencer's role in the network ## Understanding Sequencer Operations[​](#understanding-sequencer-operations "Direct link to Understanding Sequencer Operations") As a sequencer operator, your responsibilities extend beyond simply running a node. You participate in network governance, manage your stake (whether self-funded or delegated), and monitor your sequencer's performance and status on the network. ### Key Management Areas[​](#key-management-areas "Direct link to Key Management Areas") **Governance Participation**: Sequencers play a crucial role in protocol governance. You signal support for protocol upgrades, vote on proposals, and help shape the network's evolution. Active participation ensures your voice is heard in decisions that affect the protocol. **Stake Management**: Whether you're using your own stake or operating with delegated stake from others, you need to understand how staking works, monitor your balances, and ensure you maintain sufficient funds for operations. **Operational Monitoring**: Regular monitoring of your sequencer's status, performance metrics, and onchain state helps you catch issues early and maintain optimal operations. ## What This Guide Covers[​](#what-this-guide-covers "Direct link to What This Guide Covers") This guide walks you through sequencer management in four parts: ### 1. Governance and Proposal Process[​](#1-governance-and-proposal-process "Direct link to 1. Governance and Proposal Process") Learn how to participate in protocol governance: * Understanding payloads and the governance lifecycle * Signaling support for protocol upgrades * Creating and voting on proposals * Executing approved changes * Upgrading your node after governance changes See [Governance Participation](/operate/testnet/operators/sequencer-management/creating_and_voting_on_proposals.md) for detailed instructions. ### 2. Running as a Staking Provider[​](#2-running-as-a-staking-provider "Direct link to 2. Running as a Staking Provider") If you're operating a sequencer with delegated stake: * Understanding the delegated stake model * Registering as a provider with the Staking Registry * Managing sequencer identities for delegation * Updating provider configuration and commission rates * Monitoring delegator relationships See [Becoming a Staking Provider](/operate/testnet/operators/setup/become_a_staking_provider.md) for setup instructions. ### 3. Claiming Rewards[​](#3-claiming-rewards "Direct link to 3. Claiming Rewards") Learn how to claim your sequencer rewards: * Understanding how rewards accumulate in the Rollup contract * Checking reward claimability status and pending rewards * Claiming rewards to your coinbase address * Troubleshooting common claiming issues See [Claiming Rewards](/operate/testnet/operators/sequencer-management/claiming-rewards.md) for detailed instructions. ### 4. Useful Commands[​](#4-useful-commands "Direct link to 4. Useful Commands") Essential contract query commands for operators: * Finding contract addresses (Registry, Rollup, Governance) * Querying the sequencer set and individual sequencer status * Checking governance signals and proposal counts * Monitoring stake balances and voting power * Troubleshooting common query issues See [Useful Commands](/operate/testnet/operators/sequencer-management/useful-commands.md) for a complete reference. ## Getting Started[​](#getting-started "Direct link to Getting Started") Start with the [Useful Commands](/operate/testnet/operators/sequencer-management/useful-commands.md) guide to learn how to query your sequencer's status and verify it's operating correctly. This helps you establish a baseline for monitoring. If you're participating in governance, review the [Governance Participation](/operate/testnet/operators/sequencer-management/creating_and_voting_on_proposals.md) guide to understand how to signal, vote, and execute proposals. For operators running with delegated stake, the [Becoming a Staking Provider](/operate/testnet/operators/setup/become_a_staking_provider.md) guide walks you through provider registration and management. ## Best Practices[​](#best-practices "Direct link to Best Practices") **Monitor Regularly**: Check your sequencer's status, balance, and attestation activity regularly. Set up alerts for critical thresholds like low balances or missed attestations. **Participate in Governance**: Stay informed about governance proposals and participate in votes that affect your operations. Join the community discussions on Discord to understand proposed changes. **Maintain Adequate Balances**: Ensure your publisher account always has sufficient ETH (at least 0.1 ETH) to avoid being slashed. Monitor balances and set up automated top-ups if possible. **Keep Your Node Updated**: When governance proposals pass that require node upgrades, prepare during the execution delay period. Have a plan for coordinated upgrades to minimize downtime. **Communicate with Delegators**: If you're running with delegated stake, maintain open communication with your delegators about performance, commission changes, and planned maintenance. ## Next Steps[​](#next-steps "Direct link to Next Steps") * Query your sequencer status using the [Useful Commands](/operate/testnet/operators/sequencer-management/useful-commands.md) * Learn about [governance participation](/operate/testnet/operators/sequencer-management/creating_and_voting_on_proposals.md) to vote on protocol changes * Set up [monitoring](/operate/testnet/operators/monitoring.md) to track your sequencer's performance * Join the [Aztec Discord](https://discord.gg/aztec) for operator support and community discussions --- # Claiming Rewards ## Overview[​](#overview "Direct link to Overview") Sequencer rewards accumulate in the Rollup contract but are not automatically distributed. You must manually claim them by calling the Rollup contract. This guide shows you how to check pending rewards and claim them using Foundry's `cast` command. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before proceeding, you should: * Have a running sequencer that earned rewards (see [Sequencer Setup Guide](/operate/testnet/operators/setup/sequencer_management.md)) * Have Foundry installed with the `cast` command available ([installation guide](https://book.getfoundry.sh/getting-started/installation)) * Know your Rollup contract address (see [Useful Commands](/operate/testnet/operators/sequencer-management/useful-commands.md#get-the-rollup-contract-address)) * Have your sequencer's coinbase address * Have an Ethereum RPC endpoint for the network you're querying ## Understanding Reward Claiming[​](#understanding-reward-claiming "Direct link to Understanding Reward Claiming") ### How Rewards Accumulate[​](#how-rewards-accumulate "Direct link to How Rewards Accumulate") When your sequencer proposes blocks and participates in consensus, rewards accumulate in the Rollup contract under your coinbase address. These rewards come from: * Block rewards distributed by the protocol * Transaction fees from processed transactions Rewards are tracked per coinbase address in the Rollup contract's storage but remain in the contract until you claim them. ### Manual vs Automatic[​](#manual-vs-automatic "Direct link to Manual vs Automatic") Rewards are not automatically sent to your coinbase address. You must explicitly claim them by calling the `claimSequencerRewards` function on the Rollup contract. ### Claim Requirements[​](#claim-requirements "Direct link to Claim Requirements") Before claiming, verify these conditions: 1. **Rewards have accumulated**: Query your pending rewards before attempting to claim. 2. **Sufficient gas**: Ensure you have ETH to pay transaction gas costs. ## Checking Reward Status[​](#checking-reward-status "Direct link to Checking Reward Status") ### Set Up Your Environment[​](#set-up-your-environment "Direct link to Set Up Your Environment") For convenience, set your RPC URL as an environment variable: ``` export RPC_URL="https://your-ethereum-rpc-endpoint.com" export ROLLUP_ADDRESS="[YOUR_ROLLUP_CONTRACT_ADDRESS]" ``` Replace `[YOUR_ROLLUP_CONTRACT_ADDRESS]` with your actual Rollup contract address. ### Query Your Pending Rewards[​](#query-your-pending-rewards "Direct link to Query Your Pending Rewards") Check accumulated rewards: ``` cast call $ROLLUP_ADDRESS "getSequencerRewards(address)" [COINBASE_ADDRESS] --rpc-url $RPC_URL ``` Replace `[COINBASE_ADDRESS]` with your sequencer's coinbase address. **Example:** ``` # Query and convert to decimal tokens (assuming 18 decimals) cast call $ROLLUP_ADDRESS "getSequencerRewards(address)" 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb --rpc-url $RPC_URL | cast --to-dec | cast --from-wei # Output: 0.1 ``` ## Claiming Your Rewards[​](#claiming-your-rewards "Direct link to Claiming Your Rewards") The `claimSequencerRewards` function is permissionless - anyone can call it for any address. Rewards are always sent to the `coinbase` address, regardless of who submits the transaction. ### Basic Claim Command[​](#basic-claim-command "Direct link to Basic Claim Command") Use `cast send` to claim rewards: ``` cast send $ROLLUP_ADDRESS \ "claimSequencerRewards(address)" \ [COINBASE_ADDRESS] \ --rpc-url $RPC_URL \ --private-key [YOUR_PRIVATE_KEY] ``` Replace: * `[COINBASE_ADDRESS]` - The coinbase address whose rewards you want to claim * `[YOUR_PRIVATE_KEY]` - The private key of the account paying for gas **Example:** ``` cast send $ROLLUP_ADDRESS \ "claimSequencerRewards(address)" \ 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb \ --rpc-url $RPC_URL \ --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 ``` ### Using a Keystore File[​](#using-a-keystore-file "Direct link to Using a Keystore File") For better security, use a keystore file instead of exposing your private key: ``` cast send $ROLLUP_ADDRESS \ "claimSequencerRewards(address)" \ [COINBASE_ADDRESS] \ --rpc-url $RPC_URL \ --keystore [PATH_TO_KEYSTORE] \ --password [KEYSTORE_PASSWORD] ``` ### Using a Hardware Wallet[​](#using-a-hardware-wallet "Direct link to Using a Hardware Wallet") If you're using a Ledger wallet: ``` cast send $ROLLUP_ADDRESS \ "claimSequencerRewards(address)" \ [COINBASE_ADDRESS] \ --rpc-url $RPC_URL \ --ledger ``` This will prompt you to confirm the transaction on your Ledger device. ## Verifying Your Claim[​](#verifying-your-claim "Direct link to Verifying Your Claim") Check that the transaction succeeded and your pending rewards were reset to zero: ``` # Check transaction succeeded (look for status: 1) cast receipt [TRANSACTION_HASH] --rpc-url $RPC_URL # Verify pending rewards are now zero cast call $ROLLUP_ADDRESS "getSequencerRewards(address)" [COINBASE_ADDRESS] --rpc-url $RPC_URL ``` ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### No Pending Rewards[​](#no-pending-rewards "Direct link to No Pending Rewards") **Symptom**: `getSequencerRewards()` returns zero. **Possible causes**: 1. Your sequencer has not proposed any blocks yet 2. You already claimed all available rewards 3. Your coinbase address is configured incorrectly **Solutions**: 1. Verify your sequencer is active and proposing blocks (check [monitoring](/operate/testnet/operators/monitoring.md)) 2. Check your sequencer logs for block proposals 3. Verify the coinbase address in your sequencer configuration matches the address you're querying 4. Check if blocks you proposed have been proven (rewards are distributed after proof submission) ### Transaction Fails with "Out of Gas"[​](#transaction-fails-with-out-of-gas "Direct link to Transaction Fails with \"Out of Gas\"") **Symptom**: Transaction reverts due to insufficient gas. **Solution**: 1. Increase the gas limit when sending the transaction using `--gas-limit`: ``` cast send $ROLLUP_ADDRESS \ "claimSequencerRewards(address)" \ [COINBASE_ADDRESS] \ --rpc-url $RPC_URL \ --private-key [YOUR_PRIVATE_KEY] \ --gas-limit 200000 ``` 2. Ensure your account has sufficient ETH to cover gas costs ### Insufficient Funds for Gas[​](#insufficient-funds-for-gas "Direct link to Insufficient Funds for Gas") **Symptom**: Transaction fails because the sending account has insufficient ETH. **Solution**: 1. Check your account balance: ``` cast balance [YOUR_ADDRESS] --rpc-url $RPC_URL ``` 2. Send ETH to your account to cover gas costs (recommended: at least 0.005 ETH) ### Wrong Network[​](#wrong-network "Direct link to Wrong Network") **Symptom**: Transaction fails or contract calls return unexpected results. **Solution**: 1. Verify your RPC URL points to the correct network (Sepolia for testnet) 2. Verify the Rollup contract address matches your target network 3. Check your account has ETH on the correct network ## Best Practices[​](#best-practices "Direct link to Best Practices") **Claim Regularly**: Claim rewards periodically to reduce accumulated balances in the Rollup contract. This minimizes risk and simplifies accounting. **Monitor Pending Rewards**: Set up automated scripts to query pending rewards and alert you when they exceed a threshold. **Use Keystore Files**: Avoid exposing private keys in command history. Use keystore files or hardware wallets for production operations. **Verify Before Claiming**: Check pending rewards before claiming to ensure the transaction justifies the gas cost. **Track Claim History**: Keep records of claim transactions for accounting purposes using transaction hashes on blockchain explorers. **Coordinate with Delegators**: If operating with delegated stake, communicate with delegators about claiming and distribution schedules. ## Next Steps[​](#next-steps "Direct link to Next Steps") * Set up [monitoring](/operate/testnet/operators/monitoring.md) to track reward accumulation automatically * Learn about [becoming a staking provider](/operate/testnet/operators/setup/become_a_staking_provider.md) if operating with delegators * Review [useful commands](/operate/testnet/operators/sequencer-management/useful-commands.md) for other sequencer queries * Join the [Aztec Discord](https://discord.gg/aztec) for operator support and community discussions --- # Governance and Proposal Process ## Overview[​](#overview "Direct link to Overview") This guide shows you how to participate in protocol governance as a sequencer. You'll learn how to signal support for protocol upgrades, create proposals, and vote on governance decisions that shape the Aztec network. Conceptual Background Before diving into the practical steps, you may want to understand the underlying concepts: * [Governance Overview](/participate/governance.md) - How the governance system works * [Proposal Lifecycle](/participate/governance/proposal-lifecycle.md) - The stages from signaling to execution * [Voting](/participate/governance/voting.md) - How voting power and delegation work * [GSE and Stake Mobility](/participate/governance/gse.md) - How your stake moves during upgrades ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before proceeding, you should: * Have a running sequencer node (see [Sequencer Setup Guide](/operate/testnet/operators/setup/sequencer_management.md)) * Understand [how governance works](/participate/governance.md) ## Understanding Governance Components[​](#understanding-governance-components "Direct link to Understanding Governance Components") ### Payloads[​](#payloads "Direct link to Payloads") Protocol upgrades consist of a series of commands that execute on protocol contracts or replace contract references. You define these steps in a contract called a **payload** that you deploy on Ethereum. This guide assumes the payload already exists at a known address. You'll participate in the payload's journey through signaling, proposal creation, voting, and execution. Always Verify Payloads Before signaling support or voting, always: 1. Verify the payload address on Etherscan or your preferred block explorer 2. Review the `getActions()` function to understand what changes the payload will make 3. Check if the payload has been audited (if applicable) 4. Discuss the proposal with the community on [Aztec Discord](https://discord.gg/aztec) Never signal or vote for a payload you haven't personally verified. Here's an example payload structure: ``` contract UpgradePayload is IPayload { IRegistry public immutable REGISTRY; address public NEW_ROLLUP = address(new FakeRollup()); constructor(IRegistry _registry) { REGISTRY = _registry; } function getActions() external view override(IPayload) returns (IPayload.Action[] memory) { IPayload.Action[] memory res = new IPayload.Action[](1); res[0] = Action({ target: address(REGISTRY), data: abi.encodeWithSelector(REGISTRY.addRollup.selector, NEW_ROLLUP) }); return res; } function getURI() external pure override(IPayload) returns (string memory) { return "UpgradePayload"; } } ``` If this payload's proposal passes governance voting, the governance contract executes `addRollup` on the `Registry` contract. ### Contract Addresses[​](#contract-addresses "Direct link to Contract Addresses") Key contracts you'll use: * **Governance Proposer**: Handles payload signaling and proposal creation * **Governance Staking Escrow (GSE)**: Manages stake delegation and voting * **Governance**: Executes approved proposals * **Rollup**: Your sequencer stakes here and defaults to delegating voting power here **To obtain these contract addresses:** Check your sequencer logs at startup for the line beginning with `INFO: node Aztec Node started on chain...` ### Governance Lifecycle Overview[​](#governance-lifecycle-overview "Direct link to Governance Lifecycle Overview") The governance process follows these stages: 1. **Signaling**: Sequencers signal support for a payload when proposing blocks. A payload needs a quorum of support to be promoted to a proposal. Signaling can start any time from the moment a payload is deployed. 2. **Proposal Creation**: After reaching quorum, anyone can submit the payload as an official proposal. 3. **Voting Delay** (12 hours): A mandatory waiting period before voting opens (allows time for community review). 4. **Voting Period** (24 hours): Users who hold stake in the network vote on the proposal using their staked tokens. 5. **Execution Delay** (12 hours): After passing the vote, another mandatory delay before execution (allows time for node upgrades). 6. **Execution**: Anyone can execute the proposal, which applies the changes. **Note:** These timeline values are specific to testnet and are subject to change for future network phases. ## Signaling Support for a Payload[​](#signaling-support-for-a-payload "Direct link to Signaling Support for a Payload") As a sequencer, you initiate proposals through signaling. When you propose a block, you can automatically signal support for a specific payload. Once enough sequencers signal support within a round, the payload qualifies to become an official proposal. ### How Signaling Works[​](#how-signaling-works "Direct link to How Signaling Works") * Only you can signal during slots when you're the block proposer * Your sequencer node automatically calls `signal` on the `GovernanceProposer` contract when proposing a block (if you've configured a payload address) * Rounds consist of 300 slots each (180 minutes at 36 seconds per slot). At every 300-block boundary, the system checks if any payload has received 151 or more signals (the quorum threshold, which is >50% of the round size) * Payloads that reach quorum can be submitted as official proposals by anyone **Note:** Round size and quorum threshold will change between testnet and ignition. These values and any further references to these values are relevant for testnet only. ### Configure Your Signaling Preference[​](#configure-your-signaling-preference "Direct link to Configure Your Signaling Preference") Use the `setConfig` method on your node's admin interface to specify which payload address you want to signal support for. **CLI Method**: ``` curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{ "jsonrpc":"2.0", "method":"aztecAdmin_setConfig", "params":[{"governanceProposerPayload":"0x1234567890abcdef1234567890abcdef12345678"}], "id":1 }' ``` **Docker Method**: ``` docker exec -it aztec-sequencer curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{ "jsonrpc":"2.0", "method":"aztecAdmin_setConfig", "params":[{"governanceProposerPayload":"0x1234567890abcdef1234567890abcdef12345678"}], "id":1 }' ``` Replace `0x1234567890abcdef1234567890abcdef12345678` with your actual payload contract address and `aztec-sequencer` with your container name. Expected response: ``` {"jsonrpc":"2.0","id":1} ``` ### Verify Your Configuration[​](#verify-your-configuration "Direct link to Verify Your Configuration") Use the `getConfig` method to verify the payload address: **CLI Method**: ``` curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{ "jsonrpc":"2.0", "method":"aztecAdmin_getConfig", "id":1 }' ``` **Docker Method**: ``` docker exec -it aztec-sequencer curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{ "jsonrpc":"2.0", "method":"aztecAdmin_getConfig", "id":1 }' ``` Search for `governanceProposerPayload` in the response to confirm it matches your configured address. Once configured, your sequencer automatically signals support for this payload each time you propose a block. Each signal counts toward the quorum requirement. ## Creating a Proposal[​](#creating-a-proposal "Direct link to Creating a Proposal") Once a payload receives the required quorum (151 signals in a 300-slot round), you or any user can call `submitRoundWinner` on the `GovernanceProposer` contract to officially create the proposal. ### Submit the Payload[​](#submit-the-payload "Direct link to Submit the Payload") ``` cast send [GOVERNANCE_PROPOSER_ADDRESS] \ "submitRoundWinner(uint256)" [ROUND_NUMBER] \ --rpc-url [YOUR_RPC_URL] \ --private-key [YOUR_PRIVATE_KEY] ``` To find the current round number: ``` # Get the current round from the GovernanceProposer contract cast call [GOVERNANCE_PROPOSER_ADDRESS] \ "getCurrentRound()" \ --rpc-url [YOUR_RPC_URL] ``` ### Verify the Created Proposal[​](#verify-the-created-proposal "Direct link to Verify the Created Proposal") After creation, you can query the proposal in the governance contract: ``` # Get the total proposal count cast call [GOVERNANCE_CONTRACT_ADDRESS] \ "proposalCount()" \ --rpc-url [YOUR_RPC_URL] # Query the latest proposal (count - 1, since proposals are zero-indexed) cast call [GOVERNANCE_CONTRACT_ADDRESS] \ "getProposal(uint256)" $((PROPOSAL_COUNT - 1)) \ --rpc-url [YOUR_RPC_URL] ``` This returns the `Proposal` struct data, which includes: * The payload address * Creation timestamp * Voting start and end times * Current vote tallies ## Voting on Proposals[​](#voting-on-proposals "Direct link to Voting on Proposals") Once a payload becomes a proposal, there's a mandatory waiting period before voting opens. You can vote in two ways: through default delegation to the rollup contract, or by delegating to an address you control for custom voting. ### Default Voting Through the Rollup[​](#default-voting-through-the-rollup "Direct link to Default Voting Through the Rollup") By default, when you stake as a sequencer, you delegate your voting power to the rollup contract through the GSE (Governance Staking Escrow). The rollup automatically votes "yea" on proposals created through the `GovernanceProposer` using **all** delegated stake from **all** sequencers in that rollup. **Key points:** * If you signaled for a payload, your stake votes "yea" automatically—no additional action needed * If you didn't signal but other sequencers did, your stake still votes "yea" when the rollup votes * To vote differently, you must change your delegation before voting opens (see Custom Voting below) Anyone can trigger the rollup vote: ``` cast send [ROLLUP_ADDRESS] \ "vote(uint256)" [PROPOSAL_ID] \ --rpc-url [YOUR_RPC_URL] \ --private-key [YOUR_PRIVATE_KEY] ``` ### Custom Voting: Delegating to Your Own Address[​](#custom-voting-delegating-to-your-own-address "Direct link to Custom Voting: Delegating to Your Own Address") If you want to vote differently on a proposal (for example, to vote "nay" or to split your voting power), you can delegate your stake to an address you control. This removes your stake's voting power from the rollup's control and gives it to your chosen address. Voting Power Timestamp Voting power is timestamped at the moment a proposal becomes "active" (when the voting period opens). You must complete delegation **before** the voting period begins to use your voting power for that proposal. Check the proposal's voting start time and delegate well in advance. #### Step 1: Delegate Your Stake[​](#step-1-delegate-your-stake "Direct link to Step 1: Delegate Your Stake") Use the GSE contract to delegate to an address you control: ``` cast send [GSE_ADDRESS] \ "delegate(address,address,address)" \ [ROLLUP_ADDRESS] \ [YOUR_ATTESTER_ADDRESS] \ [YOUR_DELEGATEE_ADDRESS] \ --rpc-url [YOUR_RPC_URL] \ --private-key [YOUR_WITHDRAWER_PRIVATE_KEY] ``` * `[ROLLUP_ADDRESS]`: The rollup contract where you staked * `[YOUR_ATTESTER_ADDRESS]`: Your sequencer's attester address * `[YOUR_DELEGATEE_ADDRESS]`: The address that will vote (often the same as your attester address, or another address you control) * You must sign this transaction with your **withdrawer** private key (the withdrawer that you specified when you initially deposited to the rollup) #### Step 2: Vote Through GSE[​](#step-2-vote-through-gse "Direct link to Step 2: Vote Through GSE") Once you've delegated to an address you control, that address can vote directly on proposals: ``` # Vote "yea" with your voting power cast send [GSE_ADDRESS] \ "vote(uint256,uint256,bool)" \ [PROPOSAL_ID] \ [AMOUNT] \ true \ --rpc-url [YOUR_RPC_URL] \ --private-key [YOUR_DELEGATEE_PRIVATE_KEY] ``` * `[AMOUNT]`: The amount of voting power to use (can be your full stake or a partial amount) * You can vote multiple times with different amounts to split your voting power between "yea" and "nay" if desired * To vote "nay" with your voting power, set the boolean in the code above to false #### Step 3: Verify Your Vote[​](#step-3-verify-your-vote "Direct link to Step 3: Verify Your Vote") Check that your vote was recorded: ``` # Check vote counts for a proposal # Note: This returns the proposal's vote tallies from the Governance contract, not GSE cast call [GOVERNANCE_CONTRACT_ADDRESS] \ "getProposal(uint256)" [PROPOSAL_ID] \ --rpc-url [YOUR_RPC_URL] ``` This returns the current "yea" and "nay" vote tallies. ## Executing Proposals[​](#executing-proposals "Direct link to Executing Proposals") When a proposal receives sufficient support, it passes. After passing, there's another mandatory delay before the proposal becomes executable. Once executable, anyone can trigger execution. ### Execute the Proposal[​](#execute-the-proposal "Direct link to Execute the Proposal") Once the proposal state is Executable, anyone can execute it: ``` cast send [GOVERNANCE_CONTRACT_ADDRESS] \ "execute(uint256)" [PROPOSAL_ID] \ --rpc-url [YOUR_RPC_URL] \ --private-key [YOUR_PRIVATE_KEY] ``` After execution, the governance contract performs all actions defined in the payload. The protocol changes become effective immediately. ### Upgrade Your Node[​](#upgrade-your-node "Direct link to Upgrade Your Node") **Critical**: Once a proposal executes, you must upgrade your node software to track the protocol changes. Monitor proposals closely from the signaling stage through execution. When a vote passes, prepare to upgrade your node software during the execution delay period, so you're ready when the proposal becomes effective. In practice, this often means running multiple nodes, with one node being on the version upgraded from, and one being on the version being upgraded to. Stake Mobility If you deposited with `moveWithLatestRollup = true`, your stake automatically becomes available to the new rollup after an upgrade. If you used `false`, you'll need to manually exit and re-enter. See [GSE and Stake Mobility](/participate/governance/gse.md) for details. ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### My Signal Isn't Being Recorded[​](#my-signal-isnt-being-recorded "Direct link to My Signal Isn't Being Recorded") **Symptoms**: You configured a payload address, but the signal count isn't increasing. **Solutions**: 1. Verify you're actually proposing blocks in slots assigned to you 2. Check your node logs for errors related to governance signaling 3. Verify the payload address is correct and matches the format (0x...) 4. Confirm the `GovernanceProposer` contract address is correct for your network ### I Can't Delegate My Voting Power[​](#i-cant-delegate-my-voting-power "Direct link to I Can't Delegate My Voting Power") **Symptoms**: Delegation transaction fails or reverts. **Solutions**: 1. Verify you're using your **withdrawer** private key, not your attester key 2. Confirm you have stake deposited in the rollup 3. Check that the addresses are correct (rollup, attester, delegatee) 4. Ensure the rollup address matches where you actually staked ### My Vote Transaction Fails[​](#my-vote-transaction-fails "Direct link to My Vote Transaction Fails") **Symptoms**: Vote transaction reverts or fails. **Solutions**: 1. Check the proposal is in the "Active" state (voting period is open) 2. Verify you delegated before the voting period started (voting power is timestamped) 3. Confirm you have sufficient voting power (check your stake amount) 4. Ensure you're not trying to vote with more power than you have 5. Check you're using the correct private key (delegatee key, not withdrawer) ### How Do I Check When Voting Opens?[​](#how-do-i-check-when-voting-opens "Direct link to How Do I Check When Voting Opens?") Query the proposal to see the voting timeline: ``` cast call [GOVERNANCE_CONTRACT_ADDRESS] \ "getProposal(uint256)" [PROPOSAL_ID] \ --rpc-url [YOUR_RPC_URL] ``` The returned data includes timestamps for: * Voting start time * Voting end time ## Summary[​](#summary "Direct link to Summary") As a sequencer participating in governance: 1. **Signal support**: Configure your node with a payload address. Your node automatically signals when proposing blocks. 2. **Vote**: Your delegated stake automatically votes "yea" on proposals created through sequencer signaling. You don't need to take additional action if you support the proposal. To vote differently, delegate your stake to an address you control before voting opens, then vote directly through the GSE contract. 3. **Upgrade promptly**: Monitor proposals and upgrade your node software after execution to stay in sync with protocol changes. ## Next Steps[​](#next-steps "Direct link to Next Steps") * Learn about [sequencer setup](/operate/testnet/operators/setup/sequencer_management.md) for operating your node * Join the [Aztec Discord](https://discord.gg/aztec) to participate in governance discussions and stay informed about upcoming proposals --- # Slashing and Offenses ## Overview[​](#overview "Direct link to Overview") This guide explains how the Aztec network's slashing mechanism works and how your sequencer automatically participates in detecting and voting on validator offenses. You'll learn about the types of offenses that are automatically detected and how to configure your sequencer's slashing behavior. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before proceeding, you should: * Have a running sequencer node (see [Sequencer Setup Guide](/operate/testnet/operators/setup/sequencer_management.md)) * Understand that slashing actions are executed automatically when you propose blocks * Have the Sentinel enabled if you want to detect inactivity offenses ## Understanding the Slashing Model[​](#understanding-the-slashing-model "Direct link to Understanding the Slashing Model") The Aztec network uses a consensus-based slashing mechanism where validators vote on individual validator offenses during block proposal. ### How Slashing Works[​](#how-slashing-works "Direct link to How Slashing Works") **Automatic Detection**: Your sequencer runs watchers that continuously monitor the network and automatically detect slashable offenses committed by other validators. **Voting Through Proposals**: Time is divided into slashing rounds (typically 128 L2 slots per round). When you propose a block during round N, your sequencer automatically votes on which validators from round N-2 should be slashed. This 2-round offset gives the network time to detect offenses before voting. **Vote Encoding**: Votes are encoded as bytes where each validator's vote is represented by 2 bits indicating the slash amount (0-3 slash units). The L1 contract tallies these votes and slashes validators that reach quorum. **Execution**: After a round ends, there's an execution delay period (approximately 3 days) during which the slashing vetoer can pause execution if needed. Once the delay passes, anyone can execute the round to apply the slashing. ### Slashing Rounds and Offsets[​](#slashing-rounds-and-offsets "Direct link to Slashing Rounds and Offsets") ``` Round 1 (Grace Period): No voting happens Round 2 (Grace Period): No voting happens Round 3: Proposers vote on offenses from Round 1 (which are typically forgiven due to grace period) Round 4: Proposers vote on offenses from Round 2 Round N: Proposers vote on offenses from Round N-2 ``` **Key parameters**: * **Round Size**: 128 L2 slots (approximately 2.6 hours at 72 seconds per slot) * **Slashing Offset**: 2 rounds (proposers in round N vote on offenses from round N-2) * **Execution Delay**: 2 rounds on the v5 testnet (\~5 hours; mainnet uses 28 rounds, \~3 days) * **Grace Period**: First 64 slots after the rollup becomes canonical on the v5 testnet (\~1.3 hours; mainnet uses 1,200 slots, \~1 day); configurable per node via `SLASH_GRACE_PERIOD_L2_SLOTS` ### Slashing Amounts[​](#slashing-amounts "Direct link to Slashing Amounts") The L1 contract defines three fixed slashing tiers that can be configured for different offenses. These amounts are set on L1 deployment and can only be changed via governance. Network Configuration On the v5 testnet, slashing uses the AZIP-16 full-stake preset: **100,000 tokens for small offenses and 250,000 tokens for medium and large offenses**, against a 200,000 token Activation Threshold (the minimum stake required to join the validator set). The medium and large amount exceeds the stake, so those offenses slash the validator's entire stake. A validator is ejected when a slash would drop its stake below the rollup's local ejection threshold (199,000 tokens on testnet). Because even the smallest slash (100,000 tokens) drops a validator that joined at the Activation Threshold below that line, a single offense of any tier ejects the validator. (Mainnet uses smaller amounts, 2,000 and 5,000 tokens, with a 190,000 token local ejection threshold.) See [Ejection from the validator set](#ejection-from-the-validator-set) for details. ## Slashable offenses[​](#slashable-offenses "Direct link to Slashable offenses") Your sequencer automatically detects and votes to slash the following offenses. The set of offenses, and the rationale behind them, is specified in [AZIP-7](https://github.com/AztecProtocol/governance/blob/main/AZIPs/azip-7-update_slashing.md). ### 1. Inactivity[​](#1-inactivity "Direct link to 1. Inactivity") **What it is**: A validator fails to attest to checkpoint proposals when selected for committee duty, or fails to produce checkpoints and block proposals when selected as proposer. **Detection criteria**: * Measured per epoch by the Sentinel for validators on the committee during that epoch. * Evaluated at the end of each epoch (plus a small buffer) without waiting for the epoch to be proven on L1, so inactive validators can be slashed regardless of prover availability. * Block re-execution is used to attribute fault between proposers and attestors based on what actually happened in each slot, rather than using attestation count as a proxy. * A validator is considered inactive for an epoch if their failure ratio meets or exceeds `SLASH_INACTIVITY_TARGET_PERCENTAGE`. * Requires consecutive committee participation with inactivity: must be inactive for N consecutive epochs where they were on the committee (configured via `SLASH_INACTIVITY_CONSECUTIVE_EPOCH_THRESHOLD`). Epochs where the validator was not on the committee are not counted, so a validator inactive in epochs 1, 3, and 5 meets the threshold for 3 consecutive inactive epochs even though epochs 2 and 4 are skipped. **Note**: Requires the Sentinel to be enabled (`SENTINEL_ENABLED=true`). ### 2. Data withholding[​](#2-data-withholding "Direct link to 2. Data withholding") **What it is**: After a checkpoint is published, the transactions it contains were not made available on the P2P network within the tolerance window. **Detection criteria**: * Once `SLASH_DATA_WITHHOLDING_TOLERANCE_SLOTS` full L2 slots have elapsed past the checkpoint's slot, your node checks whether it has all the transactions for that checkpoint in its local mempool. * If any are missing, the validators who attested to the checkpoint are flagged. * The check runs regardless of whether the epoch is eventually proven. Slashing still applies if the data was withheld, to prevent committees from striking side deals with specific provers by releasing data only to them. **Responsibility**: Validators who attested to the checkpoint. ### 3. Broadcasted invalid block proposal[​](#3-broadcasted-invalid-block-proposal "Direct link to 3. Broadcasted invalid block proposal") **What it is**: A proposer broadcast an invalid block proposal over the P2P network. **Detection criteria**: Detected by validators during proposal validation, for example when a transaction in the proposal fails validation or the proposed block header is structurally invalid. **Responsibility**: The proposer who broadcast the invalid block. ### 4. Broadcasted invalid checkpoint proposal[​](#4-broadcasted-invalid-checkpoint-proposal "Direct link to 4. Broadcasted invalid checkpoint proposal") **What it is**: A proposer broadcast an invalid checkpoint proposal over the P2P network. This includes the AZIP-7 "submitting a block proposal after the checkpoint" case, because a later block signed by the same proposer in the same slot makes the prior checkpoint retroactively invalid. **Detection criteria**: Detected when the checkpoint terminates before a higher-index block proposal signed by the same proposer in the same slot, when the signed header does not match deterministic validator recomputation, or when the fee asset price modifier is malformed. **Responsibility**: The proposer who broadcast the invalid checkpoint. ### 5. Proposed insufficient attestations[​](#5-proposed-insufficient-attestations "Direct link to 5. Proposed insufficient attestations") **What it is**: A proposer submitted a block to L1 without collecting enough valid committee attestations. **Detection criteria**: * Block published to L1 has fewer than 2/3 + 1 attestations from the committee. * Your node detects this through L1 block validation. **Responsibility**: The proposer who published the block. ### 6. Proposed incorrect attestations[​](#6-proposed-incorrect-attestations "Direct link to 6. Proposed incorrect attestations") **What it is**: A proposer submitted a block with invalid signatures, or signatures from non-committee members. **Detection criteria**: * Block contains attestations with invalid ECDSA signatures. * Block contains signatures from addresses not in the committee. **Responsibility**: The proposer who published the block. ### 7. Proposed descendant of checkpoint with invalid attestations[​](#7-proposed-descendant-of-checkpoint-with-invalid-attestations "Direct link to 7. Proposed descendant of checkpoint with invalid attestations") **What it is**: A proposer published a checkpoint to L1 that builds on an earlier checkpoint with invalid or insufficient attestations. **Detection criteria**: * Your node has previously identified a checkpoint as having invalid or insufficient attestations. * A later proposer publishes a descendant checkpoint to L1 on top of it. **Responsibility**: The proposer of the descendant checkpoint. Under pipelining, the next proposer may have started building optimistically before the prior checkpoint's signatures were submitted to L1, so only the proposer who actually publishes the descendant checkpoint to L1 is slashed. ### 8. Attested to invalid checkpoint proposal[​](#8-attested-to-invalid-checkpoint-proposal "Direct link to 8. Attested to invalid checkpoint proposal") **What it is**: A committee member attested to a checkpoint proposal in a slot where your node detected a slashable invalid block proposal. **Detection criteria**: * Your node detected an invalid block proposal for the slot via re-execution. * A committee member subsequently attested to a checkpoint covering that slot. **Responsibility**: Committee members who attested in the invalid proposal slot. ### 9. Duplicate proposal[​](#9-duplicate-proposal "Direct link to 9. Duplicate proposal") **What it is**: A proposer broadcast multiple block or checkpoint proposals for the same position with different content (equivocation). **Detection criteria**: Detected at the P2P layer by the AttestationPool, which tracks proposals by position (slot plus `indexWithinCheckpoint` for blocks, or slot for checkpoints). A second proposal for the same position with a different archive flags the duplicate. **Responsibility**: The proposer who broadcast the duplicate proposal. ### 10. Duplicate attestation[​](#10-duplicate-attestation "Direct link to 10. Duplicate attestation") **What it is**: A validator signed attestations for different proposals at the same slot (equivocation). **Detection criteria**: Detected at the P2P layer when conflicting attestations are observed from the same signer for the same slot. **Responsibility**: The attestor. ## Configuring Your Sequencer for Slashing[​](#configuring-your-sequencer-for-slashing "Direct link to Configuring Your Sequencer for Slashing") The slashing module runs automatically when your sequencer is enabled. ### Excluding Validators from Slashing[​](#excluding-validators-from-slashing "Direct link to Excluding Validators from Slashing") You can configure your node to always or never slash specific validators: ``` # Always slash these validators (regardless of detected offenses) SLASH_VALIDATORS_ALWAYS=0x1234...,0x5678... # Never slash these validators (even if offenses are detected) SLASH_VALIDATORS_NEVER=0xabcd...,0xef01... ``` **Note**: Validators in `SLASH_VALIDATORS_NEVER` take priority. If a validator appears in both lists, they won't be slashed. **Automatic protection**: Your own validator addresses (from your keystore) are automatically added to `SLASH_VALIDATORS_NEVER` unless you set `slashSelfAllowed=true` via the node admin API. ### Verify Your Configuration[​](#verify-your-configuration "Direct link to Verify Your Configuration") Check your current slashing configuration: **CLI Method**: ``` curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{ "jsonrpc":"2.0", "method":"aztecAdmin_getConfig", "id":1 }' ``` **Docker Method**: ``` docker exec -it aztec-sequencer curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{ "jsonrpc":"2.0", "method":"aztecAdmin_getConfig", "id":1 }' ``` ## Automatic Slashing[​](#automatic-slashing "Direct link to Automatic Slashing") Your sequencer handles slashing automatically: ### 1. Continuous Offense Detection[​](#1-continuous-offense-detection "Direct link to 1. Continuous Offense Detection") Watchers run in the background, monitoring: * Validator attestations and proposals via the Sentinel (when enabled) * Invalid block and checkpoint proposals from the P2P network * Transaction data availability after each checkpoint * L1 block data for attestation validation * Equivocation (duplicate proposals and attestations) on the P2P network ### 2. Offense Storage[​](#2-offense-storage "Direct link to 2. Offense Storage") When a watcher detects an offense, it's automatically stored with: * Validator address * Offense type * Epoch or slot number * Penalty amount Offenses are kept until they're voted on or expire after the configured number of rounds. ### 3. Automatic Voting[​](#3-automatic-voting "Direct link to 3. Automatic Voting") When you're selected as a block proposer: 1. Your sequencer retrieves offenses from 2 rounds ago (the slashing offset) 2. It filters out validators in your `SLASH_VALIDATORS_NEVER` list 3. It adds synthetic offenses for validators in your `SLASH_VALIDATORS_ALWAYS` list 4. Votes are encoded as a byte array, with each validator's vote represented by two bits specifying the proposed slash amount (0–3 units) 5. The votes are submitted to L1 as part of your proposal transaction **You don't need to take any manual action** - this happens automatically during block proposal. ### 4. Round Execution[​](#4-round-execution "Direct link to 4. Round Execution") When slashing rounds become executable (after the execution delay): * Your sequencer checks if there are rounds ready to execute * If you're the proposer and a round is ready, your node includes the execution call in your proposal * This triggers the L1 contract to tally votes and slash validators that reached quorum ## Understanding the Slashing Vetoer[​](#understanding-the-slashing-vetoer "Direct link to Understanding the Slashing Vetoer") The slashing vetoer is an independent security group that can pause slashing to protect validators from unfair slashing due to software bugs. **Execution Delay**: All slashing proposals have an execution delay during which the vetoer can review and potentially block execution: \~5 hours on the v5 testnet (2 rounds), and \~3 days on mainnet (28 rounds). **Temporary Disable**: The vetoer can disable all slashing for up to 3 days if needed, with the ability to extend this period. **Purpose**: This failsafe protects sequencers from being unfairly slashed due to client software bugs or network issues that might cause false positives in offense detection. ## Ejection from the Validator Set[​](#ejection-from-the-validator-set "Direct link to Ejection from the Validator Set") If a slash would drop a validator's stake below the rollup's **local ejection threshold**, the validator's entire remaining stake is withdrawn instead of just the slashed amount: the slash is burned and the remainder is sent to their registered withdrawer address after the exit delay. **Local Ejection Threshold**: 199,000 tokens on the v5 testnet, just below the 200,000 token Activation Threshold. This is a per-rollup parameter; mainnet uses 190,000 tokens (95% of the Activation Threshold). With the testnet's full-stake slash amounts (100,000 tokens for small offenses, 250,000 for medium and large), a validator that joined at the Activation Threshold is ejected by its first offense of any tier, since even a 100,000 token slash drops its stake below the 199,000 token local ejection threshold. A separate protocol-level ejection threshold (100,000 tokens, 50% of the Activation Threshold) applies to all stake withdrawals at the GSE level, but with these parameters the local ejection threshold is the one that triggers ejection from slashing. ## Monitoring Slashing Activity[​](#monitoring-slashing-activity "Direct link to Monitoring Slashing Activity") ### Check Pending Offenses[​](#check-pending-offenses "Direct link to Check Pending Offenses") Monitor offenses your node has detected but not yet voted on by checking your node logs: ``` # Look for these log messages grep "Adding pending offense" /path/to/node/logs grep "Voting to slash" /path/to/node/logs ``` ### View Executed Slashing Rounds[​](#view-executed-slashing-rounds "Direct link to View Executed Slashing Rounds") Your node logs when slashing rounds are executed: ``` grep "Slashing round.*has been executed" /path/to/node/logs ``` ### Query L1 Contract State[​](#query-l1-contract-state "Direct link to Query L1 Contract State") You can query the SlashingProposer contract to see voting activity: ``` # Get current round information cast call [SLASHING_PROPOSER_ADDRESS] \ "getCurrentRound()" \ --rpc-url [YOUR_RPC_URL] # Check a specific round's vote count cast call [SLASHING_PROPOSER_ADDRESS] \ "getRound(uint256)" [ROUND_NUMBER] \ --rpc-url [YOUR_RPC_URL] ``` ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Slashing Module Not Running[​](#slashing-module-not-running "Direct link to Slashing Module Not Running") **Symptom**: No slashing-related logs appear in your node output. **Solutions**: 1. Verify your node is running as a validator (not just an observer) 2. Check that `disableValidator` is not set to `true` in your config 3. Confirm the rollup contract has a slashing proposer configured 4. Restart your node and check for errors during slasher initialization ### Inactivity Offenses Not Detected[​](#inactivity-offenses-not-detected "Direct link to Inactivity Offenses Not Detected") **Symptom**: Your node doesn't detect inactivity offenses even when validators miss attestations. **Solutions**: 1. Enable the Sentinel: Set `SENTINEL_ENABLED=true` 2. Verify Sentinel is tracking data: Check logs for "Sentinel" messages 3. Ensure `SLASH_INACTIVITY_PENALTY` is greater than 0 4. Check that `SENTINEL_HISTORY_LENGTH_IN_EPOCHS` is configured appropriately (see configuration section) 5. Remember: Validators need to be inactive for consecutive epochs (threshold: 2 by default) ### Own Validators Being Slashed[​](#own-validators-being-slashed "Direct link to Own Validators Being Slashed") **Symptom**: Your node is voting to slash your own validators. **Solutions**: 1. Verify that `slashSelfAllowed` is not set to `true` 2. Check that your validator addresses from the keystore are being automatically added to `SLASH_VALIDATORS_NEVER` 3. Manually add your addresses to `SLASH_VALIDATORS_NEVER` as a safeguard: ``` SLASH_VALIDATORS_NEVER=0xYourAddress1,0xYourAddress2 ``` ### Penalty Amounts Not Matching L1[​](#penalty-amounts-not-matching-l1 "Direct link to Penalty Amounts Not Matching L1") **Symptom**: Your configured penalties don't result in slashing on L1. **Solutions**: 1. On the v5 testnet, penalties use the AZIP-16 full-stake preset: `100000000000000000000000` (100,000 tokens) for small offenses and `250000000000000000000000` (250,000 tokens) for medium and large 2. Verify your penalty configuration matches the default values shown in the Environment Variables section ## Best Practices[​](#best-practices "Direct link to Best Practices") **Enable the Sentinel**: If you want to participate in inactivity slashing, make sure `SENTINEL_ENABLED=true`. This is the only way to detect validators who go offline. **Use Grace Periods**: Set `SLASH_GRACE_PERIOD_L2_SLOTS` to avoid slashing validators during the initial network bootstrap period when issues are more likely. **Monitor Your Offenses**: Regularly check your logs to see what offenses your node is detecting and voting on. This helps you verify your slashing configuration is working as expected. **Don't Disable Default Protections**: Unless you explicitly want to slash your own validators, keep `slashSelfAllowed` at its default (`false`) to avoid accidentally voting against yourself. **Understand the Impact**: Remember that slashing is permanent and affects validators' stake. Only configure `SLASH_VALIDATORS_ALWAYS` for validators you have strong evidence of malicious behavior. **Stay Updated**: Monitor Aztec Discord and governance proposals for changes to slashing parameters or new offense types being added to the protocol. ## Summary[​](#summary "Direct link to Summary") As a sequencer operator: 1. **Slashing is automatic**: Your sequencer detects offenses and votes during block proposals without manual intervention 2. **Configuration is flexible**: Use environment variables or runtime API calls to adjust penalties and behavior 3. **Safety mechanisms exist**: Grace periods, vetoer controls, and automatic self-protection prevent unfair slashing 4. **Monitoring is important**: Check logs and L1 state to ensure your slasher is operating as expected ## Next Steps[​](#next-steps "Direct link to Next Steps") * Review [Governance and Proposal Process](/operate/testnet/operators/sequencer-management/creating_and_voting_on_proposals.md) to understand how slashing parameters can be changed * Set up [monitoring](/operate/testnet/operators/monitoring.md) to track your sequencer's slashing activity * Join the [Aztec Discord](https://discord.gg/aztec) to discuss slashing behavior and network health with other operators --- # Useful Commands ## Overview[​](#overview "Direct link to Overview") This reference provides commands for common sequencer operator tasks. You'll use Foundry's `cast` command to query onchain contract state, check sequencer status, and monitor governance processes. If you need help with something not covered here, visit the [Aztec Discord](https://discord.gg/aztec) in the `#operator-faq` channel. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before using these commands, ensure you have: * **Foundry installed** with the `cast` command available ([installation guide](https://book.getfoundry.sh/getting-started/installation)) * **Aztec CLI tool** installed (see [prerequisites guide](/operate/testnet/operators/prerequisites.md#aztec-toolchain)) * **Ethereum RPC endpoint** (execution layer) for the network you're querying * **Contract addresses** for your deployment (Registry, Rollup, Governance) ## Getting Started[​](#getting-started "Direct link to Getting Started") ### Set Up Your Environment[​](#set-up-your-environment "Direct link to Set Up Your Environment") For convenience, set your RPC URL as an environment variable: ``` export RPC_URL="https://your-ethereum-rpc-endpoint.com" ``` All examples below use `--rpc-url $RPC_URL`. In production, always include this flag with your actual RPC endpoint. ### Understanding Deployments[​](#understanding-deployments "Direct link to Understanding Deployments") Assume there are multiple deployments of Aztec, such as `testnet` and `ignition-testnet`. Each deployment has a unique Registry contract address that remains constant across upgrades. If a governance upgrade deploys a new rollup contract, the Registry contract address stays the same. ### Find the Registry Contract Address[​](#find-the-registry-contract-address "Direct link to Find the Registry Contract Address") The Registry contract is your entrypoint to all other contracts for a specific deployment. You'll need this address to discover other contract addresses. Contact the Aztec team or check the documentation for the Registry contract address for your target network (testnet, ignition-testnet, etc.). ### Get the Rollup Contract Address[​](#get-the-rollup-contract-address "Direct link to Get the Rollup Contract Address") Once you have the Registry address, retrieve the Rollup contract: ``` cast call [REGISTRY_CONTRACT_ADDRESS] "getCanonicalRollup()" --rpc-url $RPC_URL ``` Replace `[REGISTRY_CONTRACT_ADDRESS]` with your actual Registry contract address. **Example:** ``` cast call 0x1234567890abcdef1234567890abcdef12345678 "getCanonicalRollup()" --rpc-url $RPC_URL ``` This returns the Rollup contract address in hexadecimal format. ## Query the Sequencer Set[​](#query-the-sequencer-set "Direct link to Query the Sequencer Set") ### Get the GSE Contract Address[​](#get-the-gse-contract-address "Direct link to Get the GSE Contract Address") The GSE (Governance Staking Escrow) contract manages sequencer registrations and balances. Get its address from the Rollup contract: ``` cast call [ROLLUP_ADDRESS] "getGSE()" --rpc-url $RPC_URL ``` This returns the GSE contract address, which you'll need for some queries below. ### Count Active Sequencers[​](#count-active-sequencers "Direct link to Count Active Sequencers") Get the total number of active sequencers in the set: ``` cast call [ROLLUP_ADDRESS] "getActiveAttesterCount()" --rpc-url $RPC_URL ``` This returns the count of currently active sequencers as a hexadecimal number. ### List Sequencers by Index[​](#list-sequencers-by-index "Direct link to List Sequencers by Index") Retrieve individual sequencer addresses by their index (0-based): ``` cast call [ROLLUP_ADDRESS] "getAttesterAtIndex(uint256)" [INDEX] --rpc-url $RPC_URL ``` Replace: * `[ROLLUP_ADDRESS]` - Your Rollup contract address * `[INDEX]` - The index of the sequencer (starting from 0) **Example:** ``` # Get the first sequencer (index 0) cast call 0xabcdef1234567890abcdef1234567890abcdef12 "getAttesterAtIndex(uint256)" 0 --rpc-url $RPC_URL # Get the second sequencer (index 1) cast call 0xabcdef1234567890abcdef1234567890abcdef12 "getAttesterAtIndex(uint256)" 1 --rpc-url $RPC_URL ``` ### Check Sequencer Status[​](#check-sequencer-status "Direct link to Check Sequencer Status") Query the complete status and information for a specific sequencer: ``` cast call [ROLLUP_ADDRESS] "getAttesterView(address)" [ATTESTER_ADDRESS] --rpc-url $RPC_URL ``` Replace: * `[ROLLUP_ADDRESS]` - Your Rollup contract address * `[ATTESTER_ADDRESS]` - The sequencer's attester address you want to check **Example:** ``` cast call 0xabcdef1234567890abcdef1234567890abcdef12 "getAttesterView(address)" 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb --rpc-url $RPC_URL ``` ### Interpret the Response[​](#interpret-the-response "Direct link to Interpret the Response") The `getAttesterView` command returns an `AttesterView` struct containing: 1. **status** - The sequencer's current status code (see Status Codes below) 2. **effectiveBalance** - The sequencer's effective stake balance 3. **exit** - Exit information struct (if the sequencer is exiting): * `withdrawalId` - Withdrawal ID in the GSE contract * `amount` - Amount being withdrawn * `exitableAt` - Timestamp when withdrawal can be finalized * `recipientOrWithdrawer` - Address that receives funds or can initiate withdrawal * `isRecipient` - Whether the exit has a recipient set * `exists` - Whether an exit exists 4. **config** - Attester configuration struct: * `publicKey` - BLS public key (G1 point with x and y coordinates) * `withdrawer` - Address authorized to withdraw stake ### Get Individual Sequencer Information[​](#get-individual-sequencer-information "Direct link to Get Individual Sequencer Information") Query specific pieces of information using the GSE contract: ``` # Check if a sequencer is registered cast call [GSE_ADDRESS] "isRegistered(address,address)" [ROLLUP_ADDRESS] [ATTESTER_ADDRESS] --rpc-url $RPC_URL # Get sequencer's balance on this rollup instance cast call [GSE_ADDRESS] "balanceOf(address,address)" [ROLLUP_ADDRESS] [ATTESTER_ADDRESS] --rpc-url $RPC_URL # Get sequencer's effective balance (includes bonus if latest rollup) cast call [GSE_ADDRESS] "effectiveBalanceOf(address,address)" [ROLLUP_ADDRESS] [ATTESTER_ADDRESS] --rpc-url $RPC_URL # Get sequencer's configuration (withdrawer and public key) cast call [ROLLUP_ADDRESS] "getConfig(address)" [ATTESTER_ADDRESS] --rpc-url $RPC_URL # Get only the status cast call [ROLLUP_ADDRESS] "getStatus(address)" [ATTESTER_ADDRESS] --rpc-url $RPC_URL ``` ### Status Codes[​](#status-codes "Direct link to Status Codes") | Status | Name | Meaning | | ------ | ---------- | ------------------------------------------------------------------------------------------------------------------------------- | | 0 | NONE | The sequencer does not exist in the sequencer set | | 1 | VALIDATING | The sequencer is currently active and participating in consensus | | 2 | ZOMBIE | The sequencer is not active (balance fell below ejection threshold, possibly due to slashing) but still has funds in the system | | 3 | EXITING | The sequencer has initiated withdrawal and is in the exit delay period | ## Governance Operations[​](#governance-operations "Direct link to Governance Operations") ### Get Governance Contract Addresses[​](#get-governance-contract-addresses "Direct link to Get Governance Contract Addresses") First, get the Governance contract from the Registry, then query it for the GovernanceProposer contract: ``` # Get the Governance contract cast call [REGISTRY_ADDRESS] "getGovernance()" --rpc-url $RPC_URL # Get the GovernanceProposer contract cast call [GOVERNANCE_ADDRESS] "governanceProposer()" --rpc-url $RPC_URL ``` Replace `[REGISTRY_ADDRESS]` and `[GOVERNANCE_ADDRESS]` with your actual addresses. ### Check Governance Quorum Requirements[​](#check-governance-quorum-requirements "Direct link to Check Governance Quorum Requirements") Query the quorum parameters for the governance system: ``` # Get the signaling round size (in L2 slots) cast call [GOVERNANCE_PROPOSER_ADDRESS] "ROUND_SIZE()" --rpc-url $RPC_URL # Get the number of signals required for quorum in any single round cast call [GOVERNANCE_PROPOSER_ADDRESS] "QUORUM_SIZE()" --rpc-url $RPC_URL ``` **What these values mean:** * **ROUND\_SIZE()** - The size of any signaling round, measured in L2 slots (e.g., 1000 slots on mainnet) * **QUORUM\_SIZE()** - The number of signals needed within a round for a payload to reach quorum (e.g., 600 signals on mainnet, which is 60% of ROUND\_SIZE) ### Find the Current Round Number[​](#find-the-current-round-number "Direct link to Find the Current Round Number") Calculate which governance round corresponds to a specific L2 slot: ``` cast call [GOVERNANCE_PROPOSER_ADDRESS] "computeRound(uint256)" [SLOT_NUMBER] --rpc-url $RPC_URL ``` Replace: * `[GOVERNANCE_PROPOSER_ADDRESS]` - Your GovernanceProposer contract address * `[SLOT_NUMBER]` - The L2 slot number you want to check This returns the round number in hexadecimal format. Convert it to decimal for use in the next command. **Example:** ``` # Check which round slot 5000 belongs to cast call 0x9876543210abcdef9876543210abcdef98765432 "computeRound(uint256)" 5000 --rpc-url $RPC_URL # Output: 0x0000000000000000000000000000000000000000000000000000000000000005 (round 5) ``` ### Check Signal Count for a Payload[​](#check-signal-count-for-a-payload "Direct link to Check Signal Count for a Payload") Check how many sequencers have signaled support for a specific payload in a given round: ``` cast call [GOVERNANCE_PROPOSER_ADDRESS] "signalCount(address,uint256,address)" [ROLLUP_ADDRESS] [ROUND_NUMBER] [PAYLOAD_ADDRESS] --rpc-url $RPC_URL ``` Replace: * `[GOVERNANCE_PROPOSER_ADDRESS]` - Your GovernanceProposer contract address * `[ROLLUP_ADDRESS]` - Your Rollup contract address * `[ROUND_NUMBER]` - The round number as a decimal integer (not hex) * `[PAYLOAD_ADDRESS]` - The address of the payload contract you're checking **Example:** ``` cast call 0x9876543210abcdef9876543210abcdef98765432 "signalCount(address,uint256,address)" 0xabcdef1234567890abcdef1234567890abcdef12 5 0x1111111111111111111111111111111111111111 --rpc-url $RPC_URL ``` This returns the number of signals the payload has received in that round. Compare this to the quorum threshold (QUORUM\_SIZE) to determine if the payload can be promoted to a proposal. ### Get Current Proposal Count[​](#get-current-proposal-count "Direct link to Get Current Proposal Count") Check how many governance proposals exist: ``` cast call [GOVERNANCE_CONTRACT_ADDRESS] "proposalCount()" --rpc-url $RPC_URL ``` ### Query a Specific Proposal[​](#query-a-specific-proposal "Direct link to Query a Specific Proposal") Get details about a specific proposal: ``` cast call [GOVERNANCE_CONTRACT_ADDRESS] "getProposal(uint256)" [PROPOSAL_ID] --rpc-url $RPC_URL ``` Replace: * `[GOVERNANCE_CONTRACT_ADDRESS]` - Your Governance contract address * `[PROPOSAL_ID]` - The proposal ID (zero-indexed, so the first proposal is 0) This returns the proposal struct containing: * Payload address * Creation timestamp * Voting start and end times * Current vote tallies ## Tips and Best Practices[​](#tips-and-best-practices "Direct link to Tips and Best Practices") ### Using Etherscan[​](#using-etherscan "Direct link to Using Etherscan") You can also query these contracts through Etherscan's "Read Contract" interface: 1. Navigate to the contract address on Etherscan 2. Go to the "Contract" tab 3. Click "Read Contract" or "Read as Proxy" 4. Find the function you want to call and enter parameters This provides a user-friendly interface without requiring command-line tools. ### Monitoring Automation[​](#monitoring-automation "Direct link to Monitoring Automation") Consider creating scripts that regularly query sequencer status and governance signals. This helps you: * Track your sequencer's health * Monitor governance proposals you care about * Receive alerts when action is needed ### Decoding Hex Output[​](#decoding-hex-output "Direct link to Decoding Hex Output") Some commands return hexadecimal values. Use `cast` to convert them: ``` # Convert hex to decimal cast --to-dec 0x03e8 # Convert hex to address format cast --to-address 0x000000000000000000000000742d35Cc6634C0532925a3b844Bc9e7595f0bEb ``` ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### "Invalid JSON RPC response"[​](#invalid-json-rpc-response "Direct link to \"Invalid JSON RPC response\"") **Issue**: Command fails with JSON RPC error. **Solutions**: * Verify your RPC endpoint is accessible and correct * Check that you're connected to the right network (Sepolia for testnet) * Ensure your RPC provider supports the `eth_call` method * Try a different RPC endpoint ### "Reverted" or "Execution reverted"[​](#reverted-or-execution-reverted "Direct link to \"Reverted\" or \"Execution reverted\"") **Issue**: Contract call reverts. **Solutions**: * Verify the contract address is correct * Check that the function signature matches the contract's ABI * Ensure you're passing the correct parameter types * Verify the contract is deployed on the network you're querying ### "Could not find function"[​](#could-not-find-function "Direct link to \"Could not find function\"") **Issue**: Function not found in contract. **Solutions**: * Verify the function name spelling and capitalization * Check that you're querying the correct contract * Ensure the contract version matches the function you're calling * Try querying through Etherscan to verify the contract ABI ## Next Steps[​](#next-steps "Direct link to Next Steps") * [Learn about sequencer setup](/operate/testnet/operators/setup/sequencer_management.md) to operate your sequencer node * [Participate in governance](/operate/testnet/operators/sequencer-management/creating_and_voting_on_proposals.md) by signaling, voting, and creating proposals * [Monitor your node](/operate/testnet/operators/monitoring.md) with metrics and observability tools * Join the [Aztec Discord](https://discord.gg/aztec) for operator support and community discussions --- # Become a Staking Provider ## Overview[​](#overview "Direct link to Overview") This guide covers running a sequencer with delegated stake on the Aztec network. Unlike conventional setups where you must have your own stake, delegated stake lets you (the "provider") operate sequencers backed by tokens from delegators. **This is a non-custodial system**: Delegators retain full control and ownership of their tokens at all times. You never take custody of the delegated tokens—they remain in the delegator's control while providing economic backing for your sequencer operations. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before proceeding, ensure you have: * Knowledge of running a sequencer node (see [Sequencer Setup Guide](/operate/testnet/operators/setup/sequencer_management.md)) * An Ethereum wallet with sufficient ETH for gas fees * Understanding of basic Aztec staking mechanics * Foundry installed for `cast` commands * Aztec CLI v5.0.0-rc.2 or later installed: ``` VERSION=5.0.0-rc.2 bash -i <(curl -sL https://install.aztec.network/5.0.0-rc.2) ``` ### Contract Addresses[​](#contract-addresses "Direct link to Contract Addresses") For Staking Registry and GSE (Governance Staking Escrow) addresses, see the [Networks page](/networks.md#l1-contract-addresses). ## How Delegated Stake Works[​](#how-delegated-stake-works "Direct link to How Delegated Stake Works") You register with the StakingRegistry contract and add sequencer identities (keystores) to a queue. When delegators stake to your provider, the system: 1. Dequeues one keystore from your provider queue 2. Creates a [Split contract](https://docs.splits.org/core/split) for reward distribution 3. Registers the sequencer into the staking queue using the dequeued keystore ### Reward Distribution[​](#reward-distribution "Direct link to Reward Distribution") When a delegator stakes to your provider, a Split contract is automatically created to manage reward distribution. You configure your sequencer to use the Split contract address as the coinbase (see [After Delegation: Configure Sequencer Coinbase](#after-delegation-configure-sequencer-coinbase)). Rewards are distributed according to your agreed commission rate: * **Provider commission**: Your `providerRewardsRecipient` address receives your commission rate (e.g., 5% for 500 basis points) * **Delegator rewards**: The delegator's Aztec Token Vault (ATV) receives the remaining percentage **Rewards flow:** 1. Rewards accumulate in the rollup under the coinbase address (the Split contract) 2. After governance unlocks rewards, anyone can release them from the rollup to the `coinbase` address. 3. Anyone can then disperse the rewards from the Split contract to both the ATV and your `providerRewardsRecipient` This design ensures delegators maintain control of their rewards while you earn commission for operating the sequencer infrastructure. ## Setup Process[​](#setup-process "Direct link to Setup Process") Before starting these steps, ensure your sequencer node infrastructure is set up (see [Prerequisites](#prerequisites)). Follow these steps to set up delegated stake: 1. Register your provider with the Staking Registry 2. Add sequencer identities to your provider queue 3. Set your metadata in the GitHub repo **After a delegator stakes:** Configure your sequencer's coinbase (see [After Delegation](#after-delegation-configure-sequencer-coinbase)) ### Step 1: Register Your Provider[​](#step-1-register-your-provider "Direct link to Step 1: Register Your Provider") Register with the `StakingRegistry` contract as a provider for delegated staking. Registration is permissionless and open to anyone. **Function signature:** ``` function registerProvider( address _providerAdmin, uint16 _providerTakeRate, address _providerRewardsRecipient ) external returns (uint256); ``` **Parameters:** * `_providerAdmin`: Address that can update provider configuration * `_providerTakeRate`: Commission rate in basis points (500 = 5%) * `_providerRewardsRecipient`: Address receiving commission payments **Returns:** Your unique `providerIdentifier`. Save this—you'll need it for all provider operations. **Example:** ``` # Register a provider with 5% commission rate cast send $STAKING_REGISTRY_ADDRESS \ "registerProvider(address,uint16,address)" \ $PROVIDER_ADMIN_ADDRESS \ 500 \ $REWARDS_RECIPIENT_ADDRESS \ --rpc-url $RPC_URL \ --private-key $YOUR_PRIVATE_KEY ``` ### Extracting Your Provider ID[​](#extracting-your-provider-id "Direct link to Extracting Your Provider ID") Once the transaction is confirmed, you need to extract your `providerIdentifier` from the transaction logs. The provider ID is emitted as the second topic in the registration event log. **Method 1: Using cast receipt** ``` cast receipt [TX_HASH] --rpc-url $RPC_URL | grep "return" | awk '{print $2}' | xargs cast to-dec ``` **Method 2: From transaction logs** The transaction receipt will contain one log where the second topic is your `providerId` in hex format: ``` # Example log output logs [{"address":"0xc3860c45e5f0b1ef3000dbf93149756f16928adb", "topics":["0x43fe1b4477c9a580955f586c904f4670929e184ef4bef4936221c52d0a79a75b", "0x0000000000000000000000000000000000000000000000000000000000000002", # This is your providerId "0x000000000000000000000000efdb4c5f3a2f04e0cb393725bcae2dd675cc3718", "0x00000000000000000000000000000000000000000000000000000000000001f4"], ... }] ``` Convert the hex value to decimal: ``` cast to-dec 0x0000000000000000000000000000000000000000000000000000000000000002 # Output: 2 ``` **Save your `providerIdentifier`**—you'll need it for all subsequent provider operations. ### Step 2: Add Sequencer Identities[​](#step-2-add-sequencer-identities "Direct link to Step 2: Add Sequencer Identities") Add sequencer identities (keystores) to your provider queue. Each keystore represents one sequencer that can be activated when a delegator stakes to you. **Function signature:** ``` function addKeysToProvider( uint256 _providerIdentifier, KeyStore[] calldata _keyStores ) external; ``` **Parameters:** * `_providerIdentifier`: Your provider identifier from registration * `_keyStores`: Array of keystore structures (max 100 per transaction) **KeyStore structure:** ``` struct KeyStore { address attester; // Sequencer's address BN254Lib.G1Point publicKeyG1; // BLS public key (G1) BN254Lib.G2Point publicKeyG2; // BLS public key (G2) BN254Lib.G1Point proofOfPossession; // BLS signature (prevents rogue key attacks) } ``` Critical: Key Management for Delegated Staking **⚠️ If you run out of keys, users cannot delegate tokens to you.** The Staking Registry **DOES NOT** check for duplicate keys. Please take **EXTREME** care when registering keys: * Duplicate keys will cause delegation failures when that duplicate is at the top of your queue * The only way to fix this is by calling `dripProviderQueue(_providerIdentifier, _numberOfKeysToDrip)` to remove the duplicate * Always verify keys before registration to avoid user experience issues ### Generating Keys for Registration[​](#generating-keys-for-registration "Direct link to Generating Keys for Registration") Use the `aztec validator-keys` command with the `--staker-output` flag to automatically generate properly formatted registration data: ``` aztec validator-keys new \ --fee-recipient 0x0000000000000000000000000000000000000000000000000000000000000000 \ --staker-output \ --gse-address 0xb6a38a51a6c1de9012f9d8ea9745ef957212eaac \ --l1-rpc-urls $ETH_RPC \ --l1-chain-id 11155111 ``` This command automatically: 1. Generates the private keystore with ETH and BLS keys 2. Generates the public keystore with G1 and G2 public keys 3. Generates the proof of possession signature 4. Outputs the data in the correct format for the `addKeysToProvider` function The public keystore file (`keyN_staker_output.json`) contains the data you'll use for provider registration. For more details on keystore creation, see the [Sequencer Setup Guide](/operate/testnet/operators/setup/sequencer_management.md#generating-keys). ### Building the Registration Command[​](#building-the-registration-command "Direct link to Building the Registration Command") You have two options for constructing the `addKeysToProvider` command: **Option 1: Use the helper script (Recommended)** Use this helper script to automatically build the command from your `validator-keys` output: The script reads the JSON output from `validator-keys staker` and constructs the properly formatted `cast send` command. **Option 2: Manual construction** If you need to manually construct the command, the function signature is: ``` addKeysToProvider(uint256,(address,(uint256,uint256),(uint256,uint256,uint256,uint256),(uint256,uint256))[]) ``` **Parameters:** * First `uint256`: Your provider identifier (from registration in Step 1) * Tuple array: `KeyStore[]` where each element contains: * `address`: Sequencer address * `(uint256,uint256)`: publicKeyG1 (x, y coordinates) * `(uint256,uint256,uint256,uint256)`: publicKeyG2 (x0, x1, y0, y1 coordinates) * `(uint256,uint256)`: proofOfPossession (x, y coordinates) Example with placeholder values: ``` cast send $STAKING_REGISTRY_ADDRESS \ "addKeysToProvider(uint256,(address,(uint256,uint256),(uint256,uint256,uint256,uint256),(uint256,uint256))[])" \ $YOUR_PROVIDER_IDENTIFIER \ "[(0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb,(12345,67890),(11111,22222,33333,44444),(98765,43210))]" \ --rpc-url $RPC_URL \ --private-key $ADMIN_PRIVATE_KEY ``` **Important:** * Replace all values above with actual data from `aztec validator-keys new --staker-output` * Add a maximum of 100 keystores per transaction to avoid gas limit issues * Verify each keystore is unique before adding to prevent duplicate key issues ### Step 3: Set Your Metadata[​](#step-3-set-your-metadata "Direct link to Step 3: Set Your Metadata") To be featured on the staking dashboard, submit metadata about your provider. **Required metadata:** * Provider name and description * Contact email * Logo image (PNG or SVG, recommended size: 256x256px) * Website URL * Discord username * Your `providerIdentifier` **Submission process:** 1. Copy [`_example.json`](https://github.com/AztecProtocol/staking-dashboard/blob/master/providers-testnet/_example.json) from the [`providers-testnet`](https://github.com/AztecProtocol/staking-dashboard/tree/master/providers-testnet) folder in the [staking-dashboard GitHub repository](https://github.com/AztecProtocol/staking-dashboard). 2. Rename it to `{providerId}-{your-provider-name}.json` (e.g. `42-my-provider.json`), where `providerId` matches your on chain registration. 3. Fill in your metadata and open a pull request adding the file to the `providers-testnet` folder. The JSON file should follow this format: ``` { "providerId": 0, "providerName": "", "providerDescription": "", "providerEmail": "", "providerWebsite": "", "providerLogoUrl": "", "discordUsername": "", "providerSelfStake": ["0x..."] } ``` The `providerId` must match your on chain registration and be unique across all submissions. The `providerSelfStake` field is an optional array of attester addresses for sequencers receiving direct provider funding. Good metadata helps delegators understand your offering and builds trust. ## After Delegation: Configure Sequencer Coinbase[​](#after-delegation-configure-sequencer-coinbase "Direct link to After Delegation: Configure Sequencer Coinbase") Once a delegator stakes to your provider, the system creates a Split contract for that delegation and activates the corresponding sequencer. **Configure the sequencer to use the Split contract address as the coinbase.** ### Why This Matters[​](#why-this-matters "Direct link to Why This Matters") The coinbase address determines where your sequencer's block rewards are sent. Setting it to the Split contract address ensures rewards are distributed according to your agreed commission rate, which is critical for maintaining trust with your delegators. ### How to Configure the Coinbase[​](#how-to-configure-the-coinbase "Direct link to How to Configure the Coinbase") Update the `coinbase` field in your sequencer node's keystore configuration to the Split contract address created for this delegation. **Example keystore configuration:** ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "0x...", // Your Ethereum sequencer private key "bls": "0x..." // Your BLS sequencer private key }, "publisher": ["0x..."], // Address that submits blocks to L1 "coinbase": "0x[SPLIT_CONTRACT_ADDRESS]", // Split contract for this delegation "feeRecipient": "0x0000000000000000000000000000000000000000000000000000000000000000" // Not currently used, set to all zeros } ] } ``` Replace `[SPLIT_CONTRACT_ADDRESS]` with the actual Split contract address created for this delegation. You can find this address in the staking dashboard (see "Finding Your Split Contract Address" below). For detailed information about keystore configuration, including different storage methods and advanced patterns, see the [Advanced Keystore Guide](/operate/testnet/operators/keystore.md). ### Finding Your Split Contract Address[​](#finding-your-split-contract-address "Direct link to Finding Your Split Contract Address") **You have to manually monitor the delegations you receive and update the `coinbase` address to the correct Split contract!** You can retrieve the Split contract address for a specific delegation through the **Staking Dashboard**: 1. Navigate to your provider dashboard on the staking dashboard 2. Look for the dropdown called **"Sequencer Registered (x)"** where x is the number of registered sequencers 3. Click on the dropdown to expand it 4. This shows the Sequencer address → Split contract relation 5. Set the Split contract as the `coinbase` for the respective Sequencer address on your node The dropdown will display a table showing which Split contract corresponds to each of your sequencer addresses, making it easy to configure the correct coinbase for each sequencer. **Manual monitoring approach:** Since coinbase configuration must be done manually, you should: * Regularly check the staking dashboard for new delegations * Set up alerts or scheduled checks (daily or more frequently during high activity) * Update keystore configurations promptly when new delegations appear * Maintain a record of which Split contracts map to which keystores ### Important Notes[​](#important-notes "Direct link to Important Notes") * **Monitor delegations actively**: The system does not automatically notify you of new delegations * Configure the coinbase immediately after each delegation to ensure rewards flow correctly from the start * Each delegation creates a unique Split contract—configure each sequencer with its specific Split contract address * Restart your sequencer node after updating the keystore for changes to take effect * Keep a mapping of sequencer addresses to Split contracts for operational tracking ## Monitoring Keystore Availability[​](#monitoring-keystore-availability "Direct link to Monitoring Keystore Availability") As a provider, you must maintain sufficient sequencer identities (keystores) in your queue to handle incoming delegations. When a delegator stakes to your provider and your queue is empty, they cannot activate a sequencer—this results in a poor delegator experience and lost opportunity. ### Why Monitoring Matters[​](#why-monitoring-matters "Direct link to Why Monitoring Matters") Each time a delegator stakes to your provider: 1. One keystore is dequeued from your provider queue 2. A sequencer is activated using that keystore 3. Your available keystore count decreases by one If your queue runs empty, new delegations cannot activate sequencers until you add more keystores. This could cause delegators to choose other providers. ### Checking Available Keystores[​](#checking-available-keystores "Direct link to Checking Available Keystores") Check your current keystore queue with this call: ```` # Check provider queue length cast call [STAKING_REGISTRY_ADDRESS] \ "getProviderQueueLength(uint256) (uint256)" \ [YOUR_PROVIDER_IDENTIFIER] \ --rpc-url [RPC_URL] This returns your provider's queue length, which is the number of keystores currently available. ### Setting Up Automated Monitoring Implement automated monitoring to alert you when your keystore queue runs low. #### Cron Job Example The following script monitors your keystore queue and alerts when it drops below a threshold. Replace the placeholder values and uncomment your preferred alert method (webhook or email): ```bash #!/bin/bash # check-keystores.sh THRESHOLD=5 # Alert when fewer than 5 keystores remain REGISTRY_ADDRESS="[STAKING_REGISTRY_ADDRESS]" PROVIDER_ID="[YOUR_PROVIDER_IDENTIFIER]" RPC_URL="[YOUR_RPC_URL]" WEBHOOK_URL="[YOUR_WEBHOOK_URL]" # Optional: for Slack/Discord notifications # Gets current queue length QUEUE_LENGTH=$(cast call "$REGISTRY_ADDRESS" \ "getProviderQueueLength(uint256)" \ "$PROVIDER_ID" \ --rpc-url "$RPC_URL") echo "Queue length: $QUEUE_LENGTH" # Check if queue is running low if [ "$QUEUE_LENGTH" -lt "$THRESHOLD" ]; then echo "WARNING: Keystore queue running low! Only $QUEUE_LENGTH keystores remaining." # Send alert (uncomment and configure your preferred method) # Slack/Discord webhook: # curl -X POST "$WEBHOOK_URL" -H "Content-Type: application/json" \ # -d "{\"text\":\"⚠️ Keystore queue low: $QUEUE_LENGTH remaining (threshold: $THRESHOLD)\"}" # Email via mail command: # echo "Keystore queue has $QUEUE_LENGTH keys remaining" | mail -s "Low Keystore Alert" your-email@example.com fi ```` Make the script executable and schedule it with cron: ``` # Make the script executable chmod +x /path/to/check-keystores.sh # Edit crontab crontab -e # Add this line to check every 4 hours 0 */4 * * * /path/to/check-keystores.sh >> /var/log/keystore-monitor.log 2>&1 ``` ### When to Add More Keystores[​](#when-to-add-more-keystores "Direct link to When to Add More Keystores") Add keystores proactively before running out: * Monitor your delegation growth rate * Add in batches (max 100 per transaction) * Stay ahead of demand during high-activity periods See [Step 2: Add Sequencer Identities](#step-2-add-sequencer-identities) for instructions. ## Managing Your Provider[​](#managing-your-provider "Direct link to Managing Your Provider") Update your provider configuration using these functions. All must be called from your `providerAdmin` address. ### Update Admin Address[​](#update-admin-address "Direct link to Update Admin Address") Transfer provider administration to a new address: ``` cast send [STAKING_REGISTRY_ADDRESS] \ "updateProviderAdmin(uint256,address)" \ [YOUR_PROVIDER_IDENTIFIER] \ [NEW_ADMIN_ADDRESS] \ --rpc-url [RPC_URL] \ --private-key [CURRENT_ADMIN_PRIVATE_KEY] ``` ### Update Rewards Recipient[​](#update-rewards-recipient "Direct link to Update Rewards Recipient") Change the address receiving commission payments: ``` cast send [STAKING_REGISTRY_ADDRESS] \ "updateProviderRewardsRecipient(uint256,address)" \ [YOUR_PROVIDER_IDENTIFIER] \ [NEW_REWARDS_RECIPIENT_ADDRESS] \ --rpc-url [RPC_URL] \ --private-key [ADMIN_PRIVATE_KEY] ``` ### Update Commission Rate[​](#update-commission-rate "Direct link to Update Commission Rate") Modify your commission rate (applies only to new delegations): ``` cast send [STAKING_REGISTRY_ADDRESS] \ "updateProviderTakeRate(uint256,uint16)" \ [YOUR_PROVIDER_IDENTIFIER] \ [NEW_RATE_BASIS_POINTS] \ --rpc-url [RPC_URL] \ --private-key [ADMIN_PRIVATE_KEY] ``` Commission Changes Only Apply to New Delegations When you update your commission rate, only **new delegations** will use the updated rate. **Existing delegations cannot be changed**—they permanently retain the original commission rate that was agreed upon when the delegation was created. ## Verification[​](#verification "Direct link to Verification") Verify your setup is working correctly. ### Check Provider Registration[​](#check-provider-registration "Direct link to Check Provider Registration") Query the StakingRegistry to confirm your provider details: ``` cast call [STAKING_REGISTRY_ADDRESS] \ "providerConfigurations(uint256) (address,uint16,address)" \ [YOUR_PROVIDER_IDENTIFIER] \ --rpc-url [RPC_URL] ``` This returns: 1. The provider's admin address 2. The provider's commission rate in bps 3. The provider's rewards recipient ### Verify Queue Length[​](#verify-queue-length "Direct link to Verify Queue Length") Check your provider queue length: ``` cast call [STAKING_REGISTRY_ADDRESS] \ "getProviderQueueLength(uint256)" \ [YOUR_PROVIDER_IDENTIFIER] \ --rpc-url [RPC_URL] ``` ### Monitor Delegations[​](#monitor-delegations "Direct link to Monitor Delegations") View these metrics on the staking dashboard: * Total stake delegated to your provider * Number of active sequencers * Commission earned * Provider performance metrics ### Confirm Node Operation[​](#confirm-node-operation "Direct link to Confirm Node Operation") Ensure your sequencer nodes are running and synced. See [Useful Commands](/operate/testnet/operators/sequencer-management/useful-commands.md) for commands to check sequencer status. ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Registration transaction fails[​](#registration-transaction-fails "Direct link to Registration transaction fails") **Issue**: The `registerProvider` transaction reverts or fails. **Solutions**: * Ensure your wallet has sufficient ETH for gas fees * Verify the StakingRegistry contract address is correct * Check that the commission rate is within acceptable bounds (typically 0-10000 basis points) * Review transaction logs for specific error messages using a block explorer ### Cannot add sequencer identities[​](#cannot-add-sequencer-identities "Direct link to Cannot add sequencer identities") **Issue**: The `addKeysToProvider` function fails. **Solutions**: * Confirm you're calling from the `providerAdmin` address * Verify your `providerIdentifier` is correct * Ensure BLS signatures in `KeyStore` are properly formatted (use the keystore creation utility) * Check that the sequencer addresses aren't already registered elsewhere * Reduce batch size if hitting gas limits (max 100 keystores per transaction) ### No delegators appearing[​](#no-delegators-appearing "Direct link to No delegators appearing") **Issue**: No delegators are staking to your provider. **Solutions**: * Verify your provider is visible on the staking dashboard * Complete all metadata fields to build trust * Ensure your commission rate is competitive with other providers * Confirm your sequencer nodes are operational and performing well * Engage with the community on Discord to build your reputation ### Commission not being received[​](#commission-not-being-received "Direct link to Commission not being received") **Issue**: Commission payments aren't arriving at the rewards recipient address. **Solutions**: * Verify the `providerRewardsRecipient` address is correct * Check that delegations are active and generating fees * Confirm your sequencers are producing blocks and earning fees * Allow time for reward distribution (may not be immediate) * Check the contract for pending distributions that need to be claimed ## Best Practices[​](#best-practices "Direct link to Best Practices") **Maintain Sufficient Keystores**: Set up automated monitoring to ensure your keystore queue never runs empty. See [Monitoring Keystore Availability](#monitoring-keystore-availability) for guidance on implementing alerts. **Communicate Changes**: Inform delegators about commission rate changes, planned maintenance, or infrastructure updates. Good communication builds trust. **Monitor Performance**: Track your sequencers' attestation rates, block proposals, and uptime. Poor performance may cause delegators to withdraw. **Secure Your Keys**: The `providerAdmin` key controls your provider configuration. Store it securely and consider using a hardware wallet or multisig. ## Next Steps[​](#next-steps "Direct link to Next Steps") After completing this setup: 1. Monitor your provider's performance through the staking dashboard 2. Maintain high uptime for your sequencer nodes 3. Keep open communication with delegators 4. Regularly add new keystores to your provider queue (see [Monitoring Keystore Availability](#monitoring-keystore-availability)) 5. Join the [Aztec Discord](https://discord.gg/aztec) for provider support and community discussions --- # Blob retrieval ## Overview[​](#overview "Direct link to Overview") Aztec uses EIP-4844 blobs to publish transaction data to Ethereum Layer 1. Since blob data is only available on L1 for a limited period (\~18 days / 4,096 epochs), nodes need reliable ways to store and retrieve blob data for synchronization and historical access. Aztec nodes can be configured to retrieve blobs from L1 consensus (beacon nodes), file stores (S3, GCS, R2), and archive services. Automatic Configuration When using `--network [NETWORK_NAME]`, blob file stores are automatically configured for you. Most users don't need to manually configure blob storage. Override Behavior Setting the `BLOB_FILE_STORE_URLS` environment variable overrides the file store configuration from the network config. ## Understanding blob sources[​](#understanding-blob-sources "Direct link to Understanding blob sources") The blob client can retrieve blobs from multiple sources, tried in order: 1. **File Store**: Fast retrieval from configured storage (S3, GCS, R2, local files, HTTPS) 2. **L1 Consensus**: Beacon node API to a (semi-)supernode for recent blobs (within \~18 days) 3. **Archive API**: Services like Blobscan for historical blob data For near-tip synchronization, the client will retry file stores with backoff to handle eventual consistency when blobs are still being uploaded by other validators. ### L1 consensus and blob availability[​](#l1-consensus-and-blob-availability "Direct link to L1 consensus and blob availability") If your beacon node has access to [supernodes or semi-supernodes](https://ethereum.org/roadmap/fusaka/peerdas/), L1 consensus alone may be sufficient for retrieving blobs within the \~18 day retention period. With the Fusaka upgrade and [PeerDAS (Peer Data Availability Sampling)](https://eips.ethereum.org/EIPS/eip-7594), Ethereum uses erasure coding to split blobs into 128 columns, enabling robust data availability: * **Supernodes** (validators with ≥4,096 ETH staked): Custody all 128 columns and all blob data for the full \~18 day retention period. These nodes form the backbone of the network and continuously heal data gaps. * **Semi-supernodes** (validators with ≥1,824 ETH / 57 validators): Handle at least 64 columns, enabling reconstruction of complete blob data. * **Regular nodes**: Only download 1/8th of the data (8 of 128 columns) to verify availability. This is **not sufficient** to serve complete blob data. Supernodes If L1 consensus is your only blob source, your beacon node must be a supernode or semi-supernode (or connected to one) to retrieve complete blobs. A regular node cannot reconstruct full blob data from its partial columns alone. This means that for recent blobs, configuring `L1_CONSENSUS_HOST_URLS` pointing to a well-connected supernode or semi-supernode may be all you need. However, file stores and archive APIs are still recommended for: * Faster retrieval (file stores are typically faster than L1 consensus queries) * Historical access (blobs older than \~18 days are pruned from L1) * Redundancy (multiple sources improve reliability) ## Configuring blob sources[​](#configuring-blob-sources "Direct link to Configuring blob sources") ### Environment variables[​](#environment-variables "Direct link to Environment variables") Configure blob sources using environment variables: | Variable | Description | Example | | ----------------------------------- | -------------------------------------------------------------------- | ---------------------------- | | `BLOB_FILE_STORE_URLS` | Comma-separated URLs to read blobs from | `gs://bucket/,s3://bucket/` | | `L1_CONSENSUS_HOST_URLS` | Beacon node URLs (comma-separated) | `https://beacon.example.com` | | `L1_CONSENSUS_HOST_API_KEYS` | API keys for beacon nodes | `key1,key2` | | `L1_CONSENSUS_HOST_API_KEY_HEADERS` | Header names for API keys | `Authorization` | | `BLOB_ARCHIVE_API_URL` | Archive API URL (e.g., Blobscan) | `https://api.blobscan.com` | | `BLOB_ALLOW_EMPTY_SOURCES` | Allow no blob sources (default: false) | `false` | | `BLOB_PREFER_FILESTORES` | Try file stores before consensus clients (default: false) | `false` | | `BLOB_FILE_STORE_TIMEOUT_MS` | Timeout for HTTP requests to blob file stores in ms (default: 10000) | `10000` | tip If you want to contribute to the network by hosting a blob file store, see the [Blob upload guide](/operate/testnet/operators/setup/blob_upload.md). ### Supported storage backends[​](#supported-storage-backends "Direct link to Supported storage backends") The blob client supports the same storage backends as snapshots: * **Google Cloud Storage** - `gs://bucket-name/path/` * **Amazon S3** - `s3://bucket-name/path/` * **Cloudflare R2** - `s3://bucket-name/path/?endpoint=https://[ACCOUNT_ID].r2.cloudflarestorage.com` * **HTTP/HTTPS** (read-only) - `https://host/path` * **Local filesystem** - `file:///absolute/path` ### Storage path format[​](#storage-path-format "Direct link to Storage path format") Blobs are stored using the following path structure: ``` {base_url}/aztec-{l1ChainId}-{rollupVersion}-{rollupAddress}/blobs/{versionedBlobHash}.data ``` For example: ``` gs://my-bucket/aztec-1-1-0x1234abcd.../blobs/0x01abc123...def.data ``` ## Configuration examples[​](#configuration-examples "Direct link to Configuration examples") ### Basic file store configuration[​](#basic-file-store-configuration "Direct link to Basic file store configuration") ``` # Read blobs from GCS BLOB_FILE_STORE_URLS=gs://my-snapshots/ ``` ### Multiple read sources with L1 fallback[​](#multiple-read-sources-with-l1-fallback "Direct link to Multiple read sources with L1 fallback") ``` # Try multiple sources in order BLOB_FILE_STORE_URLS=gs://primary-bucket/,s3://backup-bucket/ # L1 consensus fallback L1_CONSENSUS_HOST_URLS=https://beacon1.example.com,https://beacon2.example.com # Archive fallback for historical blobs BLOB_ARCHIVE_API_URL=https://api.blobscan.com ``` ### Cloudflare R2 configuration[​](#cloudflare-r2-configuration "Direct link to Cloudflare R2 configuration") ``` BLOB_FILE_STORE_URLS=s3://my-bucket/?endpoint=https://[ACCOUNT_ID].r2.cloudflarestorage.com ``` Replace `[ACCOUNT_ID]` with your Cloudflare account ID. ### Local filesystem (for testing)[​](#local-filesystem-for-testing "Direct link to Local filesystem (for testing)") ``` BLOB_FILE_STORE_URLS=file:///data/blobs ``` ## Authentication[​](#authentication "Direct link to Authentication") ### Google Cloud Storage[​](#google-cloud-storage "Direct link to Google Cloud Storage") Set up [Application Default Credentials](https://cloud.google.com/docs/authentication/application-default-credentials): ``` gcloud auth application-default login ``` Or use a service account key: ``` export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account-key.json ``` ### Amazon S3 / Cloudflare R2[​](#amazon-s3--cloudflare-r2 "Direct link to Amazon S3 / Cloudflare R2") Set AWS credentials as environment variables: ``` export AWS_ACCESS_KEY_ID=your-access-key export AWS_SECRET_ACCESS_KEY=your-secret-key ``` For R2, these credentials come from your Cloudflare R2 API tokens. ## How blob retrieval works[​](#how-blob-retrieval-works "Direct link to How blob retrieval works") When a node needs blobs for a block, the blob client alternates between two primary sources in a retry loop, then falls back to the archive: 1. **Primary source A** (default: L1 Consensus) - Query supernode beacon nodes using slot number 2. **Primary source B** (default: File Store) - Quick lookup in configured file stores 3. If blobs are still missing, retry with backoff (handles eventual consistency) 4. **Archive API** - Final fallback (e.g., Blobscan) The default order is consensus first, then file stores. Set `BLOB_PREFER_FILESTORES=true` to reverse this order if your file stores are more reliable or faster than your consensus clients. Non-supernode consensus hosts are automatically detected at startup and skipped during blob fetching. ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### No blob sources configured[​](#no-blob-sources-configured "Direct link to No blob sources configured") **Issue**: Node starts with warning about no blob sources. **Solutions**: * Configure at least one of: `BLOB_FILE_STORE_URLS`, `L1_CONSENSUS_HOST_URLS`, or `BLOB_ARCHIVE_API_URL` * Set `BLOB_ALLOW_EMPTY_SOURCES=true` only if you understand the implications (node may fail to sync) ### Blob retrieval fails[​](#blob-retrieval-fails "Direct link to Blob retrieval fails") **Issue**: Node cannot retrieve blobs for a block. **Solutions**: * Verify your file store URLs are accessible * Check L1 consensus host connectivity * Ensure authentication credentials are configured * Try using multiple file store URLs for redundancy ### L1 consensus host errors[​](#l1-consensus-host-errors "Direct link to L1 consensus host errors") **Issue**: Cannot connect to beacon nodes. **Solutions**: * Verify beacon node URLs are correct and accessible * Check if API keys are required and correctly configured * Ensure the beacon node is synced * Try multiple beacon node URLs for redundancy ## Best practices[​](#best-practices "Direct link to Best practices") * **Configure multiple sources**: Use multiple file store URLs and L1 consensus hosts for redundancy * **Use file stores for production**: File stores provide faster, more reliable blob retrieval than L1 consensus * **Use archive API for historical access**: Configure `BLOB_ARCHIVE_API_URL` for accessing blobs older than \~18 days. Even with PeerDAS supernodes providing robust data availability, blob data is pruned from L1 after 4,096 epochs. Archive services like [Blobscan](https://blobscan.com/) store historical blob data indefinitely ## Next Steps[​](#next-steps "Direct link to Next Steps") * Learn how to [host a blob file store](/operate/testnet/operators/setup/blob_upload.md) to contribute to the network * Learn about [using snapshots](/operate/testnet/operators/setup/syncing_best_practices.md) for faster node synchronization * Set up [monitoring](/operate/testnet/operators/monitoring.md) to track your node's blob retrieval * Check the [CLI reference](/operate/testnet/operators/reference/cli-reference.md) for additional blob-related options * Join the [Aztec Discord](https://discord.gg/aztec) for support --- # Blob upload ## Overview[​](#overview "Direct link to Overview") While most nodes only need to retrieve blobs, you can contribute to the network by hosting a blob file store. When configured with an upload URL, your node will automatically upload blobs it retrieves to your file store, making them available for other nodes to download. Upload is Optional Configuring blob upload is optional. You can still download blobs from file stores without uploading them yourself — other network participants (such as sequencers and validators) upload blobs to shared storage, making them available for all nodes to retrieve. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before configuring blob upload, you should: * Have access to cloud storage (Google Cloud Storage, Amazon S3, or Cloudflare R2) with **write permissions** * Understand the [blob retrieval](/operate/testnet/operators/setup/blob_storage.md) configuration ## Configuring blob upload[​](#configuring-blob-upload "Direct link to Configuring blob upload") ### Environment variable[​](#environment-variable "Direct link to Environment variable") Configure blob upload using the following environment variable in your `.env` file: | Variable | Description | Example | | ---------------------------- | ----------------------- | ----------------------- | | `BLOB_FILE_STORE_UPLOAD_URL` | URL for uploading blobs | `s3://my-bucket/blobs/` | ### Supported storage backends[​](#supported-storage-backends "Direct link to Supported storage backends") The blob client supports the following storage backends for upload: * **Google Cloud Storage** - `gs://bucket-name/path/` * **Amazon S3** - `s3://bucket-name/path/` * **Cloudflare R2** - `s3://bucket-name/path/?endpoint=https://[ACCOUNT_ID].r2.cloudflarestorage.com` * **Local filesystem** - `file:///absolute/path` warning HTTPS URLs are read-only and cannot be used for uploads. ### Storage path format[​](#storage-path-format "Direct link to Storage path format") Blobs are stored using the following path structure: ``` {base_url}/aztec-{l1ChainId}-{rollupVersion}-{rollupAddress}/blobs/{versionedBlobHash}.data ``` For example: ``` gs://my-bucket/aztec-1-1-0x1234abcd.../blobs/0x01abc123...def.data ``` ## Healthcheck file[​](#healthcheck-file "Direct link to Healthcheck file") When blob upload is configured, your node uploads a `.healthcheck` file to the storage path on startup and periodically thereafter. Other nodes use this file to verify connectivity to your file store before attempting to download blobs. Exclude from pruning If you configure lifecycle rules or pruning policies on your storage bucket, ensure the `.healthcheck` file is excluded. Deleting this file will cause connectivity checks to fail on other nodes. ## Configuration examples[​](#configuration-examples "Direct link to Configuration examples") ### Google Cloud Storage[​](#google-cloud-storage "Direct link to Google Cloud Storage") ``` BLOB_FILE_STORE_UPLOAD_URL=gs://my-bucket/blobs/ ``` ### Amazon S3[​](#amazon-s3 "Direct link to Amazon S3") ``` BLOB_FILE_STORE_UPLOAD_URL=s3://my-bucket/blobs/ ``` ### Cloudflare R2[​](#cloudflare-r2 "Direct link to Cloudflare R2") ``` BLOB_FILE_STORE_UPLOAD_URL=s3://my-bucket/blobs/?endpoint=https://[ACCOUNT_ID].r2.cloudflarestorage.com ``` Replace `[ACCOUNT_ID]` with your Cloudflare account ID. ### Local filesystem (for testing)[​](#local-filesystem-for-testing "Direct link to Local filesystem (for testing)") ``` BLOB_FILE_STORE_UPLOAD_URL=file:///data/blobs ``` ## Authentication[​](#authentication "Direct link to Authentication") Upload requires write permissions to your storage bucket. ### Google Cloud Storage[​](#google-cloud-storage-1 "Direct link to Google Cloud Storage") Set up [Application Default Credentials](https://cloud.google.com/docs/authentication/application-default-credentials): ``` gcloud auth application-default login ``` Or use a service account key with write permissions: ``` export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account-key.json ``` ### Amazon S3 / Cloudflare R2[​](#amazon-s3--cloudflare-r2 "Direct link to Amazon S3 / Cloudflare R2") Set AWS credentials as environment variables: ``` export AWS_ACCESS_KEY_ID=your-access-key export AWS_SECRET_ACCESS_KEY=your-secret-key ``` For R2, these credentials come from your Cloudflare R2 API tokens. Ensure the token has write permissions. ## Exposing a public HTTP endpoint[​](#exposing-a-public-http-endpoint "Direct link to Exposing a public HTTP endpoint") While you upload blobs using SDK URLs (`gs://`, `s3://`), you should configure a public HTTP endpoint so other nodes can download blobs without needing cloud credentials. This allows anyone to add your file store as a read source using a simple HTTPS URL. ### Google Cloud Storage[​](#google-cloud-storage-2 "Direct link to Google Cloud Storage") GCS buckets can be accessed publicly at `https://storage.googleapis.com/BUCKET_NAME/path/to/object`. To enable public access: 1. Go to your bucket in the [Google Cloud Console](https://console.cloud.google.com/storage/browser) 2. Select the **Permissions** tab 3. Click **Grant Access** 4. Add `allUsers` as a principal with the **Storage Object Viewer** role See [Making data public](https://cloud.google.com/storage/docs/access-control/making-data-public) for detailed instructions. Once configured, other nodes can use: ``` BLOB_FILE_STORE_URLS=https://storage.googleapis.com/my-bucket/blobs/ ``` ### Amazon S3[​](#amazon-s3-1 "Direct link to Amazon S3") S3 buckets can be accessed publicly via static website hosting at `http://BUCKET_NAME.s3-website.REGION.amazonaws.com`. To enable public access: 1. Go to your bucket in the [AWS S3 Console](https://console.aws.amazon.com/s3/) 2. Disable **Block Public Access** settings 3. Add a bucket policy granting public read access 4. Enable **Static website hosting** in the bucket properties See [Hosting a static website on S3](https://docs.aws.amazon.com/AmazonS3/latest/userguide/WebsiteHosting.html) for detailed instructions. note S3 website endpoints only support HTTP. For HTTPS, use [CloudFront](https://docs.aws.amazon.com/AmazonS3/latest/userguide/website-hosting-cloudfront-walkthrough.html) as a CDN in front of your bucket. ### Cloudflare R2[​](#cloudflare-r2-1 "Direct link to Cloudflare R2") R2 buckets can expose a public HTTP endpoint via a custom domain or the managed `r2.dev` subdomain. To enable public access: 1. Go to your bucket in the [Cloudflare Dashboard](https://dash.cloudflare.com/) 2. Select **Settings** > **Public Access** 3. Either enable the `r2.dev` subdomain or connect a custom domain See [Public buckets](https://developers.cloudflare.com/r2/buckets/public-buckets/) for detailed instructions. Once configured, other nodes can use: ``` BLOB_FILE_STORE_URLS=https://pub-[ID].r2.dev/ # or with custom domain: BLOB_FILE_STORE_URLS=https://blobs.yourdomain.com/ ``` tip R2 offers free egress, making it cost-effective for public blob distribution. ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Upload fails[​](#upload-fails "Direct link to Upload fails") **Issue**: Blobs are not being uploaded to file store. **Solutions**: * Verify `BLOB_FILE_STORE_UPLOAD_URL` is set * Check write permissions on the storage bucket * Ensure credentials are configured (AWS/GCP) * Note: HTTPS URLs are read-only and cannot be used for uploads ## Next Steps[​](#next-steps "Direct link to Next Steps") * Learn about [blob retrieval](/operate/testnet/operators/setup/blob_storage.md) configuration * Learn about [using snapshots](/operate/testnet/operators/setup/syncing_best_practices.md) for faster node synchronization * Join the [Aztec Discord](https://discord.gg/aztec) for support --- # Using and running a bootnode ## Overview[​](#overview "Direct link to Overview") Bootnodes facilitate peer discovery in the Aztec network by maintaining a list of active peers that new nodes can connect to. This guide covers how to connect your node to a bootnode and how to run your own bootnode. ## What is a bootnode?[​](#what-is-a-bootnode "Direct link to What is a bootnode?") Nodes in the Aztec network must connect to peers to gossip transactions and propagate them across the network. Bootnodes help new nodes discover and connect to these peers, enabling them to join the peer-to-peer layer. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before proceeding, you should: * Have the Aztec node software installed * Understand basic command-line operations * For running a bootnode: Have the necessary network infrastructure and port access ## Connecting to a bootnode[​](#connecting-to-a-bootnode "Direct link to Connecting to a bootnode") To connect your node to a bootnode for peer discovery: 1. Obtain the bootnode's ENR (Ethereum Node Record) 2. Pass the ENR to your node at startup using the `--p2p.bootstrapNodes` flag The flag accepts a comma-separated list of bootstrap node ENRs: ``` aztec start --node --p2p.bootstrapNodes [ENR] ``` For multiple bootnodes: ``` aztec start --node --p2p.bootstrapNodes [ENR1],[ENR2],[ENR3] ``` ## Running a bootnode[​](#running-a-bootnode "Direct link to Running a bootnode") To run your own bootnode, use the `--p2p-bootstrap` flag: ``` aztec start --p2p-bootstrap ``` ### Configuring the bootnode port[​](#configuring-the-bootnode-port "Direct link to Configuring the bootnode port") By default, the bootnode uses the `P2P_PORT` value. To customize the port: ``` aztec start --p2p-bootstrap --p2pBootstrap.p2pBroadcastPort [PORT] ``` ### Persisting bootnode identity[​](#persisting-bootnode-identity "Direct link to Persisting bootnode identity") To maintain a consistent bootnode identity across restarts, use the `--p2pBootstrap.peerIdPrivateKeyPath` flag to specify a private key location: ``` aztec start --p2p-bootstrap --p2pBootstrap.peerIdPrivateKeyPath [path] ``` **How it works:** * If a private key exists at `[path]`, the bootnode will use it for its identity * If no private key exists, a new one will be generated and saved to `[path]` * This ensures your bootnode maintains the same ENR across restarts ### Obtaining your bootnode's ENR[​](#obtaining-your-bootnodes-enr "Direct link to Obtaining your bootnode's ENR") After starting your bootnode, obtain its ENR from the startup logs. You can share this ENR with node operators who want to connect to your bootnode. ### Adding your bootnode to the default set[​](#adding-your-bootnode-to-the-default-set "Direct link to Adding your bootnode to the default set") info The process for adding bootnodes to Aztec's default bootnode list is currently being finalized. For now, share your bootnode ENR directly with node operators who want to connect. ## Verification[​](#verification "Direct link to Verification") To verify your bootnode setup: ### For nodes connecting to a bootnode[​](#for-nodes-connecting-to-a-bootnode "Direct link to For nodes connecting to a bootnode") 1. **Check logs**: Look for messages indicating successful peer discovery 2. **Verify peer count**: Confirm your node has connected to peers from the bootnode 3. **Monitor network activity**: Ensure transactions are being gossiped correctly ### For bootnode operators[​](#for-bootnode-operators "Direct link to For bootnode operators") 1. **Confirm bootnode is running**: Check that the process started successfully 2. **Verify port accessibility**: Ensure the configured port is open and accessible 3. **Monitor peer connections**: Check logs for incoming peer connection requests 4. **Validate ENR generation**: Confirm your bootnode's ENR is displayed in the logs ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Cannot connect to bootnode[​](#cannot-connect-to-bootnode "Direct link to Cannot connect to bootnode") **Issue**: Your node fails to connect to the specified bootnode. **Solutions**: * Verify the ENR is correct and properly formatted * Check network connectivity to the bootnode's address * Ensure the bootnode is running and accessible * Confirm firewall rules allow P2P connections ### Bootnode not discovering peers[​](#bootnode-not-discovering-peers "Direct link to Bootnode not discovering peers") **Issue**: Your bootnode isn't discovering or storing peers. **Solutions**: * Verify the bootnode process is running with the correct flags * Check that the P2P port is properly configured and accessible * Review logs for error messages or connection issues * Ensure sufficient system resources are available ### Private key path errors[​](#private-key-path-errors "Direct link to Private key path errors") **Issue**: Errors occur when specifying `--p2pBootstrap.peerIdPrivateKeyPath`. **Solutions**: * Verify the path exists and is writable * Check file permissions for the directory and file * Ensure the path doesn't contain invalid characters * Confirm the private key file format is correct (if reusing an existing key) ## Next Steps[​](#next-steps "Direct link to Next Steps") * Monitor your bootnode or node connections regularly * Consider running multiple bootnodes for redundancy * Join the Aztec community to share your bootnode ENR with other operators --- # Building Node Software from Source ## Overview[​](#overview "Direct link to Overview") This guide shows you how to build the Aztec node Docker image from source, including all build tools and dependencies. Building from source allows you to: * Run a specific tagged version * Verify the build process matches the official CI pipeline * Customize the software for development or testing * Audit the complete build chain ### Requirements[​](#requirements "Direct link to Requirements") **Hardware:** * 4 core / 8 vCPU * 16 GB RAM for Docker * 150 GB free disk space * Stable internet connection **Software:** * Git to clone the repository * Docker version 20.10 or later with at least 16 GB RAM allocated This guide assumes you're using a standard Linux distribution such as Debian or Ubuntu. While other operating systems may work, these instructions are tested and optimized for Linux environments. These requirements are for building the software. Running a node has different requirements—see [Running a Full Node](/operate/testnet/operators/setup/running_a_node.md). ## Build Steps[​](#build-steps "Direct link to Build Steps") ### Step 1: Clone the Repository[​](#step-1-clone-the-repository "Direct link to Step 1: Clone the Repository") Clone the Aztec packages repository: ``` git clone https://github.com/AztecProtocol/aztec-packages.git cd aztec-packages ``` ### Step 2: Check Out a Version Tag[​](#step-2-check-out-a-version-tag "Direct link to Step 2: Check Out a Version Tag") Check out the version tag you want to build. For example, to build version 5.0.0-rc.2: ``` git checkout v5.0.0-rc.2 ``` tip View all available release tags with: ``` git tag | grep "^v[0-9]" ``` ### Step 3: Build the Container with Build Tools[​](#step-3-build-the-container-with-build-tools "Direct link to Step 3: Build the Container with Build Tools") Build the container image with all necessary compilation tools: ``` cd build-images/src docker build --target build -t aztec-build-local:3.0 . cd ../.. ``` tip The tag `aztec-build-local:3.0` avoids conflicts with the official Docker Hub image and clearly indicates this is a locally-built version. **What this does:** * Builds the `build` stage from `build-images/src/Dockerfile` * Installs Node.js 24.15.0 from NodeSource repository * Installs Clang 16, 18, and 20 from LLVM * Installs Rust 1.85.0 using the Rust toolchain installer with wasm32 targets * Downloads and installs WASI SDK 27 from GitHub releases * Builds Foundry v1.4.1 from source * Installs CMake, Ninja, and other build essentials note This step builds all compilation tooling from scratch. The Dockerfile uses multi-stage builds—you only need the `build` target. Other targets (`devbox` and `sysbox`) are for development environments. Verifying the Build Image After the build completes, inspect the image to verify its contents: ``` # Run a shell in the container to explore docker run -it --rm aztec-build-local:3.0 /bin/bash # Check specific versions once inside: node --version # Should show v24.15.0 rustc --version # Should show Rust 1.85.0 clang-20 --version # Should show clang 20.x forge --version # Should show v1.4.1 cmake --version # Should show cmake 3.24+ ``` You can review the Dockerfile at `build-images/src/Dockerfile` to see exactly what's installed and verify each step. ### Step 4: Compile the Source Code[​](#step-4-compile-the-source-code "Direct link to Step 4: Compile the Source Code") Run the bootstrap script inside the build container to compile all source code: ``` docker run --rm \ -v $(pwd):/workspaces/aztec-packages \ -w /workspaces/aztec-packages \ aztec-build-local:3.0 \ ./bootstrap.sh full ``` **What this does:** * Mounts your local repository into the container * Compiles C++ code (Barretenberg proving system) * Compiles Rust code (Noir compiler and ACVM) * Builds TypeScript/JavaScript packages * Writes compiled artifacts to your local filesystem (persist after container exits) * Runs tests to verify the build note The bootstrap process is incremental—if interrupted, restart it to resume from where it left off. Git submodules for L1 contract dependencies are initialized automatically during the build. ### Step 5: Build the Runtime Base Image[​](#step-5-build-the-runtime-base-image "Direct link to Step 5: Build the Runtime Base Image") Build the runtime base image with Node.js dependencies. This image contains only runtime requirements—no build tools or compiled code: ``` docker build -f release-image/Dockerfile.base -t aztecprotocol/release-image-base . ``` note The tag `aztecprotocol/release-image-base` must match exactly—the Dockerfile in Step 6 references this specific tag. This image is not published to Docker Hub; it exists only locally. **What this does:** * Installs production Node.js dependencies (no dev dependencies) * Includes Node.js 24 runtime and system utilities * Copies Foundry tools (anvil, cast) from the build container * Creates a slim Ubuntu-based runtime environment without build tools ### Step 6: Build the Final Release Image[​](#step-6-build-the-final-release-image "Direct link to Step 6: Build the Final Release Image") Build the final node image, combining the runtime environment (Step 5) with your compiled code (Step 4): ``` docker build -f release-image/Dockerfile --build-arg VERSION=5.0.0-rc.2 -t aztec-local:5.0.0-rc.2 . ``` tip The tag `aztec-local:5.0.0-rc.2` avoids conflicts with the official Docker Hub image and clearly indicates this is a locally-built version. **Build arguments:** * `VERSION` - Sets the version string that appears in `aztec --version` **What this does:** * Starts from the `aztecprotocol/release-image-base` image (Step 5) * Copies compiled source code from your local filesystem (Step 4) * Sets up environment variables for Barretenberg and ACVM binaries * Configures the entrypoint to run the Aztec node ## Verification[​](#verification "Direct link to Verification") Verify your build completed successfully: ### Check Image Exists[​](#check-image-exists "Direct link to Check Image Exists") ``` docker images aztec-local ``` You should see your image listed: ``` REPOSITORY TAG IMAGE ID CREATED SIZE aztec-local 5.0.0-rc.2 abc123def456 2 minutes ago 2.5GB ``` ### Verify Version[​](#verify-version "Direct link to Verify Version") ``` docker run --rm aztec-local:5.0.0-rc.2 --version ``` Should display version 5.0.0-rc.2. ### Test Basic Functionality[​](#test-basic-functionality "Direct link to Test Basic Functionality") ``` docker run --rm aztec-local:5.0.0-rc.2 --help ``` Should display CLI help information without errors. If all checks pass, your image is ready to use. ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Build fails with "no space left on device"[​](#build-fails-with-no-space-left-on-device "Direct link to Build fails with \"no space left on device\"") **Issue**: Insufficient disk space. **Solutions**: * Clean up unused Docker images and build cache: `docker system prune -a` * Free up at least 150 GB of disk space * Ensure adequate storage for intermediate build artifacts ### Build image fails[​](#build-image-fails "Direct link to Build image fails") **Issue**: Errors during Step 3 when building `aztec-build-local:3.0`. **Solutions**: * Verify you're in the `build-images/src` directory * Ensure the `--target build` flag is specified * Retry the build if network issues occur while downloading Rust, LLVM, or WASI SDK * Review `build-images/src/Dockerfile` to identify the failing stage ### Build fails with "failed to solve with frontend dockerfile.v0: failed to create LLB definition"[​](#build-fails-with-failed-to-solve-with-frontend-dockerfilev0-failed-to-create-llb-definition "Direct link to Build fails with \"failed to solve with frontend dockerfile.v0: failed to create LLB definition\"") **Issue**: The release image build cannot find the base image. **Solutions**: * Ensure you completed Step 5 and built the base image with the exact tag: `aztecprotocol/release-image-base` * Verify the base image exists locally: `docker images aztecprotocol/release-image-base` * If missing, return to Step 5 and rebuild the base image ### Bootstrap compilation fails[​](#bootstrap-compilation-fails "Direct link to Bootstrap compilation fails") **Issue**: Errors during `./bootstrap.sh` in Step 4. **Solutions**: * Verify you're using the correct build image: `aztec-build-local:3.0` * Confirm you checked out a valid release tag (not a branch) * Retry the build—the bootstrap script is incremental and resumes where it left off * Review error messages for specifics—missing dependencies should not occur in the build container ### Docker runs out of memory[​](#docker-runs-out-of-memory "Direct link to Docker runs out of memory") **Issue**: Build crashes due to insufficient memory. **Solutions**: * Increase Docker's memory limit to at least 16 GB (Docker Desktop: Settings → Resources → Memory) * Close other applications to free system memory * Build on a machine with more RAM if possible ### Wrong version shows in `aztec --version`[​](#wrong-version-shows-in-aztec---version "Direct link to wrong-version-shows-in-aztec---version") **Issue**: Version argument not passed correctly. **Solutions**: * Ensure you used `--build-arg VERSION=X.Y.Z` when building the release image * The version should match the git tag without the 'v' prefix (e.g., `5.0.0-rc.2` not `v5.0.0-rc.2`) ## Using Your Custom Build[​](#using-your-custom-build "Direct link to Using Your Custom Build") ### Running a Node[​](#running-a-node "Direct link to Running a Node") Use your locally-built image with any node setup method. For Docker Compose, update your `docker-compose.yml`: ``` services: aztec-node: image: "aztec-local:5.0.0-rc.2" # ... rest of configuration ``` See [Running a Full Node](/operate/testnet/operators/setup/running_a_node.md) for complete setup instructions. ### Using the CLI[​](#using-the-cli "Direct link to Using the CLI") Run the Aztec CLI directly from your custom image: ``` docker run --rm aztec-local:5.0.0-rc.2 --version ``` ## Alternative Approaches[​](#alternative-approaches "Direct link to Alternative Approaches") ### Using Pre-built Build Image[​](#using-pre-built-build-image "Direct link to Using Pre-built Build Image") To save time, skip Step 3 and pull the pre-built image from Docker Hub, then tag it locally: ``` docker pull aztecprotocol/build:3.0 docker tag aztecprotocol/build:3.0 aztec-build-local:3.0 ``` This approach is faster but requires trusting the published image. The official image is built from the same `build-images/src/Dockerfile`. ### Building Without Docker[​](#building-without-docker "Direct link to Building Without Docker") To build without Docker, install all build dependencies locally and run `./bootstrap.sh` directly: * Install all toolchains from the build image (Node.js 24, Rust 1.85.0, Clang 20, CMake, wasi-sdk) * Run `bootstrap.sh check` to verify your environment * See `build-images/README.md` for details Using the build container is strongly recommended to ensure a consistent, tested environment. ## Understanding the Build Process[​](#understanding-the-build-process "Direct link to Understanding the Build Process") The build process uses these key files in the repository: * **`build-images/src/Dockerfile`** - Defines the build container with all compilation tools * **`bootstrap.sh`** - Main build script that compiles all source code (C++, Rust, TypeScript) * **`release-image/Dockerfile.base`** - Multi-stage Dockerfile that creates a slim runtime base image * **`release-image/Dockerfile`** - Final release image with compiled Aztec software * **`release-image/bootstrap.sh`** - Build script used in CI for Docker images The official CI pipeline follows a similar process. See `.github/workflows/ci3.yml` for how production images are built and deployed. ## Next Steps[​](#next-steps "Direct link to Next Steps") * Use your custom build to [run a full node](/operate/testnet/operators/setup/running_a_node.md) * Set up [monitoring](/operate/testnet/operators/monitoring.md) for your node * Review the [CLI reference](/operate/testnet/operators/reference/cli-reference.md) for configuration options * Join the [Aztec Discord](https://discord.gg/aztec) to discuss development and customization --- # High Availability Sequencers ## Overview[​](#overview "Direct link to Overview") This guide shows you how to set up high availability (HA) for your sequencer by running the same sequencer identity across multiple physical nodes with automatic coordination via a shared database. This configuration provides redundancy and resilience, ensuring your sequencer continues operating even if individual nodes fail. **What is High Availability for sequencers?** High availability means running multiple sequencer nodes that share the same attester identity but use different publisher addresses. The nodes coordinate through a shared PostgreSQL database to prevent double-signing across all validator duties. This allows your sequencer to: * Continue performing validator duties even if one node goes offline * Maintain uptime during maintenance windows and upgrades * Protect against infrastructure failures * Ensure you don't miss any validator duties * Automatically prevent double-signing & slashable actions through distributed locking ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before setting up HA sequencers, ensure you have: * Experience running a single sequencer node (see the [Sequencer Setup guide](/operate/testnet/operators/setup/sequencer_management.md)) * Understanding of basic keystore structure and configuration * Access to multiple servers or VMs for running separate nodes * Ability to securely distribute keys across infrastructure * A PostgreSQL database accessible by all HA nodes (for coordination and slashing protection) ## How HA Signing Works[​](#how-ha-signing-works "Direct link to How HA Signing Works") The HA signer uses a shared PostgreSQL database to coordinate signing across multiple nodes, preventing double-signing through distributed locking: 1. **Distributed Locking**: When a node needs to sign a duty (block proposal, checkpoint proposal, checkpoint attestation, governance vote, etc.), it first attempts to acquire a lock in the database for that specific duty (validator + slot + duty type) (+ block index within checkpoint for block proposals). 2. **First Node Wins**: The first node to acquire the lock proceeds with signing. Other nodes receive a `DutyAlreadySignedError`, which is expected and normal in HA setups. 3. **Slashing Protection**: If a node attempts to sign different data for the same duty, the database detects this and throws a `SlashingProtectionError`, preventing slashing conditions. 4. **Automatic Retry**: If a node fails mid-signing (crashes, network issue), the lock is automatically cleaned up after a timeout, allowing other nodes to retry. 5. **Background Cleanup**: The HA signer runs background tasks to clean up stuck duties (duties that were locked but never completed), ensuring the system remains healthy. This coordination happens automatically when `VALIDATOR_HA_SIGNING_ENABLED=true` - no manual intervention is required. Limitation: Post-Signature Failures If a node successfully signs a duty but fails **after** signing (before broadcasting the signature to the network), the duty will be missed. HA signing cannot help in this scenario because the duty is already marked as "signed" in the database, preventing other nodes from retrying. This is why it's still important to have reliable infrastructure even with HA enabled - HA protects against double-signing, not against all failure modes. ## What Duties Are Protected?[​](#what-duties-are-protected "Direct link to What Duties Are Protected?") The HA signing system provides double-signing protection for all validator duties: ### Block Production Duties[​](#block-production-duties "Direct link to Block Production Duties") 1. **Block Proposals**: Individual block proposals built during your assigned slot. Each slot may contain multiple blocks, and each block proposal is tracked separately with its `blockIndexWithinCheckpoint` (0, 1, 2...). 2. **Checkpoint Proposals**: The aggregated proposal submitted at the end of a slot that bundles all blocks from that slot. This is what gets submitted to L1 along with attestations. 3. **Checkpoint Attestations**: Your validator's signature attesting to a checkpoint proposal. Validators attest to checkpoints after validating all blocks in a slot. This is the primary consensus mechanism. 4. **Attestations and Signers**: Extended attestation format that includes additional signer information for consensus coordination. ### Governance Duties[​](#governance-duties "Direct link to Governance Duties") 5. **Governance Votes**: Signatures on governance proposals for protocol upgrades and parameter changes. HA protection ensures you don't accidentally vote twice on the same proposal. 6. **Slashing Votes**: Signatures on votes to slash misbehaving validators. Critical for validator accountability without risking self-slashing from duplicate votes. ## Why High Availability?[​](#why-high-availability "Direct link to Why High Availability?") ### Benefits of HA Configuration[​](#benefits-of-ha-configuration "Direct link to Benefits of HA Configuration") **1. Redundancy and Fault Tolerance** If one node crashes, experiences network issues, or needs maintenance, the other node continues operating. You won't miss any validator duties during: * Hardware failures * Network outages * Planned maintenance * Software upgrades * Infrastructure provider issues **2. Improved Uptime** With properly configured HA, your sequencer can achieve near-perfect uptime. You can perform rolling upgrades, switching nodes in and out of service without missing duties. ### The Core Concept[​](#the-core-concept "Direct link to The Core Concept") In an HA setup: * **Attester identity is shared** across both nodes (same private key) * **Publisher identity is unique** per node (different private keys) * **Shared database coordinates signing** - prevents double-signing through distributed locking * Both nodes run simultaneously and attempt to sign duties * **First node wins** - the database ensures only one node signs each duty * **Automatic failover** - if one node fails mid-signing, the other can retry * Only one proposal is accepted per slot (enforced by L1) The validator client automatically integrates with the HA signer when enabled, providing distributed locking and slashing protection without manual coordination. ## Setting Up High Availability Sequencers[​](#setting-up-high-availability-sequencers "Direct link to Setting Up High Availability Sequencers") ### Infrastructure Requirements[​](#infrastructure-requirements "Direct link to Infrastructure Requirements") **HA Setup (2 nodes):** * 2 separate servers/VMs * Each meeting the minimum sequencer requirements (see [Sequencer Setup](/operate/testnet/operators/setup/sequencer_management.md)) * Different physical locations or availability zones (recommended) * Reliable network connectivity for both nodes * Access to the same L1 infrastructure (or separate L1 endpoints) * **PostgreSQL database** accessible by all nodes (for coordination) * Monitoring and alerting for both nodes **Database Requirements:** * PostgreSQL 12 or later * Network access from all validator nodes * Sufficient connection pool capacity (default: 10 connections per node) * Regular backups recommended for production ### Key Management[​](#key-management "Direct link to Key Management") You'll need to generate: 1. **One shared attester key** - Your sequencer's identity (used by both nodes) 2. **One unique publisher key per node** - For submitting proposals 3. **Secure distribution method** - For safely deploying the shared attester key Secure Key Distribution The shared attester key must be distributed securely to both nodes. Consider using remote signers with: * Encrypted secrets management (HashiCorp Vault, AWS Secrets Manager, etc.) * Hardware security modules (HSMs) for production deployments Never transmit private keys over unencrypted channels or store them in version control. ### Step 1: Generate Keys[​](#step-1-generate-keys "Direct link to Step 1: Generate Keys") Generate a base keystore with multiple publishers using the Aztec CLI. This will create one attester identity with multiple publisher keys that can be distributed across your nodes. ``` # Generate base keystore with one attester and 3 publishers aztec validator-keys new \ --fee-recipient [YOUR_AZTEC_FEE_RECIPIENT_ADDRESS] \ --gse-address 0xb6a38a51a6c1de9012f9d8ea9745ef957212eaac \ --l1-rpc-urls $ETH_RPC \ --mnemonic "your shared mnemonic phrase for key derivation" \ --address-index 0 \ --publisher-count 3 \ --data-dir ~/ha-keys-temp ``` This command generates: * **One attester** with both ETH and BLS keys (at derivation index 0) * **Two publisher keys** (at derivation indices 1 and 2) * All keys saved to `~/ha-keys-temp/key1.json` The output will show the complete keystore JSON with all generated keys. **Save this output securely** as you'll need to extract keys from it for each node. Managing Your Mnemonic Store your mnemonic phrase securely in a password manager or hardware wallet. You'll need it to: * Regenerate keys if lost * Add more publishers later * Recover your sequencer setup Never commit mnemonics to version control or share them over insecure channels. ### Step 2: Fund Publisher Accounts[​](#step-2-fund-publisher-accounts "Direct link to Step 2: Fund Publisher Accounts") Each publisher account needs ETH to pay for L1 gas when submitting proposals. You must maintain at least **0.1 ETH** in each publisher account. **Get Sepolia ETH from faucets:** * [Sepolia Faucet](https://sepoliafaucet.com/) * [Infura Sepolia Faucet](https://www.infura.io/faucet/sepolia) * [Alchemy Sepolia Faucet](https://sepoliafaucet.com/) **Check publisher balances:** ``` # Check balance for Publisher 1 cast balance [PUBLISHER_1_ADDRESS] --rpc-url $ETH_RPC # Check balance for Publisher 2 cast balance [PUBLISHER_2_ADDRESS] --rpc-url $ETH_RPC ``` **Example:** ``` cast balance 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb --rpc-url $ETH_RPC # Output: 100000000000000000 (0.1 ETH in wei) ``` Balance Monitoring Monitor these balances regularly to ensure they don't drop below 0.1 ETH. Falling below this threshold risks slashing. Consider setting up automated alerts when balances drop below 0.15 ETH. ### Step 3: Extract Keys from Generated Keystore[​](#step-3-extract-keys-from-generated-keystore "Direct link to Step 3: Extract Keys from Generated Keystore") Open the generated keystore file (`~/ha-keys-temp/key1.json`) and extract the keys. The file will look something like this: ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "0xABC...123", // Shared attester ETH key "bls": "0xDEF...456" // Shared attester BLS key }, "publisher": [ "0x111...AAA", // Publisher 1 (for Node 1) "0x222...BBB" // Publisher 2 (for Node 2) ], "feeRecipient": "0x0000000000000000000000000000000000000000000000000000000000000000" } ] } ``` You'll use: * The **same attester keys** (both ETH and BLS) on both nodes * A **different publisher key** for each node ### Step 4: Create Node-Specific Keystores[​](#step-4-create-node-specific-keystores "Direct link to Step 4: Create Node-Specific Keystores") Create a separate keystore file for each node, using the same attester but different publishers: **Node 1 Keystore** (`~/node1/keys/keystore.json`): Use the same attester ETH and BLS keys, but only Publisher 1: ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "0xABC...123", "bls": "0xDEF...456" }, "publisher": ["0x111...AAA"], "feeRecipient": "0x0000000000000000000000000000000000000000000000000000000000000000" } ] } ``` **Node 2 Keystore** (`~/node2/keys/keystore.json`): Use the same attester ETH and BLS keys, but only Publisher 2: ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "0xABC...123", "bls": "0xDEF...456" }, "publisher": ["0x222...BBB"], "feeRecipient": "0x0000000000000000000000000000000000000000000000000000000000000000" } ] } ``` Security Best Practice After creating node-specific keystores, **securely delete** the base keystore file (`~/ha-keys-temp/key1.json`) that contains all publishers together. Each node should only have access to its own publisher key. ### Step 5: Deploy Keystores to Nodes[​](#step-5-deploy-keystores-to-nodes "Direct link to Step 5: Deploy Keystores to Nodes") Securely transfer each keystore to its respective node: ``` # Example: Copy keystores to remote nodes via SCP scp ~/node1/keys/keystore.json user@node1-server:~/aztec/keys/ scp ~/node2/keys/keystore.json user@node2-server:~/aztec/keys/ ``` Ensure proper file permissions on each node: ``` chmod 600 ~/aztec/keys/keystore.json ``` ### Step 6: Set Up the HA Database[​](#step-6-set-up-the-ha-database "Direct link to Step 6: Set Up the HA Database") Before starting your nodes, you need a PostgreSQL database that all HA nodes can access for coordination. **1. Provision a PostgreSQL database:** For production HA setups, we recommend using a managed database service with built-in high availability: * **AWS RDS PostgreSQL** with Multi-AZ for automatic failover * **Google Cloud SQL for PostgreSQL** with high availability configuration * **Azure Database for PostgreSQL** with zone redundancy * **Self-hosted PostgreSQL** with streaming replication and automatic failover (if you manage your own infrastructure) Critical: All Nodes Must Connect to the Same Primary Database The HA signing system relies on atomic database operations for distributed locking. **All validator nodes MUST connect to the SAME PRIMARY database instance**. The configurations above are safe because they use automatic failover to a single primary. **DO NOT use:** * Read replicas (replication lag breaks consistency) * Multi-master or active-active configurations (breaks distributed locking) * Different database instances per node (defeats the purpose of HA coordination) All validator nodes must use the same database connection string that points to the current primary. The key requirements are: * PostgreSQL 12 or later * **Single primary database** that all validator nodes connect to * Network accessible from all validator nodes * Sufficient connection pool capacity (default: 10 connections per node) * A database created for the HA signer (e.g., `validator_ha`) * Automatic failover is good (keeps high availability), but only one primary at a time **Example using psql** (if manually creating the database): ``` # Connect to your PostgreSQL instance psql -h your-db-host -U postgres # Create the database CREATE DATABASE validator_ha; # Exit psql \q ``` **2. Run database migrations:** The HA signer uses database migrations to set up the required tables. Run migrations **once** before starting your nodes: ``` aztec migrate-ha-db up \ --database-url postgresql://user:password@host:port/validator_ha ``` Migration Safety Migrations are idempotent and safe to run concurrently, but for cleaner logs, run them once before starting nodes. You can also run migrations from an init container or separate migration job in Kubernetes. **3. Verify the database setup:** Check that the required tables were created: ``` # Using psql psql postgresql://user:password@host:port/validator_ha -c "\dt" # Or using your cloud provider's database console ``` You should see tables like `validator_duties`, `schema_version` and `pmigrations`. ### Step 7: Configure HA Signing[​](#step-7-configure-ha-signing "Direct link to Step 7: Configure HA Signing") Configure each node with HA signing enabled. Set these environment variables on **each node**: ``` # Enable HA signing export VALIDATOR_HA_SIGNING_ENABLED=true # PostgreSQL connection string (same database for all nodes) export VALIDATOR_HA_DATABASE_URL=postgresql://user:password@host:port/validator_ha # Unique node identifier (different for each node) export VALIDATOR_HA_NODE_ID=validator-node-1 # Use validator-node-2 for second node # Optional: Tune polling and timeout settings export VALIDATOR_HA_POLLING_INTERVAL_MS=100 # Default: 100ms export VALIDATOR_HA_SIGNING_TIMEOUT_MS=3000 # Default: 3000ms ``` **Required Environment Variables:** | Variable | Description | Example | | ------------------------------ | ------------------------------------------ | ------------------------------------- | | `VALIDATOR_HA_SIGNING_ENABLED` | Enable HA signing (required) | `true` | | `VALIDATOR_HA_DATABASE_URL` | PostgreSQL connection string (required) | `postgresql://user:pass@host:5432/db` | | `VALIDATOR_HA_NODE_ID` | Unique identifier for this node (required) | `validator-node-1` | **Optional Tuning Variables:** | Variable | Description | Default | | -------------------------------------- | ------------------------------------------------ | ------------------ | | `VALIDATOR_HA_POLLING_INTERVAL_MS` | How often to check duty status | `100` | | `VALIDATOR_HA_SIGNING_TIMEOUT_MS` | Max wait for in-progress signing | `3000` | | `VALIDATOR_HA_MAX_STUCK_DUTIES_AGE_MS` | Max age before cleanup | `2 * slotDuration` | | `VALIDATOR_HA_POOL_MAX` | Max database connections | `10` | | `VALIDATOR_HA_POOL_MIN` | Min database connections | `0` | | `VALIDATOR_HA_OLD_DUTIES_MAX_AGE_H` | Clean up old signed duties after this many hours | N/A | When `VALIDATOR_HA_SIGNING_ENABLED=true`, the validator client automatically: * Creates an HA signer using the provided configuration * Wraps the base keystore with `HAKeyStore` for HA-protected signing * Coordinates signing across nodes via PostgreSQL to prevent double-signing * Provides slashing protection to block conflicting signatures ### Step 8: Start All Nodes[​](#step-8-start-all-nodes "Direct link to Step 8: Start All Nodes") Start each node (assuming you are using Docker Compose): ``` # On each server docker compose up -d ``` Ensure both nodes are configured with: * The same network (`--network testnet`) * Proper L1 endpoints * Correct P2P configuration * **HA signing enabled** with the same database URL * **Unique node IDs** for each node * Adequate resources ## Verification and Monitoring[​](#verification-and-monitoring "Direct link to Verification and Monitoring") ### Verify Your HA Setup[​](#verify-your-ha-setup "Direct link to Verify Your HA Setup") **1. Check that both nodes are running:** ``` # On each server curl http://localhost:8080/status # Or for Docker docker compose logs -f aztec-sequencer ``` **2. Confirm nodes recognize the shared attester:** Check logs for messages indicating the attester address is loaded correctly. Both nodes should show the same attester address. **3. Verify HA signer is active:** Look for log messages indicating HA signer initialization: ``` HAKeyStore initialized { nodeId: 'validator-node-1' } ``` **4. Verify different publishers:** Each node's logs should show a different publisher address being used for submitting transactions. **5. Monitor attestations:** Watch L1 for attestations from your sequencer's attester address. You should see attestations being submitted even if one node goes offline. **6. Check database coordination:** Query the database to see which node signed recent duties: ``` SELECT validator_address, slot, duty_type, node_id, status, started_at FROM validator_duties ORDER BY started_at DESC LIMIT 10; ``` You should see duties distributed across both nodes, with only one node signing each duty. **7. Check duty type distribution:** View the distribution of different duty types across your nodes: ``` SELECT duty_type, node_id, COUNT(*) as duty_count, COUNT(CASE WHEN status = 'signed' THEN 1 END) as signed_count FROM validator_duties WHERE started_at > NOW() - INTERVAL '1 hour' GROUP BY duty_type, node_id ORDER BY duty_type, node_id; ``` This helps verify that both nodes are handling all types of validator duties (block proposals, checkpoint proposals, attestations, votes, etc.). ### Testing Failover[​](#testing-failover "Direct link to Testing Failover") To verify HA is working correctly: 1. **Monitor baseline**: Note the duty completion rate with both nodes running 2. **Check database**: Verify both nodes are signing duties (query `validator_duties` table) 3. **Stop one node**: `docker compose down` on one server 4. **Verify continuity**: Check that the remaining node continues handling all validator duties 5. **Check logs**: The remaining node should show normal operation without errors 6. **Monitor database**: The remaining node should continue signing all duty types 7. **Restart the stopped node**: Verify it rejoins seamlessly and resumes signing If validator duties stop when you stop one node, check: * Database connectivity from the remaining node * HA signing is enabled (`VALIDATOR_HA_SIGNING_ENABLED=true`) * Node ID is correctly configured * Database migrations were run successfully ## Operational Best Practices[​](#operational-best-practices "Direct link to Operational Best Practices") ### Load Balancing L1 Access[​](#load-balancing-l1-access "Direct link to Load Balancing L1 Access") If possible, configure each node with its own L1 infrastructure: * **Node 1**: L1 endpoints in Region A * **Node 2**: L1 endpoints in Region B This protects against L1 provider outages affecting both nodes simultaneously. ### Geographic Distribution[​](#geographic-distribution "Direct link to Geographic Distribution") For maximum resilience, distribute nodes across: * Multiple data centers * Different cloud providers * Different geographic regions * Different network availability zones This protects against regional failures, provider outages, and network issues. ### Regular Testing[​](#regular-testing "Direct link to Regular Testing") Periodically test your HA setup: * Simulate node failures (stop nodes intentionally) * Test network partitions (firewall rules) * Test database connectivity issues (temporarily block database access) * Verify monitoring and alerting * Practice recovery procedures * Test rolling upgrades * Verify database cleanup of stuck duties ### Production Deployment Considerations[​](#production-deployment-considerations "Direct link to Production Deployment Considerations") **Database High Availability:** For production, your coordination database should also be highly available: * Use a managed PostgreSQL service (AWS RDS, Google Cloud SQL, Azure Database) with automatic failover * Enable automatic failover to standby replicas (single primary with hot standby) * **Do not use read replicas** for HA signing connections (all nodes must connect to primary) * Configure connection pooling appropriately (`VALIDATOR_HA_POOL_MAX`) * Monitor database performance and connection counts * Set up database backups and point-in-time recovery * Ensure all validator nodes use the same connection string pointing to the primary **Migration Strategy:** Run database migrations before deploying new validator nodes: ``` # Option 1: Run migrations in CI/CD pipeline aztec migrate-ha-db up --database-url $VALIDATOR_HA_DATABASE_URL # Option 2: Use Kubernetes init container (see validator-ha-signer README) # Option 3: Use separate migration job ``` **Monitoring:** Monitor these key metrics: * Database connection pool usage * Signing success/failure rates per node and per duty type * `DutyAlreadySignedError` frequency (expected in HA) * Database query latency * Stuck duty cleanup frequency * Distribution of duty types across nodes (should be relatively even over time) ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Both Nodes Stopped Performing Duties[​](#both-nodes-stopped-performing-duties "Direct link to Both Nodes Stopped Performing Duties") **Issue**: No attestations, proposals, or other validator duties from either node. **Solutions**: * Verify both nodes aren't simultaneously offline * Check L1 connectivity from each node * Verify the shared attester key is correct in both keystores * Check that the sequencer is still registered and active on L1 * Review logs for errors on both nodes * **Verify database connectivity** - check that both nodes can connect to PostgreSQL * **Check HA signing is enabled** - verify `VALIDATOR_HA_SIGNING_ENABLED=true` on both nodes * **Review database logs** - check for connection errors or timeouts * **Query validator\_duties table** - check if duties are being attempted but failing ### Database Connection Issues[​](#database-connection-issues "Direct link to Database Connection Issues") **Issue**: Nodes can't connect to the database or signing fails with database errors. **Solutions**: * Verify database is running and accessible from both nodes * Check network connectivity: `psql $VALIDATOR_HA_DATABASE_URL -c "SELECT 1;"` * Verify connection string format: `postgresql://user:password@host:port/database` * Check firewall rules allow connections from validator nodes * Verify database credentials are correct * Check connection pool limits (increase `VALIDATOR_HA_POOL_MAX` if needed) * Review database logs for connection errors ### Duplicate Signatures Appearing[​](#duplicate-signatures-appearing "Direct link to Duplicate Signatures Appearing") **Issue**: Seeing duplicate signatures for the same duty (proposals, attestations, votes) from your sequencer. **Solutions**: * Verify each node has a unique publisher key * Check that publisher keys aren't duplicated across keystores * Ensure nodes aren't sharing the same keystore file * Review keystore configuration on each node * **Verify HA signing is enabled** - duplicate signatures shouldn't occur with HA enabled * **Check database configuration** - see "Incorrect Database Configuration" below * **Check database** - query `validator_duties` to see if both nodes attempted to sign the same duty * **Review logs** for `DutyAlreadySignedError` (expected) or `SlashingProtectionError` (indicates issue) * **Check duty type** - different duty types (block proposals vs checkpoint proposals vs attestations) should be tracked separately ### Incorrect Database Configuration[​](#incorrect-database-configuration "Direct link to Incorrect Database Configuration") **Issue**: Duplicate signatures despite HA being enabled, or inconsistent behavior across nodes. **Root Cause**: Nodes may be connecting to different database instances or read replicas instead of the same primary database. **Solutions**: * **Verify all nodes use the same connection string** - check `VALIDATOR_HA_DATABASE_URL` on all nodes * **Confirm connecting to primary** - ensure connection string points to the primary database, not a read replica * **Check for multi-master setup** - multi-master or active-active database configurations will break distributed locking * **Test database connectivity** - from each node, run: ``` psql $VALIDATOR_HA_DATABASE_URL -c "SELECT pg_is_in_recovery();" ``` Should return `f` (false) for all nodes, indicating connection to the primary * **Review database failover events** - if using managed services, check if recent failover caused connection issues * **Verify no load balancing to replicas** - ensure database connection pooling or load balancers don't route to read replicas Critical If nodes connect to different database instances or read replicas, the distributed locking will fail and you **will** double-sign, leading to slashing. All nodes must connect to the same primary database. ### One Node Not Contributing[​](#one-node-not-contributing "Direct link to One Node Not Contributing") **Issue**: One node running but not performing validator duties. **Solutions**: * Check that node's sync status * Verify keystore is loaded correctly * Check network connectivity to L1 * Review logs for specific errors * Confirm publisher account has sufficient ETH * **Verify HA configuration** - check `VALIDATOR_HA_SIGNING_ENABLED`, `VALIDATOR_HA_DATABASE_URL`, and `VALIDATOR_HA_NODE_ID` * **Check database** - query to see if the node is attempting to sign duties * **Review logs for HA errors** - look for `DutyAlreadySignedError` (normal) or database connection errors * **Verify node ID is unique** - both nodes must have different `VALIDATOR_HA_NODE_ID` values * **Check duty distribution** - use the duty type distribution query from the verification section ### Keystore Loading Failures[​](#keystore-loading-failures "Direct link to Keystore Loading Failures") **Issue**: Node fails to load the keystore. **Solutions**: * Verify keystore.json syntax is valid * Check file permissions (readable by the node process) * Ensure the keystore path is correct * Validate all private keys are properly formatted * Review the [Keystore Troubleshooting guide](/operate/testnet/operators/keystore/troubleshooting.md) ### Database Migration Issues[​](#database-migration-issues "Direct link to Database Migration Issues") **Issue**: Migrations fail or nodes can't start due to missing tables. **Solutions**: * Verify migrations were run: `aztec migrate-ha-db up --database-url $VALIDATOR_HA_DATABASE_URL` * Check database permissions - the user needs CREATE TABLE privileges * Review migration logs for specific errors * Verify database version is PostgreSQL 12 or later * Check that the `validator_duties`, `schema_version` and `pmigrations` (created by node-pg-migrate) tables exist ## Related Guides[​](#related-guides "Direct link to Related Guides") Running Multiple Sequencers Per Node Want to run multiple sequencer identities on a **single node** instead? See the [Advanced Keystore Patterns guide](/operate/testnet/operators/keystore/advanced-patterns.md#multiple-sequencers)—that's a different use case from HA. ## Next Steps[​](#next-steps "Direct link to Next Steps") * Review the [Advanced Keystore Patterns guide](/operate/testnet/operators/keystore/advanced-patterns.md) for multiple sequencers per node * Set up [monitoring and observability](/operate/testnet/operators/monitoring.md) for your HA infrastructure * Learn about [governance participation](/operate/testnet/operators/sequencer-management/creating_and_voting_on_proposals.md) as a sequencer * Join the [Aztec Discord](https://discord.gg/aztec) for operator support and best practices --- # Registering a Sequencer ## Overview[​](#overview "Direct link to Overview") This guide covers registering your sequencer on the Aztec network through the staking dashboard for **self-staking**. This is one of two ways to participate as a sequencer: 1. **Self-staking** (this guide): You provide your own stake via the staking dashboard 2. **Delegated staking**: You receive stake from delegators (see [Running as a Staking Provider](/operate/testnet/operators/setup/become_a_staking_provider.md)) Before proceeding, ensure you have completed the [Sequencer Setup Guide](/operate/testnet/operators/setup/sequencer_management.md) and your node is running. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Completed sequencer node setup with keystore generated * Access to your **public keystore** file (`keyN_staker_output.json`) * Sufficient **Aztec Token Position (ATP)** or **Aztec Token Vault (ATV)** balance for staking * Wallet with ETH for gas fees * Web browser for accessing the staking dashboard ## Understanding Your Keystore[​](#understanding-your-keystore "Direct link to Understanding Your Keystore") When you generated your sequencer keys, two files were automatically created: 1. **Private keystore** (`~/.aztec/keystore/keyN.json`) - Contains private keys, used by your sequencer node. Keep this secure and never share it. 2. **Public keystore** (`~/.aztec/keystore/keyN_staker_output.json`) - Contains only public information, used for registration via the staking dashboard. ### Public Keystore Structure[​](#public-keystore-structure "Direct link to Public Keystore Structure") The public keystore contains the following information needed for registration: ``` [ { "attester": "0xYOUR_ATTESTER_ADDRESS", "publicKeyG1": { "x": "FIELD_ELEMENT_AS_DECIMAL_STRING", "y": "FIELD_ELEMENT_AS_DECIMAL_STRING" }, "publicKeyG2": { "x0": "FIELD_ELEMENT_AS_DECIMAL_STRING", "x1": "FIELD_ELEMENT_AS_DECIMAL_STRING", "y0": "FIELD_ELEMENT_AS_DECIMAL_STRING", "y1": "FIELD_ELEMENT_AS_DECIMAL_STRING" }, "proofOfPossession": { "x": "FIELD_ELEMENT_AS_DECIMAL_STRING", "y": "FIELD_ELEMENT_AS_DECIMAL_STRING" } } ] ``` **Fields explained:** * **`attester`**: Your Ethereum attester address (sequencer identifier) * **`publicKeyG1`**: BLS public key on the G1 curve (x, y coordinates) * **`publicKeyG2`**: BLS public key on the G2 curve (x0, x1, y0, y1 coordinates) * **`proofOfPossession`**: Cryptographic proof to prevent rogue key attacks tip The public keystore contains no private keys and is safe to share with the staking dashboard or other parties. ## Preparing Your Keystore File[​](#preparing-your-keystore-file "Direct link to Preparing Your Keystore File") ### Single Sequencer[​](#single-sequencer "Direct link to Single Sequencer") If you're registering one sequencer, simply use the `keyN_staker_output.json` file that was generated when you created your keys. ### Multiple Sequencers[​](#multiple-sequencers "Direct link to Multiple Sequencers") If you're registering multiple sequencers in a single transaction, combine the individual keystore files into a single JSON array. Each object in the array represents one sequencer. **Example for two sequencers:** ``` [ { "attester": "0xATTESTER_ADDRESS_1", "publicKeyG1": { "x": "0x...", "y": "0x..." }, "publicKeyG2": { "x0": "0x...", "x1": "0x...", "y0": "0x...", "y1": "0x..." }, "proofOfPossession": { "x": "0x...", "y": "0x..." } }, { "attester": "0xATTESTER_ADDRESS_2", "publicKeyG1": { "x": "0x...", "y": "0x..." }, "publicKeyG2": { "x0": "0x...", "x1": "0x...", "y0": "0x...", "y1": "0x..." }, "proofOfPossession": { "x": "0x...", "y": "0x..." } } ] ``` Simply copy the contents of each `keyN_staker_output.json` file and combine them into a single array. ## Registration Steps[​](#registration-steps "Direct link to Registration Steps") Follow these steps to register your sequencer(s) through the staking dashboard: 1. **Navigate to the staking dashboard** at 2. **Connect your wallet** with the account that holds your Aztec Token Position (ATP) or Aztec Token Vault (ATV) balance 3. **Click "Stake"** ![Staking dashboard home](/assets/images/staking_dashboard_1-f9dd165c0b8b06914f0baa0befa2281a.png) 4. **Select "Run your own Sequencer"** ![Select sequencer option](/assets/images/staking_dashboard_2-ef4c50308b741b29cfec7f995b46baf1.png) 5. **Click through "Start Registration"** after reviewing the requirements 6. **Select the ATP or ATV balance you want to stake** 7. **Upload your keystore JSON file** (either single or combined multi-sequencer file) ![Upload keystore file](/assets/images/staking_dashboard_3-8dcef3510e7073a4b559b11ddb78a254.png) 8. **Confirm your attester/sequencer addresses** ![Confirm addresses](/assets/images/staking_dashboard_4-f7c95ff94cdad0fa714e05a8765c49ac.png) 9. **Approve token spend** in your wallet ![Approve tokens](/assets/images/staking_dashboard_5-9a9d86173c781ba0d45525487dd4fbd5.png) 10. **Add staking for all sequencers to the queue** ![Add to queue](/assets/images/staking_dashboard_6-e791f311fccc92f91dd1901022d73f73.png) 11. **Execute transactions** in the dashboard ![Execute transactions](/assets/images/staking_dashboard_7-411d05fecee6a3f62ff3d580aa932410.png) 12. **Confirm each transaction** in your wallet 13. **Click "Complete"** when all transactions are confirmed 14. **Verification**: Your sequencers have entered the queue. You can verify this at ## Verification[​](#verification "Direct link to Verification") After registration, verify your sequencer is properly registered: ### Via Staking Dashboard[​](#via-staking-dashboard "Direct link to Via Staking Dashboard") Use the staking dashboard to: * View your sequencer's registration status * Monitor your stake amount * Track sequencer performance metrics ### Via Blockchain Explorer[​](#via-blockchain-explorer "Direct link to Via Blockchain Explorer") You can verify your sequencers are in the queue at ### Via Smart Contract[​](#via-smart-contract "Direct link to Via Smart Contract") You can also query the status directly using the Rollup contract. See [Useful Commands](/operate/testnet/operators/sequencer-management/useful-commands.md) for detailed instructions. ## Next Steps[​](#next-steps "Direct link to Next Steps") After registering your sequencer: 1. **Monitor performance**: Track your sequencer's attestation rate and block proposals via the staking dashboard 2. **Maintain uptime**: Keep your sequencer node running with high availability 3. **Monitor your stake**: Ensure your stake remains above the ejection threshold 4. **Stay informed**: Join the [Aztec Discord](https://discord.gg/aztec) for operator support and network updates ## Alternative: Running with Delegated Stake[​](#alternative-running-with-delegated-stake "Direct link to Alternative: Running with Delegated Stake") If you prefer to run a sequencer backed by delegated stake instead of self-staking, see the [Becoming a Staking Provider](/operate/testnet/operators/setup/become_a_staking_provider.md) guide. --- # Running a Full Node ## Overview[​](#overview "Direct link to Overview") This guide covers the steps required to run a full node on Aztec using Docker Compose. A full node allows you to connect and interact with the network, providing an interface to send and receive transactions and state updates without relying on third parties. You should run your own full node if you want to interact with the network in the most privacy-preserving way. It's also a great way to support the Aztec network and get involved with the community. ### Minimum Hardware Requirements[​](#minimum-hardware-requirements "Direct link to Minimum Hardware Requirements") * 8 core / 16 vCPU (released in 2015 or later) * 16 GB RAM * 1 TB NVMe SSD * 25 Mbps network connection These requirements are subject to change as the network throughput increases. **Before proceeding:** Ensure you've reviewed and completed the [prerequisites](/operate/testnet/operators/prerequisites.md). This setup includes only essential settings. The `--network testnet` flag applies network-specific defaults—see the [CLI reference](/operate/testnet/operators/reference/cli-reference.md) for all available configuration options. ## Setup[​](#setup "Direct link to Setup") ### Step 1: Set Up Directory Structure[​](#step-1-set-up-directory-structure "Direct link to Step 1: Set Up Directory Structure") Create the directory structure for node data: ``` mkdir -p aztec-node/data cd aztec-node touch .env ``` ### Step 2: Configure Environment Variables[​](#step-2-configure-environment-variables "Direct link to Step 2: Configure Environment Variables") Add the following to your `.env` file: ``` DATA_DIRECTORY=./data LOG_LEVEL=info ETHEREUM_HOSTS=[your L1 execution endpoint] L1_CONSENSUS_HOST_URLS=[your L1 consensus endpoint] ETHEREUM_DEBUG_HOSTS=[your trace capable L1 execution endpoint] P2P_IP=[your external IP address] P2P_PORT=40400 AZTEC_PORT=8080 AZTEC_ADMIN_PORT=8880 ``` tip Find your public IP address with: `curl ipv4.icanhazip.com` warning In order to retrieve blocks posted to L1 via non-standard contract interactions, it is necessary to have access to an L1 rpc endpoint with 'trace' capability (either `trace_transaction` or `debug_traceTransaction`). The variable `ETHEREUM_DEBUG_HOSTS` is used to provide these url/s to the node. If not provided, the value of this will default to that set in `ETHEREUM_HOSTS`. The node will validate whether it is able to execute a trace call on the provided url/s, if not, it looks to the value set in `ETHEREUM_ALLOW_NO_DEBUG_HOSTS` to determine whether this should prevent the node from starting. By default `ETHEREUM_ALLOW_NO_DEBUG_HOSTS` is `true`, allowing the node to start. Any url provided in `ETHEREUM_DEBUG_HOSTS` will only be used in the case of having to execute a trace, it won't be used in regular L1 interactions. Note - if the node does not have access to an rpc url that is capable of trace calls and it encounters a block posted via a transaction using non-standard contract interactions, it may become stuck and unable to progress the chain. ### Step 3: Create Docker Compose File[​](#step-3-create-docker-compose-file "Direct link to Step 3: Create Docker Compose File") Create a `docker-compose.yml` file in your `aztec-node` directory: ``` services: aztec-node: image: "aztecprotocol/aztec:5.0.0-rc.2" container_name: "aztec-node" ports: - ${AZTEC_PORT}:${AZTEC_PORT} - ${P2P_PORT}:${P2P_PORT} - ${P2P_PORT}:${P2P_PORT}/udp volumes: - ${DATA_DIRECTORY}:/var/lib/data environment: DATA_DIRECTORY: /var/lib/data LOG_LEVEL: ${LOG_LEVEL} ETHEREUM_HOSTS: ${ETHEREUM_HOSTS} L1_CONSENSUS_HOST_URLS: ${L1_CONSENSUS_HOST_URLS} ETHEREUM_DEBUG_HOSTS: ${ETHEREUM_DEBUG_HOSTS} P2P_IP: ${P2P_IP} P2P_PORT: ${P2P_PORT} AZTEC_PORT: ${AZTEC_PORT} AZTEC_ADMIN_PORT: ${AZTEC_ADMIN_PORT} entrypoint: >- node --no-warnings /usr/src/yarn-project/aztec/dest/bin/index.js start --node --network testnet networks: - aztec restart: always networks: aztec: name: aztec ``` Security: Admin Port Not Exposed The admin port (8880) is intentionally **not exposed** to the host machine for security reasons. The admin API provides sensitive operations like configuration changes and database rollbacks that should never be accessible from outside the container. If you need to access admin endpoints, use `docker exec`: ``` docker exec -it aztec-node curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztecAdmin_getConfig","params":[],"id":1}' ``` ### Step 4: Start the Node[​](#step-4-start-the-node "Direct link to Step 4: Start the Node") Start the node: ``` docker compose up -d ``` ## Verification[​](#verification "Direct link to Verification") Once your node is running, verify it's working correctly: ### Check Node Sync Status[​](#check-node-sync-status "Direct link to Check Node Sync Status") Check the current sync status: ``` curl -s -X POST -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getChainTips","params":[],"id":67}' \ http://localhost:8080 | jq -r ".result.proven.number" ``` Compare the output with block explorers (see [Networks page](/networks.md) for explorer links). ### Check Node Status[​](#check-node-status "Direct link to Check Node Status") ``` curl http://localhost:8080/status ``` ### Verify Port Connectivity[​](#verify-port-connectivity "Direct link to Verify Port Connectivity") ``` # Check TCP connectivity on port 40400 nc -vz [YOUR_EXTERNAL_IP] 40400 # Should return: "Connection to [YOUR_EXTERNAL_IP] 40400 port [tcp/*] succeeded!" # Check UDP connectivity on port 40400 nc -vu [YOUR_EXTERNAL_IP] 40400 # Should return: "Connection to [YOUR_EXTERNAL_IP] 40400 port [udp/*] succeeded!" ``` ### View Logs[​](#view-logs "Direct link to View Logs") ``` docker compose logs -f aztec-node ``` If all checks pass, your node should be up, running, and connected to the network. ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Port forwarding not working[​](#port-forwarding-not-working "Direct link to Port forwarding not working") **Issue**: Your node cannot connect to peers. **Solutions**: * Verify your external IP address matches the `P2P_IP` setting * Check firewall rules on your router and local machine * Test connectivity using: `nc -zv [your-ip] 40400` ### Node not syncing[​](#node-not-syncing "Direct link to Node not syncing") **Issue**: Your node is not synchronizing with the network. **Solutions**: * Check L1 endpoint connectivity * Verify both execution and consensus clients are fully synced * Review logs for specific error messages * Ensure L1 endpoints support high throughput ### Docker issues[​](#docker-issues "Direct link to Docker issues") **Issue**: Container won't start or crashes. **Solutions**: * Ensure Docker and Docker Compose are up to date * Check disk space availability * Verify the `.env` file is properly formatted * Review container logs: `docker compose logs aztec-node` ## Next Steps[​](#next-steps "Direct link to Next Steps") * Review [syncing best practices](/operate/testnet/operators/setup/syncing_best_practices.md) for faster synchronization * Learn about [bootnode operation](/operate/testnet/operators/setup/bootnode_operation.md) for peer discovery * Check the [CLI reference](/operate/testnet/operators/reference/cli-reference.md) for advanced configuration options * Join the [Aztec Discord](https://discord.gg/aztec) for support and community discussions --- # Running a Prover ## Overview[​](#overview "Direct link to Overview") This guide covers the steps required to run a prover on the Aztec network. Operating a prover is a resource-intensive role typically undertaken by experienced engineers due to its technical complexity and hardware requirements. Aztec provers are critical infrastructure components. They generate cryptographic proofs attesting to transaction correctness, ultimately producing a single rollup proof submitted to Ethereum. Prerequisites Before proceeding, ensure you've reviewed and completed the [prerequisites](/operate/testnet/operators/prerequisites.md). ## Prover Architecture[​](#prover-architecture "Direct link to Prover Architecture") The prover consists of three main components: 1. **Prover node**: Polls L1 for unproven epochs, creates prover jobs, distributes them to the broker, and submits the final rollup proof to the rollup contract. 2. **Prover broker**: Manages the job queue, distributing work to agents and collecting results. 3. **Prover agent(s)**: Executes proof generation jobs in a stateless manner. ## Minimum Requirements[​](#minimum-requirements "Direct link to Minimum Requirements") ### Prover Node[​](#prover-node "Direct link to Prover Node") * 16 core / 32 vCPU (released in 2015 or later) * 16 GB RAM * 1 TB NVMe SSD * 25 Mbps network connection ### Prover Broker[​](#prover-broker "Direct link to Prover Broker") * 8 core / 16 vCPU (released in 2015 or later) * 16 GB RAM * 10 GB SSD ### Prover Agents[​](#prover-agents "Direct link to Prover Agents") **For each agent:** * 32 core / 64 vCPU (released in 2015 or later) * 128 GB RAM * 10 GB SSD These requirements are subject to change as the network throughput increases. Prover agents require high-performance hardware, typically data center-grade infrastructure. Running Multiple Agents You can run multiple prover agents on a single machine by adjusting `PROVER_AGENT_COUNT`. Hardware requirements scale approximately linearly: * **2 agents**: 64 cores, 256 GB RAM * **3 agents**: 96 cores, 384 GB RAM * **4 agents**: 128 cores, 512 GB RAM ## Generating Keys[​](#generating-keys "Direct link to Generating Keys") Before setting up your prover, you need to generate the required Ethereum private key for the prover publisher. ### Prover Publisher Private Key[​](#prover-publisher-private-key "Direct link to Prover Publisher Private Key") The prover publisher key is used to submit proofs to L1. This account needs ETH funding to pay for L1 gas. Generate an Ethereum private key using Foundry's `cast` tool: ``` # Generate a new wallet with a 24-word mnemonic cast wallet new-mnemonic --words 24 # This outputs a mnemonic phrase, a derived address, and private key # Save these securely - you'll need the private key for PROVER_PUBLISHER_PRIVATE_KEY # and the address for PROVER_ID ``` **Important notes:** * Save both the private key and the derived address securely * The private key will be used for `PROVER_PUBLISHER_PRIVATE_KEY` * The derived Ethereum address will be used for `PROVER_ID` Account Funding Required The publisher account needs to be funded with ETH to post proofs to L1. Ensure the account holds sufficient ETH for gas costs during operation. tip If you don't have Foundry installed, follow the installation guide at [getfoundry.sh](https://getfoundry.sh/). ## Setup[​](#setup "Direct link to Setup") The prover components are distributed across multiple machines for better performance and resource utilization. This setup runs multiple prover agents on separate high-performance machines, isolates the broker for better job queue management, and separates network-facing components (prover node) from compute-intensive components (agents). ### Architecture[​](#architecture "Direct link to Architecture") * **Prover Node**: Runs on a machine with network access and L1 connectivity * **Prover Broker**: Can run on the same machine as the prover node or separately (must be accessible from prover agents) * **Prover Agents**: Run on separate high-performance machines (32+ cores each, scalable with `PROVER_AGENT_COUNT`) Network Requirements Prover agents must communicate with the prover broker over the network. Ensure that: * The broker machine's port 8080 is accessible from all agent machines * Firewall rules allow traffic between agents and broker * Network connectivity is stable and low-latency between components ### Prover Node and Broker Setup[​](#prover-node-and-broker-setup "Direct link to Prover Node and Broker Setup") On the machine that will run the prover node and broker: #### Step 1: Set Up Directory Structure[​](#step-1-set-up-directory-structure "Direct link to Step 1: Set Up Directory Structure") ``` mkdir -p aztec-prover-node/prover-node-data aztec-prover-node/prover-broker-data cd aztec-prover-node touch .env ``` #### Step 2: Configure Environment Variables[​](#step-2-configure-environment-variables "Direct link to Step 2: Configure Environment Variables") Add to your `.env` file: ``` # Prover Node Configuration DATA_DIRECTORY=./prover-node-data P2P_IP=[your external IP address] P2P_PORT=40400 ETHEREUM_HOSTS=[your L1 execution endpoint] L1_CONSENSUS_HOST_URLS=[your L1 consensus endpoint] LOG_LEVEL=info PROVER_BROKER_HOST=http://prover-broker:8080 PROVER_PUBLISHER_PRIVATE_KEY=[your prover publisher private key, see prerequisites] AZTEC_PORT=8080 AZTEC_ADMIN_PORT=8880 # Prover Broker Configuration PROVER_BROKER_DATA_DIRECTORY=./prover-broker-data PROVER_BROKER_PORT=8080 ``` #### Step 3: Create Docker Compose File[​](#step-3-create-docker-compose-file "Direct link to Step 3: Create Docker Compose File") Create `docker-compose.yml`: ``` name: aztec-prover-node services: prover-node: image: aztecprotocol/aztec:5.0.0-rc.2 entrypoint: >- node --no-warnings /usr/src/yarn-project/aztec/dest/bin/index.js start --prover-node --network testnet depends_on: prover-broker: condition: service_started required: true environment: DATA_DIRECTORY: /var/lib/data ETHEREUM_HOSTS: ${ETHEREUM_HOSTS} L1_CONSENSUS_HOST_URLS: ${L1_CONSENSUS_HOST_URLS} LOG_LEVEL: ${LOG_LEVEL} PROVER_BROKER_HOST: ${PROVER_BROKER_HOST} PROVER_PUBLISHER_PRIVATE_KEY: ${PROVER_PUBLISHER_PRIVATE_KEY} P2P_IP: ${P2P_IP} P2P_PORT: ${P2P_PORT} AZTEC_PORT: ${AZTEC_PORT} AZTEC_ADMIN_PORT: ${AZTEC_ADMIN_PORT} ports: - ${AZTEC_PORT}:${AZTEC_PORT} - ${P2P_PORT}:${P2P_PORT} - ${P2P_PORT}:${P2P_PORT}/udp volumes: - ${DATA_DIRECTORY}:/var/lib/data restart: unless-stopped prover-broker: image: aztecprotocol/aztec:5.0.0-rc.2 entrypoint: >- node --no-warnings /usr/src/yarn-project/aztec/dest/bin/index.js start --prover-broker --network testnet environment: DATA_DIRECTORY: /var/lib/data ETHEREUM_HOSTS: ${ETHEREUM_HOSTS} P2P_IP: ${P2P_IP} LOG_LEVEL: ${LOG_LEVEL} ports: - ${PROVER_BROKER_PORT}:8080 volumes: - ${PROVER_BROKER_DATA_DIRECTORY}:/var/lib/data restart: unless-stopped ``` Security: Admin Port Not Exposed The admin port (8880) is intentionally **not exposed** to the host machine for security reasons. The admin API provides sensitive operations like configuration changes and database rollbacks that should never be accessible from outside the container. If you need to access admin endpoints, use `docker exec`: ``` docker exec -it prover-node curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztecAdmin_getConfig","params":[],"id":1}' ``` **Important:** The broker exposes port 8080 via `ports: - ${PROVER_BROKER_PORT}:8080`, making it accessible to external prover agents. Ensure this port is reachable from your agent machines. This configuration includes only essential settings. The `--network testnet` flag applies network-specific defaults—see the [CLI reference](/operate/testnet/operators/reference/cli-reference.md) for all available configuration options. #### Step 4: Start Node and Broker[​](#step-4-start-node-and-broker "Direct link to Step 4: Start Node and Broker") ``` docker compose up -d ``` ### Prover Agent Setup[​](#prover-agent-setup "Direct link to Prover Agent Setup") On each machine that will run prover agents: #### Step 1: Set Up Directory[​](#step-1-set-up-directory "Direct link to Step 1: Set Up Directory") ``` mkdir aztec-prover-agent cd aztec-prover-agent touch .env ``` #### Step 2: Configure Environment Variables[​](#step-2-configure-environment-variables-1 "Direct link to Step 2: Configure Environment Variables") Add to your `.env` file: ``` PROVER_AGENT_COUNT=1 PROVER_AGENT_POLL_INTERVAL_MS=10000 PROVER_BROKER_HOST=http://[BROKER_MACHINE_IP]:8080 PROVER_ID=[address corresponding to PROVER_PUBLISHER_PRIVATE_KEY] ``` Replace `[BROKER_MACHINE_IP]` with the IP address of the machine running the prover broker. **Agent configuration tips:** * Set `PROVER_AGENT_COUNT` based on your machine's hardware (e.g., 64 cores/256 GB RAM = 2 agents, 96 cores/384 GB RAM = 3 agents, 128 cores/512 GB RAM = 4 agents) * Test connectivity before starting: `curl http://[BROKER_MACHINE_IP]:8080` * If the curl test fails, check your network configuration, firewall rules, and ensure the broker is running #### Step 3: Create Docker Compose File[​](#step-3-create-docker-compose-file-1 "Direct link to Step 3: Create Docker Compose File") Create `docker-compose.yml`: ``` name: aztec-prover-agent services: prover-agent: image: aztecprotocol/aztec:5.0.0-rc.2 entrypoint: >- node --no-warnings /usr/src/yarn-project/aztec/dest/bin/index.js start --prover-agent --network testnet environment: PROVER_AGENT_COUNT: ${PROVER_AGENT_COUNT} PROVER_AGENT_POLL_INTERVAL_MS: ${PROVER_AGENT_POLL_INTERVAL_MS} PROVER_BROKER_HOST: ${PROVER_BROKER_HOST} PROVER_ID: ${PROVER_ID} restart: unless-stopped ``` #### Step 4: Start Agent[​](#step-4-start-agent "Direct link to Step 4: Start Agent") ``` docker compose up -d ``` **Scaling your prover capacity:** * **Horizontal scaling**: Add more agent machines by repeating the agent setup on additional high-performance machines * **Vertical scaling**: Increase `PROVER_AGENT_COUNT` on existing machines (ensure adequate hardware) All agents, regardless of which machine they're on, must be able to communicate with the broker at the configured `PROVER_BROKER_HOST`. ## Verification[​](#verification "Direct link to Verification") Once your prover is running, verify all components are working correctly: ### Check Services[​](#check-services "Direct link to Check Services") On the prover node machine: ``` docker compose ps ``` On each agent machine: ``` docker compose ps ``` ### View Logs[​](#view-logs "Direct link to View Logs") On prover node machine: ``` # Prover node logs docker compose logs -f prover-node # Broker logs docker compose logs -f prover-broker ``` On agent machines: ``` # Agent logs docker compose logs -f prover-agent ``` ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Components not communicating[​](#components-not-communicating "Direct link to Components not communicating") **Issue**: Prover agent cannot connect to broker. **Solutions**: * Verify the broker IP address in `PROVER_BROKER_HOST` is correct * Ensure port 8080 on the broker machine is accessible from agent machines * Check firewall rules between machines allow traffic on port 8080 * Test connectivity from agent machine: `curl http://[BROKER_IP]:8080` * Verify the broker container is running: `docker compose ps` * Check if the broker port is exposed in docker-compose.yml * Review broker logs for connection attempts: `docker compose logs prover-broker` ### Insufficient resources[​](#insufficient-resources "Direct link to Insufficient resources") **Issue**: Prover agent crashes or performs poorly. **Solutions**: * Verify your hardware meets the minimum requirements (32 cores per agent, 128 GB RAM per agent) * Check system resource usage: `docker stats` * Reduce `PROVER_AGENT_COUNT` if running multiple agents per machine * Ensure no other resource-intensive processes are running * Monitor CPU and memory usage to verify resources match your configured agent count ### Agent not picking up jobs[​](#agent-not-picking-up-jobs "Direct link to Agent not picking up jobs") **Issue**: Agent logs show no job activity. **Solutions**: * Verify the broker is receiving jobs from the prover node * Check broker logs for errors * Confirm `PROVER_ID` matches your publisher address * Ensure agent can reach the broker endpoint * Test broker connectivity: `curl http://[BROKER_IP]:8080` ### Docker issues[​](#docker-issues "Direct link to Docker issues") **Issue**: Containers won't start or crash repeatedly. **Solutions**: * Ensure Docker and Docker Compose are up to date * Check disk space availability on all machines * Verify `.env` files are properly formatted * Review logs for specific error messages ### Common Issues[​](#common-issues "Direct link to Common Issues") See the [Operator FAQ](/operate/testnet/operators/operator-faq.md) for additional common issues and resolutions. ## Next Steps[​](#next-steps "Direct link to Next Steps") * Monitor your prover's performance and proof submission rate * Consider adding more prover agents for increased capacity (either by increasing `PROVER_AGENT_COUNT` or adding more machines) * Join the [Aztec Discord](https://discord.gg/aztec) for operator support * Review [governance participation](/operate/testnet/operators/sequencer-management/creating_and_voting_on_proposals.md) for participating in governance --- # Running a Sequencer ## Overview[​](#overview "Direct link to Overview") This guide covers sequencer lifecycle management on the Aztec network: keystore configuration, node setup, registration, ongoing operations, and eventual exit. Minimum Stake Requirement To participate as a sequencer on the Aztec network, you must stake a minimum of **200,000 AZTEC tokens**. Ensure you have sufficient tokens before proceeding with sequencer setup and registration. Sequencer nodes are critical infrastructure responsible for ordering transactions and producing blocks. They perform three key actions: 1. Assemble unprocessed transactions and propose the next block 2. Attest to correct execution of transactions in proposed blocks (when part of the sequencer committee) 3. Submit successfully attested blocks to L1 Before publication, blocks must be validated by a committee of sequencer nodes who re-execute public transactions and verify private function proofs. Committee members attest to validity by signing the block header. Once sufficient attestations are collected (two-thirds of the committee plus one), the block can be submitted to L1. ### Minimum Hardware Requirements[​](#minimum-hardware-requirements "Direct link to Minimum Hardware Requirements") * 8 core / 16 vCPU (released in 2015 or later) * 16 GB RAM * 1 TB NVMe SSD * 25 Mbps network connection These requirements are subject to change as the network throughput increases. **Before proceeding:** Ensure you've reviewed and completed the [prerequisites](/operate/testnet/operators/prerequisites.md). ## Keystore Explanation[​](#keystore-explanation "Direct link to Keystore Explanation") Sequencers require private keys to identify themselves as valid proposers and attesters. These keys are configured through a private keystore file. ### Private Keystore Structure[​](#private-keystore-structure "Direct link to Private Keystore Structure") The private keystore file (`keystore.json`) uses the following structure: ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "ETH_PRIVATE_KEY", "bls": "BLS_PRIVATE_KEY" }, "publisher": ["PUBLISHER_PRIVATE_KEY"], // Optional: defaults to attester key "feeRecipient": "0x0000000000000000000000000000000000000000000000000000000000000000", // Not currently used, set to all zeros "coinbase": "ETH_ADDRESS" } ] } ``` info The attester field contains both Ethereum and BLS keys: * **ETH key**: Derives the address that serves as your sequencer's unique identifier in the protocol * **BLS key**: Used to sign proposals and attestations, as well as for staking operations ### Field Descriptions[​](#field-descriptions "Direct link to Field Descriptions") #### attester (required)[​](#attester-required "Direct link to attester (required)") **Your sequencer's identity.** Contains both Ethereum and BLS keys: * **Format**: Object with `eth` and `bls` fields * **eth**: Ethereum private key - the derived address serves as your sequencer's unique identifier in the protocol * **bls**: BLS private key - actually signs proposals and attestations, and is used for staking operations (validator registration and proof of possession) * **Purpose**: The ETH address identifies your sequencer, while the BLS key performs the cryptographic signing of consensus messages #### publisher (optional)[​](#publisher-optional "Direct link to publisher (optional)") Separate private key(s) for submitting BLS-signed messages to L1. The publisher just pays gas to post already-signed proposals and attestations. * **Format**: Array of Ethereum private keys * **Default**: Uses attester key if not specified * **Purpose**: Posts signed messages to L1 and pays for gas (doesn't participate in signing) * **Rule of thumb**: Ensure every publisher account maintains at least 0.1 ETH per attester account it serves. This balance allows the selected publisher to successfully post transactions when chosen. tip If you're using the attester ETH key for publishing (no separate publisher keys), you can omit the `publisher` field entirely from your keystore, but you will still need to fund the attester account according to the rule of thumb above. #### feeRecipient[​](#feerecipient "Direct link to feeRecipient") Aztec address that would receive L2 transaction fees. * **Format**: 32-byte Aztec address (64 hex characters) * **Current status**: Not currently used by the protocol - set to `0x0000000000000000000000000000000000000000000000000000000000000000` * **Purpose**: Reserved for future fee distribution mechanisms #### coinbase (optional)[​](#coinbase-optional "Direct link to coinbase (optional)") Ethereum address that receives all L1 block rewards and tx fees. * **Format**: Ethereum address * **Default**: Uses attester address if not specified ### Generating Keys[​](#generating-keys "Direct link to Generating Keys") Use the Aztec CLI's keystore utility to generate both your private and public keystores: ``` aztec validator-keys new \ --fee-recipient 0x0000000000000000000000000000000000000000000000000000000000000000 \ --staker-output \ --gse-address 0xb6a38a51a6c1de9012f9d8ea9745ef957212eaac \ --l1-rpc-urls $ETH_RPC ``` **Relevant parameters:** * `--fee-recipient`: Set to all zeros (not currently used by the protocol) * `--staker-output`: Generate the public keystore for the staking dashboard * `--gse-address`: The GSE (Governance Staking Escrow) contract address (`0xb6a38a51a6c1de9012f9d8ea9745ef957212eaac` for Sepolia testnet) * `--l1-rpc-urls`: Your Ethereum Sepolia L1 RPC endpoint * Set `ETH_RPC` environment variable, or replace `$ETH_RPC` with your RPC URL (e.g., `https://sepolia.infura.io/v3/YOUR_API_KEY`) * `--count`: Number of validator identities to generate (default: 1) * Use this to generate multiple attester identities in a single keystore * Example: `--count 5` generates 5 validator identities with sequential addresses * All identities are derived from the same mnemonic using different derivation paths * Useful for operators running multiple sequencer identities or delegated staking providers * `--publisher-count` Number of publisher accounts per validator (default 1) **This command creates two JSON files:** 1. **Private keystore** (`~/.aztec/keystore/keyN.json`) - Contains your ETH and BLS private keys for running the node 2. **Public keystore** (`~/.aztec/keystore/keyN_staker_output.json`) - Contains only public information (public keys and proof of possession) for the staking dashboard Where `N` is an auto-incrementing number (e.g., `key1.json`, `key2.json`, etc.) **What gets generated:** * Automatically generates a mnemonic for key derivation (or provide your own with `--mnemonic`) * Creates an ETH key (for your sequencer identifier) and BLS key (for signing) * Computes BLS public keys (G1 and G2) and proof of possession * Outputs your attester address, publisher address and BLS public keys to the console **Example output (single validator):** ``` No mnemonic provided, generating new one... Using new mnemonic: word1 word2 word3 word4 word5 word6 word7 word8 word9 word10 word11 word12 Wrote validator keystore to /Users/aztec/.aztec/keystore/key1.json Wrote staker output for 1 validator(s) to /Users/aztec/.aztec/keystore/key1_staker_output.json acc1: attester: eth: 0xA55aB561877E479361BA033c4ff7B516006CF547 bls: 0xa931139040533679ff3990bfc4f40b63f50807815d77346e3c02919d71891dc1 ``` **Example output (multiple validators with `--count 3`):** ``` No mnemonic provided, generating new one... Using new mnemonic: word1 word2 word3 word4 word5 word6 word7 word8 word9 word10 word11 word12 Wrote validator keystore to /Users/aztec/.aztec/keystore/key1.json Wrote staker output for 3 validator(s) to /Users/aztec/.aztec/keystore/key1_staker_output.json acc1: attester: eth: 0xA55aB561877E479361BA033c4ff7B516006CF547 bls: 0xa931139040533679ff3990bfc4f40b63f50807815d77346e3c02919d71891dc1 acc2: attester: eth: 0xB66bC672988F590472CA144e5D8d9F82307DA658 bls: 0xb842240151644780ff4991cfd5f51c74f61918926e88457f4d13020e82902ed2 acc3: attester: eth: 0xC77cD783999F601583DB255f6E9e0F93418EB769 bls: 0xc953351262755891ff5aa2dfe6f62d85f72a29a37f99568f5e24131f93a13fe3 ``` **Critical: Save your mnemonic phrase!** * The mnemonic is the **only thing you must save** - it can regenerate all your keys, addresses, and keystores * Store it securely offline (not on the server running the node) **For convenience, note:** * **Attester address** (eth): Your sequencer's identifier (e.g., `0xA55aB...F547`) - useful for registration and monitoring * **File paths**: Where the keystores were saved All other information (BLS keys, public keys, addresses) can be re-derived from the mnemonic if needed. Provide Your Own Mnemonic For deterministic key generation or to recreate keys later, provide your own mnemonic: ``` aztec validator-keys new \ --fee-recipient 0x0000000000000000000000000000000000000000000000000000000000000000 \ --staker-output \ --gse-address 0xb6a38a51a6c1de9012f9d8ea9745ef957212eaac \ --l1-rpc-urls $ETH_RPC \ --mnemonic "your twelve word mnemonic phrase here" ``` Generate Multiple Validator Identities To generate multiple validator identities (useful for delegated staking providers or operators running multiple sequencers): ``` # Generate 5 validator identities from the same mnemonic aztec validator-keys new \ --fee-recipient 0x0000000000000000000000000000000000000000000000000000000000000000 \ --staker-output \ --gse-address 0xb6a38a51a6c1de9012f9d8ea9745ef957212eaac \ --l1-rpc-urls $ETH_RPC \ --count 5 ``` Each identity gets a unique attester address derived from sequential derivation paths. All identities are included in: * The same private keystore file (`keyN.json`) * The same public keystore file (`keyN_staker_output.json`) For detailed instructions, advanced options, and complete examples, see the [Creating Sequencer Keystores guide](/operate/testnet/operators/keystore/creating_keystores.md). ## Setup with Docker Compose[​](#setup-with-docker-compose "Direct link to Setup with Docker Compose") ### Step 1: Set Up Directory Structure[​](#step-1-set-up-directory-structure "Direct link to Step 1: Set Up Directory Structure") Create the directory structure for sequencer data storage: ``` mkdir -p aztec-sequencer/keys aztec-sequencer/data cd aztec-sequencer touch .env ``` ### Step 2: Generate and Move Private Keystore to Docker Directory[​](#step-2-generate-and-move-private-keystore-to-docker-directory "Direct link to Step 2: Generate and Move Private Keystore to Docker Directory") If you haven't already generated your private and public keystores, do so now (see [Generating Keys](#generating-keys) above). Move the private keystore (not the public keystore) into the Docker directory: ``` # Move the private keystore to Docker directory (replace N with your key number) cp ~/.aztec/keystore/keyN.json aztec-sequencer/keys/keystore.json # Keep the public keystore for later use with the staking dashboard # It will be at ~/.aztec/keystore/keyN_staker_output.json ``` ### Step 3: Fund Your Publisher Account[​](#step-3-fund-your-publisher-account "Direct link to Step 3: Fund Your Publisher Account") Your sequencer needs ETH to pay for gas when submitting blocks to L1. Fund the account that will act as the publisher. **Determine which address to fund:** ``` # Get your attester address (this will be your publisher if no separate publisher is configured) jq -r '.[0].attester' ~/.aztec/keystore/keyN_staker_output.json # If you have a separate publisher configured: (Note this returns the publisher private key) jq -r '.validators[0].publisher[0]' aztec-sequencer/keys/keystore.json ``` **Funding requirements:** * **Rule of thumb**: Maintain at least **0.1 ETH per attester account** in each publisher account * Publisher accounts submit blocks to L1 and pay for gas fees * The system does not retry with another publisher if a transaction fails due to insufficient funds **Examples:** * 1 attester with 1 publisher (or using attester as publisher) → Maintain ≥ 0.1 ETH * 3 attesters with 1 publisher → Maintain ≥ 0.3 ETH in that publisher account * 3 attesters with 2 publishers → Maintain ≥ 0.15 ETH in each publisher account (0.3 ETH total) tip Set up monitoring or alerts to notify you when the publisher balance falls below the recommended threshold to prevent failed block publications. ### Step 4: Configure Environment Variables[​](#step-4-configure-environment-variables "Direct link to Step 4: Configure Environment Variables") Add the following to your `.env` file: ``` DATA_DIRECTORY=./data KEY_STORE_DIRECTORY=./keys LOG_LEVEL=info ETHEREUM_HOSTS=[your Ethereum Sepolia execution endpoint, or a comma separated list if you have multiple] L1_CONSENSUS_HOST_URLS=[your Ethereum Sepolia consensus endpoint, or a comma separated list if you have multiple] ETHEREUM_DEBUG_HOSTS=[your trace capable L1 execution endpoint] P2P_IP=[your external IP address] P2P_PORT=40400 AZTEC_PORT=8080 AZTEC_ADMIN_PORT=8880 ``` tip Find your public IP address with: `curl ipv4.icanhazip.com` Nethermind Users (versions before v1.36.0) If you are using Nethermind as your L1 execution client with a version before v1.36.0, you must add the following environment variable: ``` # Required for Nethermind versions before v1.36.0 L1_FIXED_PRIORITY_FEE_PER_GAS=1 ``` This issue was fixed in Nethermind v1.36.0, so users on that version or later do not need this setting. warning In order to retrieve blocks posted to L1 via non-standard contract interactions, it is necessary to have access to an L1 rpc endpoint with 'trace' capability (either `trace_transaction` or `debug_traceTransaction`). The variable `ETHEREUM_DEBUG_HOSTS` is used to provide these url/s to the node. If not provided, the value of this will default to that set in `ETHEREUM_HOSTS`. The node will validate whether it is able to execute a trace call on the provided url/s, if not, it looks to the value set in `ETHEREUM_ALLOW_NO_DEBUG_HOSTS` to determine whether this should prevent the node from starting. By default `ETHEREUM_ALLOW_NO_DEBUG_HOSTS` is `true`, allowing the node to start. Any url provided in `ETHEREUM_DEBUG_HOSTS` will only be used in the case of having to execute a trace, it won't be used in regular L1 interactions. Note - if the node does not have access to an rpc url that is capable of trace calls and it encounters a block posted via a transaction using non-standard contract interactions, it may become stuck and unable to progress the chain. ### Step 5: Create Docker Compose File[​](#step-5-create-docker-compose-file "Direct link to Step 5: Create Docker Compose File") Create a `docker-compose.yml` file in your `aztec-sequencer` directory: ``` services: aztec-sequencer: image: "aztecprotocol/aztec:5.0.0-rc.2" container_name: "aztec-sequencer" ports: - ${AZTEC_PORT}:${AZTEC_PORT} - ${P2P_PORT}:${P2P_PORT} - ${P2P_PORT}:${P2P_PORT}/udp volumes: - ${DATA_DIRECTORY}:/var/lib/data - ${KEY_STORE_DIRECTORY}:/var/lib/keystore environment: KEY_STORE_DIRECTORY: /var/lib/keystore DATA_DIRECTORY: /var/lib/data LOG_LEVEL: ${LOG_LEVEL} ETHEREUM_HOSTS: ${ETHEREUM_HOSTS} L1_CONSENSUS_HOST_URLS: ${L1_CONSENSUS_HOST_URLS} ETHEREUM_DEBUG_HOSTS: ${ETHEREUM_DEBUG_HOSTS} P2P_IP: ${P2P_IP} P2P_PORT: ${P2P_PORT} AZTEC_PORT: ${AZTEC_PORT} AZTEC_ADMIN_PORT: ${AZTEC_ADMIN_PORT} entrypoint: >- node --no-warnings /usr/src/yarn-project/aztec/dest/bin/index.js start --node --sequencer --network testnet networks: - aztec restart: always networks: aztec: name: aztec ``` Security: Admin Port Not Exposed The admin port (8880) is intentionally **not exposed** to the host machine for security reasons. The admin API provides sensitive operations like configuration changes and database rollbacks that should never be accessible from outside the container. If you need to access admin endpoints, use `docker exec`: ``` docker exec -it aztec-sequencer curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztecAdmin_getConfig","params":[],"id":1}' ``` This configuration includes only essential settings. The `--network testnet` flag applies network-specific defaults—see the [CLI reference](/operate/testnet/operators/reference/cli-reference.md) for all available configuration options. ### Step 6: Start the Sequencer[​](#step-6-start-the-sequencer "Direct link to Step 6: Start the Sequencer") Start the sequencer: ``` docker compose up -d ``` ## Verification[​](#verification "Direct link to Verification") Once your sequencer is running, verify it's working correctly: ### Check Sync Status[​](#check-sync-status "Direct link to Check Sync Status") Check the current sync status (this may take a few minutes): ``` curl -s -X POST -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"aztec_getChainTips","params":[],"id":67}' \ http://localhost:8080 | jq -r ".result.proven.number" ``` Compare the output with block explorers (see [Networks page](/networks.md) for explorer links). ### Check Node Status[​](#check-node-status "Direct link to Check Node Status") ``` curl http://localhost:8080/status ``` ### View Logs[​](#view-logs "Direct link to View Logs") ``` docker compose logs -f --tail 100 aztec-sequencer ``` ## Next Steps: Registering Your Sequencer[​](#next-steps-registering-your-sequencer "Direct link to Next Steps: Registering Your Sequencer") Now that your sequencer node is set up and running, you need to register it with the network. There are two ways to participate as a sequencer: ### Option 1: Self-Staking via Staking Dashboard[​](#option-1-self-staking-via-staking-dashboard "Direct link to Option 1: Self-Staking via Staking Dashboard") Register your sequencer and provide your own stake through the staking dashboard. This is the most common approach for individual operators. **→ [Register Your Sequencer (Self-Staking)](/operate/testnet/operators/setup/registering_sequencer.md)** You'll use the **public keystore** file (`keyN_staker_output.json`) that was generated when you created your keys. ### Option 2: Running with Delegated Stake[​](#option-2-running-with-delegated-stake "Direct link to Option 2: Running with Delegated Stake") Operate sequencers backed by tokens from delegators. This non-custodial system allows you to run sequencer infrastructure while delegators provide the economic backing. **→ [Run as a Staking Provider](/operate/testnet/operators/setup/become_a_staking_provider.md)** As a provider, you'll register with the Staking Registry and manage a queue of sequencer identities that activate when delegators stake to you. Which Option Should I Choose? * **Self-staking**: You have tokens and want to run your own sequencer * **Delegated staking**: You want to operate sequencer infrastructure and earn commission from delegators' stake Both options use the same node setup from this guide. ## Monitoring Sequencer Status[​](#monitoring-sequencer-status "Direct link to Monitoring Sequencer Status") You can query the status of any sequencer (attester) using the Rollup and GSE (Governance Staking Escrow) contracts on L1. ### Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Foundry installed (`cast` command) * Ethereum RPC endpoint * Registry contract address for your network ### Get Contract Addresses[​](#get-contract-addresses "Direct link to Get Contract Addresses") First, get the canonical Rollup contract address from the Registry: ``` # Get the canonical rollup address cast call [REGISTRY_CONTRACT_ADDRESS] "getCanonicalRollup()" --rpc-url [YOUR_RPC_URL] ``` Then get the GSE contract address from the Rollup: ``` # Get the GSE contract address cast call [ROLLUP_ADDRESS] "getGSE()" --rpc-url [YOUR_RPC_URL] ``` ### Query Sequencer Status[​](#query-sequencer-status "Direct link to Query Sequencer Status") Check the complete status and information for a specific sequencer: ``` # Get full attester view (status, balance, exit info, config) cast call [ROLLUP_ADDRESS] "getAttesterView(address)" [ATTESTER_ADDRESS] --rpc-url [YOUR_RPC_URL] ``` This returns an `AttesterView` struct containing: 1. **status** - The sequencer's current status (see Status Codes below) 2. **effectiveBalance** - The sequencer's effective stake balance 3. **exit** - Exit information (if the sequencer is exiting) 4. **config** - Attester configuration (withdrawer address and public key) #### Status Codes[​](#status-codes "Direct link to Status Codes") | Status | Name | Meaning | | ------ | ---------- | ------------------------------------------------------------------------------------------------------------------------------- | | 0 | NONE | The sequencer does not exist in the sequencer set | | 1 | VALIDATING | The sequencer is currently active and participating in consensus | | 2 | ZOMBIE | The sequencer is not active (balance fell below ejection threshold, possibly due to slashing) but still has funds in the system | | 3 | EXITING | The sequencer has initiated withdrawal and is in the exit delay period | ### Performance Monitoring[​](#performance-monitoring "Direct link to Performance Monitoring") Track your sequencer's performance by monitoring: * **Effective balance** - Should remain above the ejection threshold * **Status** - Should be VALIDATING for active participation * **Attestation rate** - How many attestations you've successfully submitted * **Proposal success rate** - How many of your proposed blocks were accepted * **Network participation metrics** - Overall participation in network consensus ## Exiting a Sequencer[​](#exiting-a-sequencer "Direct link to Exiting a Sequencer") warning Information about the exit process will be added when the mechanism is finalized. Check the [Aztec Discord](https://discord.gg/aztec) for the latest information on exiting the sequencer set. ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Port forwarding not working[​](#port-forwarding-not-working "Direct link to Port forwarding not working") **Issue**: Your node cannot connect to peers. **Solutions**: * Verify your external IP address matches the `P2P_IP` setting * Check firewall rules on your router and local machine * Test connectivity using: `nc -zv [your-ip] 40400` ### Sequencer not syncing[​](#sequencer-not-syncing "Direct link to Sequencer not syncing") **Issue**: Your node is not synchronizing with the network. **Solutions**: * Check L1 endpoint connectivity * Verify both execution and consensus clients are fully synced * Review logs for specific error messages * Ensure L1 endpoints support high throughput ### Private keystore issues[​](#private-keystore-issues "Direct link to Private keystore issues") **Issue**: Private keystore not loading or errors about invalid keys. **Solutions**: * Ensure `keystore.json` is properly formatted * Verify private keys are valid Ethereum private keys * Check file permissions on the keys directory ### Docker issues[​](#docker-issues "Direct link to Docker issues") **Issue**: Container won't start or crashes. **Solutions**: * Ensure Docker and Docker Compose are up to date * Check disk space availability * Verify the `.env` file is properly formatted * Review container logs: `docker compose logs aztec-sequencer` ### Common Issues[​](#common-issues "Direct link to Common Issues") See the [Operator FAQ](/operate/testnet/operators/operator-faq.md) for additional common issues and resolutions. ## Additional Resources[​](#additional-resources "Direct link to Additional Resources") After setting up and registering your sequencer: * **[Register Your Sequencer](/operate/testnet/operators/setup/registering_sequencer.md)** - Complete registration via staking dashboard * **[Monitor Sequencer Status](#monitoring-sequencer-status)** - Track performance and attestation rate * **[Operator FAQ](/operate/testnet/operators/operator-faq.md)** - Common issues and resolutions * **[Governance Participation](/operate/testnet/operators/sequencer-management/creating_and_voting_on_proposals.md)** - Participate in governance * **[Advanced Keystore Patterns](/operate/testnet/operators/keystore/advanced-patterns.md)** - Manage multiple sequencer identities **Community support:** * Join the [Aztec Discord](https://discord.gg/aztec) for operator support and network updates --- # Using and uploading snapshots ## Overview[​](#overview "Direct link to Overview") All nodes on the Aztec network must download and synchronize the blockchain state before they can operate. This guide covers different sync modes, including how to use snapshots for faster synchronization and how to create your own snapshots. Automatic Configuration When using `--network [NETWORK_NAME]`, snapshot URLs are automatically configured for you. Most users don't need to manually set snapshot sources. ## Understanding sync modes[​](#understanding-sync-modes "Direct link to Understanding sync modes") Nodes can synchronize state in two ways: 1. **L1 sync**: Queries the rollup and data availability layer for historical state directly from Layer 1 2. **Snapshot sync**: Downloads pre-built state snapshots from a storage location for faster synchronization Since Aztec uses blobs, syncing from L1 requires an archive node that stores complete blob history from Aztec's deployment. Snapshot sync is significantly faster, doesn't require archive nodes, and reduces load on L1 infrastructure, making it the recommended approach for most deployments. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before proceeding, you should: * Have the Aztec node software installed * Understand basic node operation * For uploading snapshots: Have access to cloud storage (Google Cloud Storage, Amazon S3, or Cloudflare R2) with appropriate permissions ## Using snapshots to sync your node[​](#using-snapshots-to-sync-your-node "Direct link to Using snapshots to sync your node") ### Configuring sync mode[​](#configuring-sync-mode "Direct link to Configuring sync mode") Control how your node synchronizes using the `SYNC_MODE` environment variable in your `.env` file: ``` aztec start --node --sync-mode [MODE] SYNC_MODE=[MODE] ``` Available sync modes: * **`snapshot`**: Downloads and uses a snapshot only if no local data exists (default behavior) * **`force-snapshot`**: Downloads and uses a snapshot even if local data exists, overwriting it * **`l1`**: Syncs directly from Layer 1 without using snapshots ### Setting the snapshot source[​](#setting-the-snapshot-source "Direct link to Setting the snapshot source") By default, nodes use Aztec's official snapshot storage. To specify a custom snapshot location, add the `SNAPSHOTS_URL` environment variable to your `.env` file: ``` SYNC_MODE=snapshot SNAPSHOTS_URL=[BASE_URL] ``` The node searches for the snapshot index at: ``` [BASE_URL]/aztec-[L1_CHAIN_ID]-[VERSION]-[ROLLUP_ADDRESS]/index.json ``` **Supported storage backends**: * **Google Cloud Storage** - `gs://bucket-name/path/` * **Amazon S3** - `s3://bucket-name/path/` * **Cloudflare R2** - `s3://bucket-name/path/?endpoint=https://[ACCOUNT_ID].r2.cloudflarestorage.com` * **HTTP/HTTPS** - `https://host/path` * **Local filesystem** - `file:///absolute/path` **Default snapshot locations by network**: * **Mainnet**: `https://aztec-labs-snapshots.com/mainnet/` * **Testnet**: `https://aztec-labs-snapshots.com/testnet/` * **Staging networks**: Configured via network metadata ### Using custom snapshot sources[​](#using-custom-snapshot-sources "Direct link to Using custom snapshot sources") You can configure your node to use custom snapshot sources for various use cases. Add the following to your `.env` file: **Google Cloud Storage:** ``` SYNC_MODE=force-snapshot SNAPSHOTS_URL=gs://my-snapshots/ ``` **Cloudflare R2:** ``` SYNC_MODE=snapshot SNAPSHOTS_URL=s3://my-bucket/snapshots/?endpoint=https://[ACCOUNT_ID].r2.cloudflarestorage.com ``` Replace `[ACCOUNT_ID]` with your Cloudflare account ID. **HTTP/HTTPS mirror:** ``` SYNC_MODE=snapshot SNAPSHOTS_URL=https://my-mirror.example.com/snapshots/ ``` Then add the environment variables to your `docker-compose.yml`: ``` environment: # ... other environment variables SYNC_MODE: ${SYNC_MODE} SNAPSHOTS_URL: ${SNAPSHOTS_URL} ``` ## Creating and uploading snapshots[​](#creating-and-uploading-snapshots "Direct link to Creating and uploading snapshots") You can create snapshots of your node's state for backup purposes or to share with other nodes. This is done by calling the `aztecAdmin_startSnapshotUpload` method on the node admin API. ### How snapshot upload works[​](#how-snapshot-upload-works "Direct link to How snapshot upload works") When triggered, the upload process: 1. Pauses node syncing temporarily 2. Creates a backup of the archiver and world-state databases 3. Uploads the backup to the specified storage location 4. Resumes normal operation ### Uploading a snapshot[​](#uploading-a-snapshot "Direct link to Uploading a snapshot") Use the node admin API to trigger a snapshot upload. You can upload to Google Cloud Storage, Amazon S3, or Cloudflare R2 by specifying the appropriate storage URI. **Example command**: **Upload to Google Cloud Storage:** ``` docker exec -it aztec-node curl -XPOST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{ "method": "aztecAdmin_startSnapshotUpload", "params": ["gs://your-bucket/snapshots/"], "id": 1, "jsonrpc": "2.0" }' ``` **Upload to Amazon S3:** ``` docker exec -it aztec-node curl -XPOST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{ "method": "aztecAdmin_startSnapshotUpload", "params": ["s3://your-bucket/snapshots/"], "id": 1, "jsonrpc": "2.0" }' ``` **Upload to Cloudflare R2:** ``` docker exec -it aztec-node curl -XPOST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{ "method": "aztecAdmin_startSnapshotUpload", "params": ["s3://your-bucket/snapshots/?endpoint=https://[ACCOUNT_ID].r2.cloudflarestorage.com"], "id": 1, "jsonrpc": "2.0" }' ``` Replace `aztec-node` with your container name and `[ACCOUNT_ID]` with your Cloudflare account ID. **Note**: Ensure your storage credentials are configured before uploading: * **Google Cloud Storage**: Set up [Application Default Credentials](https://cloud.google.com/docs/authentication/application-default-credentials) * **Amazon S3 / Cloudflare R2**: Set environment variables `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` ### Scheduling regular snapshots[​](#scheduling-regular-snapshots "Direct link to Scheduling regular snapshots") For continuous backup, schedule the upload command to run at regular intervals using cron or a similar scheduler. The frequency depends on how current you need your snapshots to be. Once uploaded, other nodes can download these snapshots by configuring their `--snapshots-url` to point to your storage location. ## Verification[​](#verification "Direct link to Verification") To verify your sync configuration is working: ### For snapshot downloads[​](#for-snapshot-downloads "Direct link to For snapshot downloads") 1. **Check startup logs**: Look for messages indicating snapshot download progress 2. **Monitor sync time**: Snapshot sync should be significantly faster than L1 sync 3. **Verify state completeness**: Confirm your node has the expected block height after sync 4. **Check data directories**: Ensure the archiver and world-state databases are populated ### For snapshot uploads[​](#for-snapshot-uploads "Direct link to For snapshot uploads") 1. **Check API response**: The upload command should return a success response 2. **Monitor logs**: Watch for upload progress messages in the node logs 3. **Verify storage**: Check your storage bucket to confirm the snapshot files exist 4. **Validate index file**: Ensure the `index.json` file is created at the expected path 5. **Test download**: Try downloading the snapshot with another node to confirm it works ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Snapshot download fails[​](#snapshot-download-fails "Direct link to Snapshot download fails") **Issue**: Node cannot download snapshot from the specified URL. **Solutions**: * Verify the `--snapshots-url` is correct and accessible * Check network connectivity to the storage location * Confirm the snapshot index file exists at the expected path * Review node logs for specific error messages * Try using Aztec's default snapshot URL to isolate custom URL issues ### Snapshot upload fails[​](#snapshot-upload-fails "Direct link to Snapshot upload fails") **Issue**: The `aztecAdmin_startSnapshotUpload` command returns an error. **Solutions**: * Verify storage credentials are properly configured (Google Cloud, AWS, or Cloudflare R2) * Check that the specified bucket exists and you have write permissions * Confirm sufficient disk space is available for creating the backup * Review node logs for detailed error messages * For S3/R2: Ensure environment variables `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` are set ### Storage space issues[​](#storage-space-issues "Direct link to Storage space issues") **Issue**: Running out of disk space during sync or snapshot creation. **Solutions**: * Ensure sufficient disk space (at least 2x the expected database size for snapshots) * Clean up old snapshots or data if running recurring uploads * Monitor disk usage and set up alerts * Consider using a larger volume or adding storage ## Best practices[​](#best-practices "Direct link to Best practices") * **Use snapshot sync for production**: Snapshot sync is significantly faster and more efficient than L1 sync * **Choose the right storage backend**: * Google Cloud Storage for simplicity and GCP integration * Amazon S3 for AWS infrastructure integration * Cloudflare R2 for cost-effective public distribution (free egress) * **Schedule regular snapshots**: Create snapshots at regular intervals if running critical infrastructure * **Test snapshot restoration**: Periodically verify that your snapshots download and restore correctly * **Monitor storage costs**: Implement retention policies to manage cloud storage costs * **Keep snapshots current**: Older snapshots require more time to sync to the current state * **Use `force-snapshot` sparingly**: Only use when you need to reset to a known state, as it overwrites local data ## Next Steps[​](#next-steps "Direct link to Next Steps") * Learn about [running bootnodes](/operate/testnet/operators/setup/bootnode_operation.md) for improved peer discovery * Set up [monitoring](/operate/testnet/operators/monitoring.md) to track your node's sync progress * Check the [CLI reference](/operate/testnet/operators/reference/cli-reference.md) for additional sync-related options * Join the [Aztec Discord](https://discord.gg/aztec) for sync optimization tips --- # v4.x (Upgrade from Ignition) ## Overview[​](#overview "Direct link to Overview") **Migration difficulty**: High ## Breaking changes[​](#breaking-changes "Direct link to Breaking changes") ### Node.js upgraded to v24[​](#nodejs-upgraded-to-v24 "Direct link to Node.js upgraded to v24") Node.js minimum version changed from v22 to v24.12.0. ### Bot fee padding configuration renamed[​](#bot-fee-padding-configuration-renamed "Direct link to Bot fee padding configuration renamed") The bot configuration for fee padding has been renamed from "base fee" to "min fee". **v3.x:** ``` --bot.baseFeePadding ($BOT_BASE_FEE_PADDING) ``` **v4.0.0:** ``` --bot.minFeePadding ($BOT_MIN_FEE_PADDING) ``` **Migration**: Update your configuration to use the new flag name and environment variable. ### L2Tips API restructured with checkpoint information[​](#l2tips-api-restructured-with-checkpoint-information "Direct link to L2Tips API restructured with checkpoint information") The `getL2Tips()` RPC endpoint now returns a restructured response with additional checkpoint tracking. **v3.x response:** ``` { "latest": { "number": 100, "hash": "0x..." }, "proven": { "number": 98, "hash": "0x..." }, "finalized": { "number": 95, "hash": "0x..." } } ``` **v4.0.0 response:** ``` { "proposed": { "number": 100, "hash": "0x..." }, "checkpointed": { "block": { "number": 99, "hash": "0x..." }, "checkpoint": { "number": 10, "hash": "0x..." } }, "proven": { "block": { "number": 98, "hash": "0x..." }, "checkpoint": { "number": 9, "hash": "0x..." } }, "finalized": { "block": { "number": 95, "hash": "0x..." }, "checkpoint": { "number": 8, "hash": "0x..." } } } ``` **Migration**: * Replace `tips.latest` with `tips.proposed` * For `checkpointed`, `proven`, and `finalized` tips, access block info via `.block` (e.g., `tips.proven.block.number`) ### Block gas limits reworked[​](#block-gas-limits-reworked "Direct link to Block gas limits reworked") The byte-based block size limit has been removed and replaced with field-based blob limits and automatic gas budget computation from L1 rollup limits. **Removed:** ``` --maxBlockSizeInBytes ($SEQ_MAX_BLOCK_SIZE_IN_BYTES) ``` **Changed to optional (now auto-computed from L1 if not set):** ``` --maxL2BlockGas ($SEQ_MAX_L2_BLOCK_GAS) --maxDABlockGas ($SEQ_MAX_DA_BLOCK_GAS) ``` **New (proposer):** ``` --perBlockAllocationMultiplier ($SEQ_PER_BLOCK_ALLOCATION_MULTIPLIER) --maxTxsPerCheckpoint ($SEQ_MAX_TX_PER_CHECKPOINT) ``` **New (validator):** ``` --validateMaxL2BlockGas ($VALIDATOR_MAX_L2_BLOCK_GAS) --validateMaxDABlockGas ($VALIDATOR_MAX_DA_BLOCK_GAS) --validateMaxTxsPerBlock ($VALIDATOR_MAX_TX_PER_BLOCK) --validateMaxTxsPerCheckpoint ($VALIDATOR_MAX_TX_PER_CHECKPOINT) ``` **Migration**: Remove `SEQ_MAX_BLOCK_SIZE_IN_BYTES` from your configuration. Per-block L2 and DA gas budgets are now derived automatically as `(checkpointLimit / maxBlocks) * multiplier`, where the multiplier defaults to 2. You can still override `SEQ_MAX_L2_BLOCK_GAS` and `SEQ_MAX_DA_BLOCK_GAS` explicitly, but they will be capped at the checkpoint-level limits. Validators can now set independent per-block and per-checkpoint limits via the `VALIDATOR_` env vars; when not set, only checkpoint-level protocol limits are enforced. ### Setup phase allow list requires function selectors[​](#setup-phase-allow-list-requires-function-selectors "Direct link to Setup phase allow list requires function selectors") The transaction setup phase allow list now enforces function selectors, restricting which specific functions can run during setup on whitelisted contracts. Previously, any public function on a whitelisted contract or class was permitted. The semantics of the environment variable `TX_PUBLIC_SETUP_ALLOWLIST` have changed: **v3.x:** ``` --txPublicSetupAllowList ($TX_PUBLIC_SETUP_ALLOWLIST) ``` The variable fully **replaced** the hardcoded defaults. Format allowed entries without selectors: `I:address`, `C:classId`. **v4.0.0:** ``` --txPublicSetupAllowListExtend ($TX_PUBLIC_SETUP_ALLOWLIST) ``` The variable now **extends** the hardcoded defaults (which are always present). Selectors are now mandatory. An optional flags segment can be appended for additional validation: ``` I:address:selector[:flags] C:classId:selector[:flags] ``` Where `flags` is a `+`-separated list of: * `os` — `onlySelf`: only allow calls where msg\_sender == contract address * `rn` — `rejectNullMsgSender`: reject calls with a null msg\_sender * `cl=N` — `calldataLength`: enforce exact calldata length of N fields Example: `C:0xabc:0x1234:os+cl=4` **Migration**: If you were using `TX_PUBLIC_SETUP_ALLOWLIST`, ensure all entries include function selectors. Note the variable now adds to defaults rather than replacing them. If you were not setting this variable, no action is needed — the hardcoded defaults now include the correct selectors automatically. ### Token removed from default setup allowlist[​](#token-removed-from-default-setup-allowlist "Direct link to Token removed from default setup allowlist") Token class-based entries (`_increase_public_balance` and `transfer_in_public`) have been removed from the default public setup allowlist. FPC-based fee payments using custom tokens no longer work out of the box. This change was made because Token class IDs change with aztec-nr releases, making the allowlist impossible to keep up to date with new library releases. In addition, `transfer_in_public` requires complex additional logic to be built into the node to prevent mass transaction invalidation attacks. **FPC-based fee payment with custom tokens won't work on mainnet alpha**. **Migration**: Node operators who need FPC support must manually add Token entries via `TX_PUBLIC_SETUP_ALLOWLIST`. Example: ``` TX_PUBLIC_SETUP_ALLOWLIST="C:::os+cl=3,C:::cl=5" ``` Replace `` with the deployed Token contract class ID and ``/`` with the respective function selectors. Keep in mind that this will only work on local network setups, since even if you as an operator add these entries, other nodes will not have them and will not pick up these transactions. ### Sequencer environment variable renames[​](#sequencer-environment-variable-renames "Direct link to Sequencer environment variable renames") Several sequencer environment variables have been renamed: | Old variable | New variable | | ---------------------------------------- | --------------------------------------------------------------------- | | `SEQ_TX_POLLING_INTERVAL_MS` | `SEQ_POLLING_INTERVAL_MS` | | `SEQ_MAX_L1_TX_INCLUSION_TIME_INTO_SLOT` | `SEQ_L1_PUBLISHING_TIME_ALLOWANCE_IN_SLOT` | | `SEQ_MAX_TX_PER_BLOCK` | `SEQ_MAX_TX_PER_CHECKPOINT` | | `SEQ_MAX_BLOCK_SIZE_IN_BYTES` | Removed (see [Block gas limits reworked](#block-gas-limits-reworked)) | **Migration**: Search your configuration for the old variable names and replace them. The node will not recognize the old names. ### Double signing slashing[​](#double-signing-slashing "Direct link to Double signing slashing") New slashable offenses have been introduced for duplicate proposals and duplicate attestations. Penalty amounts are currently set to 0, but the detection infrastructure is active. If you run redundant sequencer nodes, you **must** enable high-availability signing with PostgreSQL to prevent accidental double signing: ``` VALIDATOR_HA_SIGNING_ENABLED=true VALIDATOR_HA_DATABASE_URL=postgresql://:@:/ VALIDATOR_HA_NODE_ID= ``` Run the database migration before starting your nodes: ``` aztec migrate-ha-db up --database-url ``` **Migration**: If you run a single node, no action is required. If you run redundant nodes for high availability, configure HA signing immediately. See the [High Availability Sequencers](/operate/testnet/operators/setup/high_availability_sequencers.md) guide for details. ### Blob-only data publication[​](#blob-only-data-publication "Direct link to Blob-only data publication") Transaction data is now published entirely via EIP-4844 blobs. The calldata fallback has been removed. Your consensus client (e.g., Lighthouse, Prysm) must run as a **supernode** or **semi-supernode** to make blobs available for retrieval. Standard pruning configurations will not retain blobs long enough. You should also configure blob file stores for redundancy: ``` BLOB_FILE_STORE_URLS= BLOB_FILE_STORE_UPLOAD_URL= BLOB_ARCHIVE_API_URL= ``` **Migration**: Ensure your consensus client is configured as a supernode. If you previously relied on calldata for data availability, switch to blob-based retrieval. See the [Blob Storage](/operate/testnet/operators/setup/blob_storage.md) guide for configuration details. ### Withdrawal delay increase[​](#withdrawal-delay-increase "Direct link to Withdrawal delay increase") The governance execution delay has increased from 7 days to 30 days. This extends the time required for staker withdrawals from approximately 15 days to approximately 38 days. **Migration**: No configuration changes needed. Be aware that withdrawal processing will take longer after the upgrade. ### Prover architecture change[​](#prover-architecture-change "Direct link to Prover architecture change") The prover now runs as a node subsystem rather than a separate standalone process. Start it alongside your node using the `--prover-node` flag: ``` aztec start --node --prover-node ``` **Migration**: If you were running the prover as a separate process, update your deployment to run it as part of the node with `--prover-node`. ## Removed features[​](#removed-features "Direct link to Removed features") ## New features[​](#new-features "Direct link to New features") ### Initial ETH per fee asset configuration[​](#initial-eth-per-fee-asset-configuration "Direct link to Initial ETH per fee asset configuration") A new environment variable `AZTEC_INITIAL_ETH_PER_FEE_ASSET` has been added to configure the initial exchange rate between ETH and the fee asset (AZTEC) at contract deployment. This value uses 1e12 precision. **Default**: `10000000` (0.00001 ETH per AZTEC) **Configuration:** ``` --initialEthPerFeeAsset ($AZTEC_INITIAL_ETH_PER_FEE_ASSET) ``` This replaces the previous hardcoded default and allows network operators to set the starting price point for the fee asset. ### `reloadKeystore` admin RPC endpoint[​](#reloadkeystore-admin-rpc-endpoint "Direct link to reloadkeystore-admin-rpc-endpoint") Node operators can now update validator attester keys, coinbase, and fee recipient without restarting the node by calling the new `reloadKeystore` admin RPC endpoint. What is updated on reload: * Validator attester keys (add, remove, or replace) * Coinbase and fee recipient per validator * Publisher-to-validator mapping What is NOT updated (requires restart): * L1 publisher signers * Prover keys * HA signer connections New validators must use a publisher key already initialized at startup. Reload is rejected with a clear error if validation fails. ### Admin API key authentication[​](#admin-api-key-authentication "Direct link to Admin API key authentication") The admin JSON-RPC endpoint now supports auto-generated API key authentication. **Behavior:** * A cryptographically secure API key is auto-generated at first startup and displayed once via stdout * Only the SHA-256 hash is persisted to `/admin/api_key_hash` * The key is reused across restarts when `--data-directory` is set * Supports both `x-api-key` and `Authorization: Bearer ` headers * Health check endpoint (`GET /status`) is excluded from auth (for k8s probes) **Configuration:** ``` --admin-api-key-hash ($AZTEC_ADMIN_API_KEY_HASH) # Use a pre-generated SHA-256 key hash --disable-admin-api-key ($AZTEC_DISABLE_ADMIN_API_KEY) # Disable auth entirely --reset-admin-api-key ($AZTEC_RESET_ADMIN_API_KEY) # Force key regeneration ``` **Helm charts**: Admin API key auth is disabled by default (`disableAdminApiKey: true`). Set to `false` in production values to enable. **Migration**: No action required — auth is opt-out. To enable, ensure `--disable-admin-api-key` is not set and note the key printed at startup. ### Transaction pool error codes for RPC callers[​](#transaction-pool-error-codes-for-rpc-callers "Direct link to Transaction pool error codes for RPC callers") Transaction submission via RPC now returns structured rejection codes when a transaction is rejected by the mempool: * `LOW_PRIORITY_FEE` — tx priority fee is too low * `INSUFFICIENT_FEE_PAYER_BALANCE` — fee payer doesn't have enough balance * `NULLIFIER_CONFLICT` — conflicting nullifier already in pool **Impact**: Improved developer experience — callers can now programmatically handle specific rejection reasons. ### RPC transaction replacement price bump[​](#rpc-transaction-replacement-price-bump "Direct link to RPC transaction replacement price bump") Transactions submitted via RPC that clash on nullifiers with existing pool transactions must now pay at least X% more in priority fee to replace them. The same bump applies when the pool is full and the incoming tx needs to evict the lowest-priority tx. P2P gossip behavior is unchanged. **Configuration:** ``` P2P_RPC_PRICE_BUMP_PERCENTAGE=10 # default: 10 (percent) ``` Set to `0` to disable the percentage-based bump (still requires strictly higher fee). ### Validator-specific block limits[​](#validator-specific-block-limits "Direct link to Validator-specific block limits") Validators can now enforce per-block and per-checkpoint limits independently from the sequencer (proposer) limits. This allows operators to accept proposals that exceed their own proposer settings, or to reject proposals that are too large even if the proposer's limits allow them. **Configuration:** ``` VALIDATOR_MAX_L2_BLOCK_GAS= # Max L2 gas per block for validation VALIDATOR_MAX_DA_BLOCK_GAS= # Max DA gas per block for validation VALIDATOR_MAX_TX_PER_BLOCK= # Max txs per block for validation VALIDATOR_MAX_TX_PER_CHECKPOINT= # Max txs per checkpoint for validation ``` When not set, no per-block limit is enforced for that dimension — only checkpoint-level protocol limits apply. These do not fall back to the `SEQ_` values. ### Setup allow list extendable via network config[​](#setup-allow-list-extendable-via-network-config "Direct link to Setup allow list extendable via network config") The setup phase allow list can now be extended via the network configuration JSON (`txPublicSetupAllowListExtend` field). This allows network operators to distribute additional allowed setup functions to all nodes without requiring code changes. The local environment variable takes precedence over the network-json value. ## Changed defaults[​](#changed-defaults "Direct link to Changed defaults") ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ## Next steps[​](#next-steps "Direct link to Next steps") * [How to Run a Sequencer Node](/operate/testnet/operators/setup/sequencer_management.md) - Updated setup instructions * [Advanced Keystore Usage](/operate/testnet/operators/keystore/creating_keystores.md) - Keystore configuration * [Ethereum RPC Calls Reference](/operate/testnet/operators/reference/ethereum_rpc_reference.md) - Infrastructure requirements * [Aztec Discord](https://discord.gg/aztec) - Upgrade support --- # Operating Aztec Infrastructure This section covers everything you need to run and maintain Aztec network infrastructure. Whether you're running a full node for personal use or operating a professional sequencer, you'll find the guides you need here. ## Getting Started[​](#getting-started "Direct link to Getting Started") 1. Review the [Prerequisites](/operate/operators/prerequisites.md) to ensure you have the necessary hardware and software 2. [Run a Full Node](/operate/operators/setup/running_a_node.md) - the foundation for all other roles 3. Choose your path: [Sequencer](/operate/operators/setup/sequencer_management.md) or [Prover](/operate/operators/setup/running_a_prover.md) ## Roles[​](#roles "Direct link to Roles") ### Full Node Operator[​](#full-node-operator "Direct link to Full Node Operator") Run a node to interact with the network, submit transactions, and maintain a copy of the state. * [Running a Node](/operate/operators/setup/running_a_node.md) * [Syncing Best Practices](/operate/operators/setup/syncing_best_practices.md) ### Sequencer Operator[​](#sequencer-operator "Direct link to Sequencer Operator") Produce blocks, participate in consensus, and earn rewards. * [Sequencer Setup](/operate/operators/setup/sequencer_management.md) * [Registration](/operate/operators/setup/registering_sequencer.md) * [High Availability](/operate/operators/setup/high_availability_sequencers.md) * [Governance Participation](/operate/operators/sequencer-management/creating_and_voting_on_proposals.md) ### Prover Operator[​](#prover-operator "Direct link to Prover Operator") Generate cryptographic proofs for the network. * [Running a Prover](/operate/operators/setup/running_a_prover.md) ### Staking Provider[​](#staking-provider "Direct link to Staking Provider") Accept delegated stake and operate sequencers on behalf of token holders. * [Becoming a Staking Provider](/operate/operators/setup/become_a_staking_provider.md) ## Operations[​](#operations "Direct link to Operations") * [Monitoring](/operate/operators/monitoring.md) - Set up observability for your infrastructure * [Keystore Management](/operate/operators/keystore.md) - Secure key handling * [Sequencer Management](/operate/operators/sequencer-management.md) - Day-to-day operations ## Reference[​](#reference "Direct link to Reference") * [CLI Reference](/operate/operators/reference/cli-reference.md) * [Node API Reference](/operate/operators/reference/node_api_reference.md) * [Changelog](/operate/operators/reference/changelog.md) *** Conceptual Background For background on how the network works, see the [Participate section](/participate.md). --- # Advanced Keystore Usage ## Overview[​](#overview "Direct link to Overview") The keystore manages private keys and addresses for your Aztec sequencer or prover. This guide covers advanced keystore configurations including secure key storage methods, multi-account setups, and production deployment patterns. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before proceeding, you should: * Be familiar with running a sequencer or prover node * Understand the basic keystore structure from the [sequencer setup guide](/operate/operators/setup/sequencer_management.md) * Have access to appropriate key management infrastructure (if using remote signers) ## Understanding Keystore Roles[​](#understanding-keystore-roles "Direct link to Understanding Keystore Roles") The keystore manages different types of keys depending on your node type. Understanding these roles helps you configure the right keys for your needs. ### Sequencer Keys[​](#sequencer-keys "Direct link to Sequencer Keys") When running a sequencer, you configure these keys and addresses: * **Attester** (required): Your sequencer's identity. This key signs block proposals and attestations. The corresponding Ethereum address uniquely identifies your sequencer on the network. * **Publisher** (optional): Submits block proposals to L1. Defaults to using the attester key if not specified. Must be funded with at least 0.1 ETH. * **Coinbase** (optional): Ethereum address that receives L2 block rewards on L1. Defaults to the attester address if not set. * **Fee Recipient** (required): Aztec address that receives unburnt L2 transaction fees from blocks you produce. ### Prover Keys[​](#prover-keys "Direct link to Prover Keys") Prover nodes use a simpler configuration: * **Prover ID**: Ethereum address identifying your prover and receiving rewards. * **Publisher**: Submits proof transactions to L1. Must be funded with ETH for gas costs. ### Slasher Keys[​](#slasher-keys "Direct link to Slasher Keys") If you're running a slasher to monitor the network: * **Slasher**: Key used to create slash payloads on L1 when detecting sequencer misbehavior. ## What This Guide Covers[​](#what-this-guide-covers "Direct link to What This Guide Covers") This guide walks you through advanced keystore configurations in three parts: ### 1. Key Storage Methods[​](#1-key-storage-methods "Direct link to 1. Key Storage Methods") Learn about different ways to store and access private keys: * Inline private keys (for testing) * Remote signers with Web3Signer (recommended for production Ethereum keys) * JSON V3 encrypted keystores * BIP44 mnemonic derivation See [Key Storage Methods](/operate/operators/keystore/storage-methods.md) for detailed instructions. ### 2. Advanced Configuration Patterns[​](#2-advanced-configuration-patterns "Direct link to 2. Advanced Configuration Patterns") Explore complex deployment scenarios: * Using multiple publisher accounts for load distribution * Running multiple sequencers on a single node * Infrastructure provider configurations * High availability setups See [Advanced Configuration Patterns](/operate/operators/keystore/advanced-patterns.md) for examples. ### 3. Troubleshooting[​](#3-troubleshooting "Direct link to 3. Troubleshooting") Get help with common issues: * Keystore loading failures * Key format validation * Security best practices * Permission problems See [Troubleshooting](/operate/operators/keystore/troubleshooting.md) for solutions. ## Getting Started[​](#getting-started "Direct link to Getting Started") **First time creating a keystore?** Start with the [Creating Validator Keystores guide](/operate/operators/keystore/creating_keystores.md) to learn how to use the Aztec CLI to generate keystores for sequencers and provers. Once you have a basic keystore, explore the [Key Storage Methods](/operate/operators/keystore/storage-methods.md) guide to understand advanced options like remote signers and encrypted keystores. Then check out [Advanced Configuration Patterns](/operate/operators/keystore/advanced-patterns.md) for complex deployment scenarios. For production deployments, we strongly recommend using remote signers or encrypted keystores instead of inline private keys. --- # Sample configuration patterns ## Overview[​](#overview "Direct link to Overview") This guide covers advanced keystore configuration patterns for complex deployments, including multi-publisher setups, running multiple sequencers, and infrastructure provider scenarios. ## Multiple publishers[​](#multiple-publishers "Direct link to Multiple publishers") Multiple publisher accounts provide: * **Load distribution**: Spread L1 transaction costs across accounts * **Parallelization**: Submit multiple transactions simultaneously * **Resilience**: Continue operating if one publisher runs out of gas **Array of publishers:** ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "0xATTESTER_ETH_PRIVATE_KEY", "bls": "0xATTESTER_BLS_PRIVATE_KEY" }, "publisher": [ "0xPUBLISHER_1_PRIVATE_KEY", "0xPUBLISHER_2_PRIVATE_KEY", "0xPUBLISHER_3_PRIVATE_KEY" ], "feeRecipient": "0x1234567890123456789012345678901234567890123456789012345678901234" } ] } ``` **Mixed storage methods:** ``` { "schemaVersion": 1, "remoteSigner": "https://signer1.example.com:8080", "validators": [ { "attester": { "eth": "0xATTESTER_ETH_PRIVATE_KEY", "bls": "0xATTESTER_BLS_PRIVATE_KEY" }, "publisher": [ "0xLOCAL_PRIVATE_KEY", "0xREMOTE_SIGNER_ADDRESS_1", { "address": "0xREMOTE_SIGNER_ADDRESS_2", "remoteSignerUrl": "https://signer2.example.com:8080" }, { "mnemonic": "test test test test test test test test test test test junk", "addressCount": 2 } ], "feeRecipient": "0x1234567890123456789012345678901234567890123456789012345678901234" } ] } ``` This creates 5 publishers: 1. Local private key 2. Address in default remote signer (signer1.example.com) 3. Address in alternative remote signer (signer2.example.com) 4. Two mnemonic-derived addresses Publisher Funding Required All publisher accounts must be funded with ETH. Monitor balances to avoid missed proposals or proofs. ## Multiple sequencers[​](#multiple-sequencers "Direct link to Multiple sequencers") Run multiple sequencer identities in a single node. This is useful when you operate multiple sequencers but want to consolidate infrastructure. High Availability Across Nodes If you want to run the **same** sequencer across multiple nodes for redundancy and high availability, see the [High Availability Sequencers guide](/operate/operators/setup/high_availability_sequencers.md). That guide covers running one sequencer identity on multiple physical nodes. This section covers running **multiple different sequencer identities** on a single node. **When to use multiple sequencers per node:** * You have multiple sequencer identities (different attester addresses) * You want to consolidate infrastructure and reduce operational overhead * You're running sequencers for multiple entities or clients * You want to simplify management of several sequencers **Use two approaches:** **Option 1: Shared configuration** Multiple attesters sharing the same publisher, coinbase, and fee recipient: ``` { "schemaVersion": 1, "validators": [ { "attester": [ { "eth": "0xSEQUENCER_1_ETH_KEY", "bls": "0xSEQUENCER_1_BLS_KEY" }, { "eth": "0xSEQUENCER_2_ETH_KEY", "bls": "0xSEQUENCER_2_BLS_KEY" } ], "publisher": ["0xSHARED_PUBLISHER"], "coinbase": "0xSHARED_COINBASE", "feeRecipient": "0xSHARED_FEE_RECIPIENT" } ] } ``` **Option 2: Separate configurations** Each sequencer with its own publisher, coinbase, and fee recipient: ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "0xSEQUENCER_1_ETH_KEY", "bls": "0xSEQUENCER_1_BLS_KEY" }, "publisher": ["0xPUBLISHER_1"], "coinbase": "0xCOINBASE_1", "feeRecipient": "0xFEE_RECIPIENT_1" }, { "attester": { "eth": "0xSEQUENCER_2_ETH_KEY", "bls": "0xSEQUENCER_2_BLS_KEY" }, "publisher": ["0xPUBLISHER_2"], "coinbase": "0xCOINBASE_2", "feeRecipient": "0xFEE_RECIPIENT_2" } ] } ``` For high availability configurations where you run the same sequencer across multiple nodes, see the [High Availability Sequencers guide](/operate/operators/setup/high_availability_sequencers.md). ## Infrastructure provider scenarios[​](#infrastructure-provider-scenarios "Direct link to Infrastructure provider scenarios") ### Scenario 1: Multiple sequencers with isolation[​](#scenario-1-multiple-sequencers-with-isolation "Direct link to Scenario 1: Multiple sequencers with isolation") For sequencers requiring complete separation, use separate keystore files: **keystore-sequencer-a.json:** ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "0xSEQUENCER_A_ETH_KEY", "bls": "0xSEQUENCER_A_BLS_KEY" }, "feeRecipient": "0xFEE_RECIPIENT_A" } ] } ``` **keystore-sequencer-b.json:** ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "0xSEQUENCER_B_ETH_KEY", "bls": "0xSEQUENCER_B_BLS_KEY" }, "feeRecipient": "0xFEE_RECIPIENT_B" } ] } ``` Point `KEY_STORE_DIRECTORY` to the directory containing both files. ### Scenario 2: Shared publisher infrastructure[​](#scenario-2-shared-publisher-infrastructure "Direct link to Scenario 2: Shared publisher infrastructure") Multiple sequencers sharing a publisher pool for simplified gas management: ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "0xSEQUENCER_1_ETH_KEY", "bls": "0xSEQUENCER_1_BLS_KEY" }, "publisher": ["0xPUBLISHER_1", "0xPUBLISHER_2"], "feeRecipient": "0xFEE_RECIPIENT_1" }, { "attester": { "eth": "0xSEQUENCER_2_ETH_KEY", "bls": "0xSEQUENCER_2_BLS_KEY" }, "publisher": ["0xPUBLISHER_1", "0xPUBLISHER_2"], "feeRecipient": "0xFEE_RECIPIENT_2" } ] } ``` Both sequencers share publishers while maintaining separate identities and fee recipients. ## Prover configurations[​](#prover-configurations "Direct link to Prover configurations") **Simple prover** (uses same key for identity and publishing): ``` { "schemaVersion": 1, "prover": "0xPROVER_PRIVATE_KEY" } ``` **Prover with dedicated publishers:** ``` { "schemaVersion": 1, "prover": { "id": "0xPROVER_IDENTITY_ADDRESS", "publisher": [ "0xPUBLISHER_1_PRIVATE_KEY", "0xPUBLISHER_2_PRIVATE_KEY" ] } } ``` The `id` receives prover rewards while `publisher` accounts submit proofs. ## Complete Configuration Examples[​](#complete-configuration-examples "Direct link to Complete Configuration Examples") ### High Availability Sequencer Setup[​](#high-availability-sequencer-setup "Direct link to High Availability Sequencer Setup") Creating keystores for running the same sequencer across multiple nodes: ``` # Step 1: Generate a base keystore with your attester and multiple publishers aztec validator-keys new \ --fee-recipient [YOUR_FEE_RECIPIENT] \ --mnemonic "your shared mnemonic..." \ --address-index 0 \ --publisher-count 3 \ --data-dir ~/keys-temp # This generates ONE keystore with: # - Attester keys (ETH and BLS) at derivation index 0 # - Three publisher keys at indices 1, 2, and 3 ``` After generation, you'll have a keystore with one attester and multiple publishers. Create separate keystores for each node by copying the base keystore and editing each to use only one publisher: **Node 1** - Uses publisher at index 1 **Node 2** - Uses publisher at index 2 **Node 3** - Uses publisher at index 3 Each node's keystore will have the **same attester keys** (both ETH and BLS) but a **different publisher key**. For detailed step-by-step HA setup instructions, see the [High Availability Sequencers guide](/operate/operators/setup/high_availability_sequencers.md). ## Next steps[​](#next-steps "Direct link to Next steps") * See [Troubleshooting](/operate/operators/keystore/troubleshooting.md) for common issues * Return to [Key Storage Methods](/operate/operators/keystore/storage-methods.md) for more options * Start with basics at [Creating Keystores](/operate/operators/keystore/creating_keystores.md) --- # Creating Sequencer Keystores ## Overview[​](#overview "Direct link to Overview") Keystores are configuration files that store the cryptographic keys and addresses your sequencer node needs to operate on the Aztec network. This guide shows you how to create keystores using the Aztec CLI's `validator-keys` commands. A keystore contains: * **Attester keys**: Your sequencer's identity (Ethereum and BLS keys for signing proposals and attestations) * **Publisher keys**: Keys used to submit blocks to L1 (requires ETH for gas) * **Fee recipient**: Aztec address for L2 transaction fees (currently not used) * **Coinbase address**: Ethereum address receiving L1 block rewards (optional, defaults to attester address) ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before creating keystores, ensure you have: * Basic understanding of Ethereum addresses and private keys * Access to an Ethereum L1 RPC endpoint * Foundry toolkit installed (for creating publisher addresses) ## Installing the Aztec CLI[​](#installing-the-aztec-cli "Direct link to Installing the Aztec CLI") First, install the Aztec CLI using the official installer: ``` VERSION=4.3.1 bash -i <(curl -sL https://install.aztec.network/4.3.1) ``` Verify your CLI installation: ``` aztec --version ``` ## Recommended Setup: Multiple Validators with Shared Publisher[​](#recommended-setup-multiple-validators-with-shared-publisher "Direct link to Recommended Setup: Multiple Validators with Shared Publisher") This approach creates multiple sequencer identities (validators) that share a single publisher address for submitting transactions to L1. This is the recommended configuration for production deployments. ### Step 1: Create Publisher Address and Set RPC Endpoint[​](#step-1-create-publisher-address-and-set-rpc-endpoint "Direct link to Step 1: Create Publisher Address and Set RPC Endpoint") First, set your Ethereum mainnet L1 RPC endpoint: ``` export ETH_RPC=https://ethereum-rpc.publicnode.com ``` Or use your preferred Ethereum RPC provider (Infura, Alchemy, etc.). Then generate a separate address for publishing transactions to L1 using the Foundry toolkit: ``` cast wallet new-mnemonic --words 24 ``` **Example output:** ``` Successfully generated a new mnemonic. Phrase: word1 word2 word3 word4 word5 word6 word7 word8 word9 word10 word11 word12 word13 word14 word15 word16 word17 word18 word19 word20 word21 word22 word23 word24 Accounts: - Account 0: Address: 0xE434A95e816991E66bF7052955FD699aEf8a286b Private key: 0x7988a4a7...79f058a0 ``` Critical: Save Your Publisher Mnemonic The 24-word mnemonic is the **only way** to recover your publisher private key. Store it securely offline (not on the server running the node). **Save from the output:** * ✅ The 24-word mnemonic (for recovery) * ✅ The private key (you'll use this in the next step) * ✅ The address (you'll fund this with ETH) ### Step 2: Generate Your Keystores with Publisher[​](#step-2-generate-your-keystores-with-publisher "Direct link to Step 2: Generate Your Keystores with Publisher") Generate 5 validators with the publisher private key from Step 1: ``` aztec validator-keys new \ --fee-recipient 0x0000000000000000000000000000000000000000000000000000000000000000 \ --staker-output \ --gse-address 0xa92ecFD0E70c9cd5E5cd76c50Af0F7Da93567a4f \ --l1-rpc-urls $ETH_RPC \ --count 5 \ --publishers 0x7988a4a779f058a0 ``` Replace `0x7988a4a779f058a0` with your actual publisher private key from Step 1. **What this command does:** * Generates a new mnemonic for your validator keys (save this securely!) * Creates 5 sequencer identities (validators) with Ethereum and BLS keys * Configures all validators to use the same publisher address for L1 submissions * Generates public keystore data for the staking dashboard * Saves files to `~/.aztec/keystore/` **Example output:** ``` No mnemonic provided, generating new one... Using new mnemonic: absent city nephew garment million badge front text memory grape two lizard Wrote validator keystore to /Users/your-name/.aztec/keystore/key1.json Wrote staker output for 5 validator(s) to /Users/your-name/.aztec/keystore/key1_staker_output.json acc1: attester: eth: 0x8E76a8B8D66E0A56E241F2768fD2ad4eba07E565 bls: 0x29eaf46e4699e33a1abe7300258567c624a7304a2134e31aa2609437f281d81d publisher: - 0x7988a4a779f058a0 acc2: attester: eth: 0x2037b472537a4246B1A7325f327028EF450ba0Ef bls: 0x8d7eb7d9436ac6cb9b8f1c211673ea228c7f438882e6438b2caefca753df28e8 publisher: - 0x7988a4a779f058a0 acc3: attester: eth: 0x0c14593f7465DeDbb86d68982374BB05F4C60386 bls: 0xad1cccf512d2f180238af795831344445f7ac47e2d623f3dac854e93e5b1e76d publisher: - 0x7988a4a779f058a0 acc4: attester: eth: 0x4D213928988f0123f6b3B4A377F856812F08E831 bls: 0xa90f5889dddd4cd6bc5a28db5e0db60d3cbf5147eb6e82b313024b2d0634110e publisher: - 0x7988a4a779f058a0 acc5: attester: eth: 0x29f147Da38d5F66bB84e791969b365c796829c92 bls: 0x0d683001c2ce866e322f0c7509f087a909508787d125336931aa9168d2a1f95b publisher: - 0x7988a4a779f058a0 Note: The publisher value shown is the private key (truncated in this example). All validators share the same publisher private key. Staker outputs: [ { "attester": "0x8E76a8B8D66E0A56E241F2768fD2ad4eba07E565", "publicKeyG1": { "x": "0x...", "y": "0x..." }, "publicKeyG2": { "x0": "0x...", "x1": "0x...", "y0": "0x...", "y1": "0x..." }, "proofOfPossession": { "x": "0x...", "y": "0x..." } }, ... (4 more validators) ] ``` Critical: Save Both Mnemonics You now have **two separate mnemonics** to secure: 1. **Validator mnemonic** (shown above, 12 words) - Regenerates your attester keys 2. **Publisher mnemonic** (from Step 1, 24 words) - Regenerates your publisher key Both must be stored securely offline. Losing either mnemonic means losing access to those keys. **Files created:** * `~/.aztec/keystore/key1.json` - Private keystore with all 5 validators and publisher configured * `~/.aztec/keystore/key1_staker_output.json` - Public keystore for staking dashboard ### Step 3: Fund the Publisher Address[​](#step-3-fund-the-publisher-address "Direct link to Step 3: Fund the Publisher Address") Your publisher address needs ETH to pay for L1 gas when submitting proposals. **Funding requirement:** At least **0.3 ETH** for 5 validators (rule of thumb: 0.1 ETH per validator) Transfer ETH to the publisher address from Step 1. You can check the balance with: ``` cast balance 0xE434A95e816991E66bF7052955FD699aEf8a286b --rpc-url $ETH_RPC ``` Replace the address with your actual publisher address. Monitor Publisher Balance Set up monitoring to alert when the publisher balance falls below 0.5 ETH to prevent failed block publications. ### Step 4: Upload Keystore to Your Node[​](#step-4-upload-keystore-to-your-node "Direct link to Step 4: Upload Keystore to Your Node") Now you're ready to spin up your sequencer node! **Upload the private keystore to your server:** The `key1.json` file contains your private keys and must be uploaded to your sequencer node. **For standard server deployments:** ``` # Upload to your server's keystore directory scp ~/.aztec/keystore/key1.json user@your-server:/path/to/aztec-sequencer/keys/keystore.json ``` **For dAppNode deployments:** * Upload `key1.json` to the dAppNode keystore folder * Rename it to `keystore.json` Keep the Public Keystore Local Keep `key1_staker_output.json` on your local machine - you'll need it for registration on the staking dashboard. **Do not upload this to your server.** ### Step 5: Start Your Node[​](#step-5-start-your-node "Direct link to Step 5: Start Your Node") Start your sequencer node following the [Sequencer Setup guide](/operate/operators/setup/sequencer_management.md). When your node starts successfully, you'll see output similar to: ``` Started validator with addresses: 0x8E76a8B8D66E0A56E241F2768fD2ad4eba07E565, 0x2037b472537a4246B1A7325f327028EF450ba0Ef, 0x0c14593f7465DeDbb86d68982374BB05F4C60386, 0x4D213928988f0123f6b3B4A377F856812F08E831, 0x29f147Da38d5F66bB84e791969b365c796829c92 ``` These are your validator attester addresses - they match the addresses shown when you generated your keys. ### Step 6: Register Your Validators[​](#step-6-register-your-validators "Direct link to Step 6: Register Your Validators") Use the public keystore (`key1_staker_output.json`) to register your validators on the staking dashboard. See [Registering a Sequencer](/operate/operators/setup/registering_sequencer.md) for details. *** ### Quick Setup Summary[​](#quick-setup-summary "Direct link to Quick Setup Summary") By following the recommended setup, you've accomplished: ✅ **Generated a dedicated publisher address** with its own 24-word mnemonic ✅ **Created 5 validator identities** with a separate 12-word mnemonic ✅ **Configured all validators** to use the shared publisher for L1 transactions ✅ **Funded the publisher** with at least 0.3 ETH for gas costs ✅ **Uploaded the private keystore** (`key1.json`) to your sequencer node ✅ **Started your node** and verified validator addresses in the output ✅ **Ready to register** using the public keystore (`key1_staker_output.json`) **Two mnemonics to keep secure:** 1. **Publisher mnemonic** (24 words) - Recovers publisher private key 2. **Validator mnemonic** (12 words) - Recovers all 5 validator attester keys ## Alternative: Single Validator Setup[​](#alternative-single-validator-setup "Direct link to Alternative: Single Validator Setup") For testing or simpler setups, you can create a single validator that uses its attester key as the publisher. ### Basic Single Validator[​](#basic-single-validator "Direct link to Basic Single Validator") ``` aztec validator-keys new \ --fee-recipient 0x0000000000000000000000000000000000000000000000000000000000000000 \ --staker-output \ --gse-address 0xa92ecFD0E70c9cd5E5cd76c50Af0F7Da93567a4f \ --l1-rpc-urls $ETH_RPC ``` This creates: * One validator with attester keys * No separate publisher (attester key used for publishing) * Private keystore at `~/.aztec/keystore/keyN.json` * Public keystore at `~/.aztec/keystore/keyN_staker_output.json` When to Use Single Validator Use single validator setup for: * Testing and development * Simple deployments with one sequencer identity * When you don't need to isolate attester and publisher keys ## Understanding Keystore Structure[​](#understanding-keystore-structure "Direct link to Understanding Keystore Structure") ### Private Keystore Format[​](#private-keystore-format "Direct link to Private Keystore Format") The private keystore (`key1.json`) contains sensitive private keys: ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "0x...", // Ethereum private key - sequencer identifier "bls": "0x..." // BLS private key - signs proposals and attestations }, "publisher": ["0x..."], // Publisher private key(s) for L1 submissions "feeRecipient": "0x0000000000000000000000000000000000000000000000000000000000000000", "coinbase": "0x..." // Optional: custom address for L1 rewards } ] } ``` **Field descriptions:** * **attester.eth**: Derives the address that serves as your sequencer's unique identifier * **attester.bls**: Signs proposals and attestations, used for staking operations * **publisher**: Array of private keys for submitting signed messages to L1 (pays gas) * **feeRecipient**: L2 fee recipient (not currently used, set to all zeros) * **coinbase**: L1 block reward recipient (optional, defaults to attester address) ### Public Keystore Format[​](#public-keystore-format "Direct link to Public Keystore Format") The public keystore (`key1_staker_output.json`) contains only public information safe to share: ``` [ { "attester": "0xYOUR_ATTESTER_ADDRESS", "publicKeyG1": { "x": "0x...", "y": "0x..." }, "publicKeyG2": { "x0": "0x...", "x1": "0x...", "y0": "0x...", "y1": "0x..." }, "proofOfPossession": { "x": "0x...", "y": "0x..." } } ] ``` This file is used for registration on the staking dashboard and contains no private keys. ## Advanced Options[​](#advanced-options "Direct link to Advanced Options") ### Providing Your Own Mnemonic[​](#providing-your-own-mnemonic "Direct link to Providing Your Own Mnemonic") For deterministic key generation or to recreate keys from an existing mnemonic: ``` aztec validator-keys new \ --fee-recipient 0x0000000000000000000000000000000000000000000000000000000000000000 \ --staker-output \ --gse-address 0xa92ecFD0E70c9cd5E5cd76c50Af0F7Da93567a4f \ --l1-rpc-urls $ETH_RPC \ --mnemonic "your existing twelve word mnemonic phrase here" \ --count 5 \ --publishers 0x7988a4a779f058a0 ``` This regenerates the same validators if you've used this mnemonic before, or creates new ones at the next derivation indices. ### Custom Output Location[​](#custom-output-location "Direct link to Custom Output Location") Specify custom directory and filename: ``` aztec validator-keys new \ --fee-recipient 0x0000000000000000000000000000000000000000000000000000000000000000 \ --staker-output \ --gse-address 0xa92ecFD0E70c9cd5E5cd76c50Af0F7Da93567a4f \ --l1-rpc-urls $ETH_RPC \ --count 5 \ --publishers 0x7988a4a779f058a0 \ --data-dir ~/my-sequencer/keys \ --file sequencer1.json ``` This creates keystores at: * `~/my-sequencer/keys/sequencer1.json` (private keystore) * `~/my-sequencer/keys/sequencer1_staker_output.json` (public keystore) **Default behavior** (if you don't specify `--data-dir` or `--file`): * **Directory**: `~/.aztec/keystore/` * **Filename**: `key1.json`, `key2.json`, etc. (auto-increments) ## Verifying Your Keystore[​](#verifying-your-keystore "Direct link to Verifying Your Keystore") Verify the keystore is valid JSON: ``` cat ~/.aztec/keystore/key1.json | jq . ``` Check validator count: ``` jq '.validators | length' ~/.aztec/keystore/key1.json ``` Verify BLS keys are present: ``` jq '.validators[0].attester.bls' ~/.aztec/keystore/key1.json ``` Extract attester addresses: ``` # Get attester ETH private key (to derive address) jq -r '.validators[0].attester.eth' ~/.aztec/keystore/key1.json ``` ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") If you encounter issues during keystore creation or management, see the **[Troubleshooting and Best Practices Guide](/operate/operators/keystore/troubleshooting.md)** for: * Keystore creation issues (RPC, permissions, invalid JSON, legacy BLS keys) * Runtime and operational issues (node startup, remote signers, nonce conflicts) * Comprehensive security best practices * Complete CLI reference ## Next Steps[​](#next-steps "Direct link to Next Steps") Now that you've created your keystores: ### For Sequencer Operators[​](#for-sequencer-operators "Direct link to For Sequencer Operators") 1. **Fund publisher addresses** - At least 0.1 ETH per validator 2. **Set up your node** - See [Sequencer Management](/operate/operators/setup/sequencer_management.md) 3. **Register validators** - Use the public keystore with the staking dashboard 4. **Monitor operations** - Track attestations and publisher balance ### Advanced Configurations[​](#advanced-configurations "Direct link to Advanced Configurations") * **[Advanced Keystore Patterns](/operate/operators/keystore/advanced-patterns.md)** - Multiple validators, high availability, remote signers * **[Key Storage Methods](/operate/operators/keystore/storage-methods.md)** - Encrypted keystores, HSMs, key management systems * **[Troubleshooting and Best Practices](/operate/operators/keystore/troubleshooting.md)** - Common issues, security best practices, and CLI reference ### Getting Help[​](#getting-help "Direct link to Getting Help") * Review the [Operator FAQ](/operate/operators/operator-faq.md) for common questions * Join the [Aztec Discord](https://discord.gg/aztec) for operator support * Check the [CLI reference](/operate/operators/reference/cli-reference.md) for all available commands --- # Key storage methods ## Overview[​](#overview "Direct link to Overview") The keystore supports four methods for storing and accessing private keys. These methods can be mixed within a single configuration. ## Private keys (inline)[​](#private-keys-inline "Direct link to Private keys (inline)") The simplest method is to include private keys directly in the keystore. The `validator-keys new` command generates keystores in this format by default: ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "0xef17bcb86452f3f6a73678c01bee757e9d46d1cd0050f043c10cfc953b17bad2", "bls": "0x20f2f5989b66462b39229900948c7846403768fec5b76d1c2937d64e04aac4b9" }, "feeRecipient": "0x0000000000000000000000000000000000000000000000000000000000000000" } ] } ``` Note that the attester field now contains both Ethereum (`eth`) and BLS (`bls`) private keys. Both are required for sequencer operation. Not for Production Use Inline private keys are convenient for testing but should be avoided in production. Use remote signers or encrypted keystores for production deployments. ## Remote signers (Web3Signer)[​](#remote-signers-web3signer "Direct link to Remote signers (Web3Signer)") Remote signers keep private keys in a separate, secure signing service. This is the recommended approach for production environments for Ethereum keys. The keystore supports [Web3Signer](https://docs.web3signer.consensys.io/) endpoints for Ethereum keys. The keystore automatically detects whether a value is a private key or an address based on string length: * **66 characters** (`0x` + 64 hex characters): Interpreted as a private key (stored inline) * **42 characters** (`0x` + 40 hex characters): Interpreted as an address and uses the nearest `remoteSignerUrl` BLS Keys Do Not Support Remote Signers BLS keys must always be stored as private keys directly in the keystore. The keystore does not check `remoteSignerUrl` for BLS keys. Web3Signer's BLS support is designed for Ethereum consensus layer operations and is not compatible with Aztec's BLS key requirements. Remote signers can be configured at three levels: **Global level** (applies to all ETH keys): ``` { "schemaVersion": 1, "remoteSigner": "https://signer.example.com:8080", "validators": [ { "attester": { "eth": "0x1234567890123456789012345678901234567890", "bls": "0x20f2f5989b66462b39229900948c7846403768fec5b76d1c2937d64e04aac4b9" }, "feeRecipient": "0x1234567890123456789012345678901234567890123456789012345678901234" } ] } ``` In this example, the Ethereum attester address (42 characters) is managed by the remote signer, while the BLS key (66 characters) is a private key stored directly in the keystore. **Validator (sequencer) block level** (applies to all ETH keys in a sequencer configuration): ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "0x1234567890123456789012345678901234567890", "bls": "0x20f2f5989b66462b39229900948c7846403768fec5b76d1c2937d64e04aac4b9" }, "feeRecipient": "0x1234567890123456789012345678901234567890123456789012345678901234", "remoteSigner": "https://signer.example.com:8080" } ] } ``` **Account level** (applies to a specific ETH key): ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": { "address": "0x1234567890123456789012345678901234567890", "remoteSignerUrl": "https://signer.example.com:8080" }, "bls": "0x20f2f5989b66462b39229900948c7846403768fec5b76d1c2937d64e04aac4b9" }, "feeRecipient": "0x1234567890123456789012345678901234567890123456789012345678901234" } ] } ``` ### Client certificate authentication[​](#client-certificate-authentication "Direct link to Client certificate authentication") For remote signers requiring client certificates: ``` { "schemaVersion": 1, "remoteSigner": { "remoteSignerUrl": "https://signer.example.com:8080", "certPath": "/path/to/client-cert.p12", "certPass": "certificate-password" }, "validators": [...] } ``` ## JSON V3 encrypted keystores[​](#json-v3-encrypted-keystores "Direct link to JSON V3 encrypted keystores") JSON V3 keystores provide standard Ethereum-compatible encrypted key storage. **Single file:** ``` { "schemaVersion": 1, "validators": [ { "attester": { "path": "/path/to/keystore.json", "password": "keystore-password" }, "feeRecipient": "0x1234567890123456789012345678901234567890123456789012345678901234" } ] } ``` **Directory of keystores:** ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "0x1234567890123456789012345678901234567890123456789012345678901234", "bls": "0x2345678901234567890123456789012345678901234567890123456789012345" }, "publisher": { "path": "/path/to/keystores/", "password": "shared-password" }, "feeRecipient": "0x1234567890123456789012345678901234567890123456789012345678901234" } ] } ``` All `.json` files in the directory will be loaded using the provided password. ## Mnemonics (BIP44 derivation)[​](#mnemonics-bip44-derivation "Direct link to Mnemonics (BIP44 derivation)") Mnemonics derive multiple keys from a single seed phrase using [BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) paths. **Single key** (default path `m/44'/60'/0'/0/0`): ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "0x1234567890123456789012345678901234567890123456789012345678901234", "bls": "0x2345678901234567890123456789012345678901234567890123456789012345" }, "publisher": { "mnemonic": "test test test test test test test test test test test junk" }, "feeRecipient": "0x1234567890123456789012345678901234567890123456789012345678901234" } ] } ``` **Multiple sequential keys:** ``` { "publisher": { "mnemonic": "test test test test test test test test test test test junk", "addressCount": 4 } } ``` Generates 4 keys at paths `m/44'/60'/0'/0/0` through `m/44'/60'/0'/0/3`. **Custom derivation paths:** ``` { "publisher": { "mnemonic": "test test test test test test test test test test test junk", "accountIndex": 5, "addressIndex": 3, "addressCount": 2 } } ``` Not for Production Use Mnemonics are convenient for testing but should be avoided in production. Use remote signers or encrypted keystores for production deployments. ## Next steps[​](#next-steps "Direct link to Next steps") * Learn about [Advanced Configuration Patterns](/operate/operators/keystore/advanced-patterns.md) * See [Troubleshooting](/operate/operators/keystore/troubleshooting.md) if you encounter issues --- # Troubleshooting and Best Practices ## Keystore Creation Issues[​](#keystore-creation-issues "Direct link to Keystore Creation Issues") ### Missing fee-recipient Flag[​](#missing-fee-recipient-flag "Direct link to Missing fee-recipient Flag") **Error message:** ``` error: required option '--fee-recipient
' not specified ``` **Solution:** The CLI requires the `--fee-recipient` flag. Use the zero address: ``` --fee-recipient 0x0000000000000000000000000000000000000000000000000000000000000000 ``` ### RPC Connection Issues[​](#rpc-connection-issues "Direct link to RPC Connection Issues") **Error message:** ``` Error: HTTP request failed ``` **Solutions:** * Verify `$ETH_RPC` is set correctly: `echo $ETH_RPC` * Test RPC connectivity: `cast block-number --rpc-url $ETH_RPC` * Try a different RPC provider if the current one is rate-limited ### Permission Denied[​](#permission-denied "Direct link to Permission Denied") **Error message:** ``` Error: permission denied ``` **Solution:** Ensure you have write permissions for the target directory: ``` mkdir -p ~/.aztec/keystore chmod 755 ~/.aztec/keystore ``` ### Invalid Keystore JSON[​](#invalid-keystore-json "Direct link to Invalid Keystore JSON") **Error:** Node fails to load keystore or CLI rejects keystore file **Solutions:** * Validate JSON syntax: `jq . ~/.aztec/keystore/key1.json` * Ensure all required fields are present * Check that publisher is an array: `["0x..."]` not `"0x..."` * Verify private keys are 64-character hex strings (with or without `0x` prefix) ### Legacy BLS Key Derivation (2.1.4 Users)[​](#legacy-bls-key-derivation-214-users "Direct link to Legacy BLS Key Derivation (2.1.4 Users)") **Issue:** Need to regenerate keys that were created with CLI version 2.1.4 or earlier Version 2.1.5 changed the BLS key derivation path, which means keys generated from the same mnemonic produce different results. This affects users who: * Generated keys with version 2.1.4 using `--count` parameter * Used `--account-index` explicitly in version 2.1.4 * Need to regenerate keys from mnemonic that are already registered in the GSE contract **The derivation path change:** * **2.1.4**: `m/12381/3600/0/0/0`, `m/12381/3600/1/0/0`, `m/12381/3600/2/0/0` * **2.1.5+**: `m/12381/3600/0/0/0`, `m/12381/3600/0/0/1`, `m/12381/3600/0/0/2` **Solution: Use the --legacy flag** If you generated keys with version 2.1.4 and need to regenerate them from your mnemonic, use the `--legacy` flag: ``` aztec validator-keys new \ --fee-recipient 0x0000000000000000000000000000000000000000000000000000000000000000 \ --staker-output \ --gse-address 0xa92ecFD0E70c9cd5E5cd76c50Af0F7Da93567a4f \ --l1-rpc-urls $ETH_RPC \ --mnemonic "your twelve word mnemonic phrase here" \ --count 5 \ --legacy ``` The `--legacy` flag uses the 2.1.4 derivation path to reproduce your original keys. When NOT to Use --legacy Do NOT use the `--legacy` flag if: * You're generating keys for the first time * You generated keys with version 2.1.5 or later * You didn't use `--count` or `--account-index` in version 2.1.4 Using `--legacy` unnecessarily will create keys with the old derivation path that won't match your newer registrations. Why This Matters BLS keys are registered in the GSE (Governance Staking Escrow) contract and cannot be easily updated. If you regenerate keys with a different derivation path, they won't match what's registered on chain, and your sequencer won't be able to attest properly. ## Runtime and Operational Issues[​](#runtime-and-operational-issues "Direct link to Runtime and Operational Issues") ### "No validators found in keystore"[​](#no-validators-found-in-keystore "Direct link to \"No validators found in keystore\"") **Symptoms**: Node fails to start with no sequencer configurations loaded **Causes**: * Keystore file not found at specified path * Invalid JSON syntax * Missing required fields * File permissions prevent reading **Solutions**: 1. Verify keystore path: ``` ls -la $KEY_STORE_DIRECTORY ``` 2. Validate JSON syntax: ``` cat keystore.json | jq . ``` 3. Check required fields: ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "REQUIRED - Ethereum private key", "bls": "REQUIRED - BLS private key" }, "feeRecipient": "REQUIRED - Aztec address" } ] } ``` 4. Fix file permissions: ``` chmod 600 keystore.json chown aztec:aztec keystore.json ``` ### "Failed to connect to remote signer"[​](#failed-to-connect-to-remote-signer "Direct link to \"Failed to connect to remote signer\"") **Symptoms**: Node cannot reach Web3Signer endpoint **Causes**: * Incorrect URL or port * Network connectivity issues * Certificate validation failures * Remote signer not running **Solutions**: 1. Test connectivity: ``` curl https://signer.example.com:8080/upcheck ``` 2. Verify certificate: ``` openssl s_client -connect signer.example.com:8080 -showcerts ``` 3. Check remote signer logs for authentication errors 4. For self-signed certificates, ensure proper certificate configuration in keystore ### "Insufficient funds for gas"[​](#insufficient-funds-for-gas "Direct link to \"Insufficient funds for gas\"") **Symptoms**: Transactions fail with insufficient balance errors **Causes**: * Publisher accounts not funded * ETH balance depleted **Solutions**: 1. Check publisher balances: ``` cast balance 0xPUBLISHER_ADDRESS --rpc-url $ETHEREUM_HOST ``` 2. Fund publisher accounts with ETH 3. Set up automated balance monitoring and alerts ### "Nonce too low" or "Replacement transaction underpriced"[​](#nonce-too-low-or-replacement-transaction-underpriced "Direct link to \"Nonce too low\" or \"Replacement transaction underpriced\"") **Symptoms**: Transaction submission failures related to nonces **Causes**: * Multiple nodes using same publisher key * Publisher key reused across keystores * Transaction pool issues **Solutions**: 1. **Never share publisher keys across multiple running nodes** 2. If you must use the same key, ensure only one node is active at a time 3. Clear pending transactions if safe to do so ### "Keystore file not loaded"[​](#keystore-file-not-loaded "Direct link to \"Keystore file not loaded\"") **Symptoms**: Only some keystores load from a directory **Causes**: * Invalid JSON in some files * Incorrect file extensions * Schema version mismatch **Solutions**: 1. Check all files in directory: ``` for file in /path/to/keystores/*.json; do echo "Checking $file" jq . "$file" || echo "Invalid JSON in $file" done ``` 2. Ensure all files use `.json` extension 3. Verify `schemaVersion: 1` in all keystores ### "Cannot decrypt JSON V3 keystore"[​](#cannot-decrypt-json-v3-keystore "Direct link to \"Cannot decrypt JSON V3 keystore\"") **Symptoms**: Failed to load encrypted keystore files **Causes**: * Incorrect password * Corrupted keystore file * Unsupported encryption algorithm **Solutions**: 1. Verify password is correct 2. Test decryption manually: ``` # Using ethereumjs-wallet or similar tool ``` 3. Re-generate keystore if corrupted 4. Ensure keystore was generated using standard tools (geth, web3.py, ethers.js) ## Security Best Practices[​](#security-best-practices "Direct link to Security Best Practices") ### Protecting Private Keys[​](#protecting-private-keys "Direct link to Protecting Private Keys") 1. **Never commit keystores to version control** * Add `keystore.json` to `.gitignore` * Store keystores outside your project directory 2. **Backup your mnemonic securely** * Write it down offline * Store in a secure location (not on the server) * Consider using a hardware wallet or password manager 3. **Limit keystore access** ``` chmod 600 ~/.aztec/keystore/key1.json ``` 4. **Separate publisher from attester** * Use dedicated publisher keys * Keep attester keys offline when possible * Use remote signers for production ### Key Storage[​](#key-storage "Direct link to Key Storage") **DO:** * Use remote signers (Web3Signer) for production deployments * Store keystores in encrypted volumes * Use JSON V3 keystores with strong passwords * Restrict file permissions to 600 (owner read/write only) * Keep backups of keystores in secure, encrypted locations **DON'T:** * Commit keystores or private keys to version control * Store unencrypted private keys on disk * Share private keys between nodes * Use the same keys across test and production environments * Log private keys or keystore passwords ### Publisher Key Management[​](#publisher-key-management "Direct link to Publisher Key Management") **DO:** * Use separate publisher keys for each sequencer if possible * Monitor publisher account balances with alerting * Rotate publisher keys periodically * Maintain multiple funded publishers for resilience * Keep publisher keys separate from attester keys **DON'T:** * Reuse publisher keys across multiple nodes * Run out of gas in publisher accounts * Use sequencer attester keys as publishers if avoidable * Share publisher keys between sequencers ### Remote Signer Security[​](#remote-signer-security "Direct link to Remote Signer Security") **DO:** * Use TLS/HTTPS for all remote signer connections * Implement client certificate authentication * Run remote signers on isolated networks * Monitor remote signer access logs * Use firewall rules to restrict access **DON'T:** * Use unencrypted HTTP connections * Expose remote signers to the public internet * Share remote signer endpoints between untrusted parties * Disable certificate verification ### Operational Security[​](#operational-security "Direct link to Operational Security") **DO:** * Implement principle of least privilege for file access * Use hardware security modules (HSMs) for high-value sequencers * Maintain audit logs of key access and usage * Test keystore configurations in non-production environments first * Document your key management procedures **DON'T:** * Run nodes as root user * Store passwords in shell history or scripts * Share attester keys between sequencers * Neglect monitoring and alerting ### Production Deployments[​](#production-deployments "Direct link to Production Deployments") For production, consider: * **Hardware Security Modules (HSMs)** for key storage * **Remote signers** to keep keys off the node * **Encrypted keystores** with password protection * **Key management systems** (HashiCorp Vault, AWS Secrets Manager) See [Key Storage Methods](/operate/operators/keystore/storage-methods.md) for advanced security patterns. ## CLI Reference[​](#cli-reference "Direct link to CLI Reference") ### validator-keys new[​](#validator-keys-new "Direct link to validator-keys new") Create a new keystore with validators: ``` aztec validator-keys new [options] ``` **Common Options:** | Option | Description | Default | | ---------------------------- | -------------------------------------------------------------- | ------------------- | | `--fee-recipient
` | L2 fee recipient (required) | None | | `--mnemonic ` | 12 or 24 word mnemonic | Auto-generated | | `--count ` | Number of validators to create | `1` | | `--publisher-count ` | Publishers per validator | `0` | | `--staker-output` | Generate public keystore for staking | `false` | | `--gse-address
` | GSE contract address (required with --staker-output) | None | | `--l1-rpc-urls ` | L1 RPC endpoints (required with --staker-output) | None | | `--legacy` | Use 2.1.4 BLS derivation path (only for regenerating old keys) | `false` | | `--data-dir ` | Output directory | `~/.aztec/keystore` | | `--file ` | Keystore filename | `key1.json` | For the complete list: ``` aztec validator-keys new --help ``` ### validator-keys add[​](#validator-keys-add "Direct link to validator-keys add") Add validators to an existing keystore: ``` aztec validator-keys add [options] ``` ### validator-keys staker[​](#validator-keys-staker "Direct link to validator-keys staker") Generate staker output from an existing keystore: ``` aztec validator-keys staker \ --from \ --gse-address
\ --l1-rpc-urls \ --output ``` ## Getting Help[​](#getting-help "Direct link to Getting Help") If you encounter issues not covered here: * Review the [Operator FAQ](/operate/operators/operator-faq.md) for common questions * Join the [Aztec Discord](https://discord.gg/aztec) for operator support * Check the [CLI reference](/operate/operators/reference/cli-reference.md) for all available commands * Review node logs for specific error messages (redact private keys!) * When asking for help, provide: * Error messages (with private keys redacted) * Keystore structure (anonymized) * Node version and deployment environment ## Related Documentation[​](#related-documentation "Direct link to Related Documentation") * **[Creating Keystores](/operate/operators/keystore/creating_keystores.md)** - Main guide for generating keystores * **[Advanced Keystore Patterns](/operate/operators/keystore/advanced-patterns.md)** - Multiple validators, high availability, remote signers * **[Key Storage Methods](/operate/operators/keystore/storage-methods.md)** - Encrypted keystores, HSMs, key management systems * **[Sequencer Management](/operate/operators/setup/sequencer_management.md)** - Operational guidance for running sequencers --- # Monitoring and Observability ## Overview[​](#overview "Direct link to Overview") This guide shows you how to set up monitoring and observability for your Aztec node using OpenTelemetry, Prometheus, and Grafana. Monitoring helps you maintain healthy node operations, diagnose issues quickly, and track performance over time. Docker Compose Setup This monitoring setup is designed to work with Docker Compose deployments of Aztec nodes. ## Architecture[​](#architecture "Direct link to Architecture") The monitoring stack uses three components working together: * **OpenTelemetry Collector**: Receives metrics from your Aztec node via OTLP protocol * **Prometheus**: Stores and queries time-series metrics data * **Grafana**: Visualizes metrics with dashboards and alerts Your Aztec node exports metrics to the OpenTelemetry Collector, which processes and exposes them in a format Prometheus can scrape. Prometheus stores the metrics as time-series data, and Grafana queries Prometheus to create visualizations and alerts. ## Getting Started[​](#getting-started "Direct link to Getting Started") Follow these guides in order to set up your complete monitoring stack: 1. [OpenTelemetry Collector Setup](/operate/operators/monitoring/otel-setup.md) - Configure OTEL to receive metrics from your node 2. [Prometheus Setup](/operate/operators/monitoring/prometheus-setup.md) - Set up Prometheus to store and query metrics 3. [Grafana Setup](/operate/operators/monitoring/grafana-setup.md) - Configure Grafana for visualization and alerting 4. [Key Metrics Reference](/operate/operators/monitoring/metrics-reference.md) - Understand the metrics your node exposes and create custom dashboards 5. [Complete Example and Troubleshooting](/operate/operators/monitoring/troubleshooting.md) - Full Docker Compose configuration and troubleshooting help ## Available Metrics Overview[​](#available-metrics-overview "Direct link to Available Metrics Overview") Your Aztec node exposes metrics through OpenTelemetry to help you monitor performance and health. The metrics available depend on your node type (full node, sequencer, or prover) and version. ### Metric Categories[​](#metric-categories "Direct link to Metric Categories") Your node exposes metrics in these categories: * **Node Metrics**: Block height, sync status, peer count, and transaction processing * **Sequencer Metrics**: Attestation activity, block proposals, and committee participation (sequencer nodes only) * **Prover Metrics**: Job queue, proof generation, and agent utilization (prover nodes only) * **System Metrics**: CPU, memory, disk I/O, and network bandwidth For detailed information about each metric, PromQL queries, and dashboard creation, see the [Key Metrics Reference](/operate/operators/monitoring/metrics-reference.md). ## Next Steps[​](#next-steps "Direct link to Next Steps") Once your monitoring stack is running: * Review the [Key Metrics Reference](/operate/operators/monitoring/metrics-reference.md) to understand available metrics and PromQL queries * Set up alerting rules in Prometheus for critical conditions * Create custom dashboards tailored to your operational needs * Configure notification channels (Slack, PagerDuty, email) in Grafana * Join the [Aztec Discord](https://discord.gg/aztec) to share dashboards with the community For troubleshooting common monitoring issues, see the [Troubleshooting](/operate/operators/monitoring/troubleshooting.md) guide. --- # Grafana Setup ## Overview[​](#overview "Direct link to Overview") Grafana provides visualization and alerting for your metrics, allowing you to create custom dashboards and receive notifications when issues arise. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Completed [Prometheus Setup](/operate/operators/monitoring/prometheus-setup.md) * Prometheus running and accessible at `http://prometheus:9090` ## Setup Steps[​](#setup-steps "Direct link to Setup Steps") ### Step 1: Add Grafana to Docker Compose[​](#step-1-add-grafana-to-docker-compose "Direct link to Step 1: Add Grafana to Docker Compose") Add Grafana to your `docker-compose.yml`: ``` services: # ... existing services (otel-collector, prometheus, etc.) ... grafana: image: grafana/grafana:latest container_name: aztec-grafana ports: - 3000:3000 volumes: - grafana-data:/var/lib/grafana environment: - GF_SECURITY_ADMIN_PASSWORD=admin - GF_USERS_ALLOW_SIGN_UP=false networks: - aztec restart: always volumes: # ... existing volumes ... grafana-data: networks: aztec: name: aztec ``` Admin Password Security Change the default admin password (`GF_SECURITY_ADMIN_PASSWORD`) to a secure value for production deployments. ### Step 2: Start Grafana[​](#step-2-start-grafana "Direct link to Step 2: Start Grafana") ``` docker compose up -d grafana ``` ### Step 3: Access Grafana[​](#step-3-access-grafana "Direct link to Step 3: Access Grafana") 1. Navigate to `http://localhost:3000` 2. Login with username `admin` and the password you set (default: `admin`) 3. You'll be prompted to change the password on first login ### Step 4: Add Prometheus Data Source[​](#step-4-add-prometheus-data-source "Direct link to Step 4: Add Prometheus Data Source") 1. In the left sidebar, click **Connections** → **Data sources** 2. Click **Add data source** 3. Search for and select **Prometheus** 4. Configure: * **Name**: Aztec Prometheus * **URL**: `http://prometheus:9090` 5. Click **Save & Test** You should see a green success message confirming Grafana can connect to Prometheus. ## Creating Dashboards[​](#creating-dashboards "Direct link to Creating Dashboards") ### Option 1: Create a Basic Dashboard[​](#option-1-create-a-basic-dashboard "Direct link to Option 1: Create a Basic Dashboard") 1. In the left sidebar, click **Dashboards** 2. Click **New** → **New Dashboard** 3. Click **Add visualization** 4. Select your **Aztec Prometheus** data source 5. In the query editor, enter a metric (explore available metrics using the autocomplete) 6. Customize the visualization type and settings 7. Click **Apply** 8. Click **Save dashboard** icon (top right) 9. Give your dashboard a name and click **Save** ### Option 2: Import a Pre-built Dashboard[​](#option-2-import-a-pre-built-dashboard "Direct link to Option 2: Import a Pre-built Dashboard") If the Aztec community has created shared dashboards: 1. Click **+** → **Import** 2. Enter dashboard ID or upload JSON file 3. Select **Aztec Prometheus** as the data source 4. Click **Import** ### Recommended Dashboard Panels[​](#recommended-dashboard-panels "Direct link to Recommended Dashboard Panels") Example panels you can create (adjust metric names based on what's actually available): 1. **Block Height Over Time**: Line graph tracking block sync progress 2. **Sync Rate**: Line graph showing blocks synced over time window (use `increase()` function) 3. **Peer Count**: Gauge showing P2P connections 4. **Memory Usage**: Line graph of `process_resident_memory_bytes` 5. **CPU Usage**: Line graph of `rate(process_cpu_seconds_total[5m])` ## Setting Up Alerts[​](#setting-up-alerts "Direct link to Setting Up Alerts") Configure alerts to notify you of issues: ### Step 1: Create an Alert Rule[​](#step-1-create-an-alert-rule "Direct link to Step 1: Create an Alert Rule") 1. In the left sidebar, click **Alerting** (bell icon) 2. Click **Alert rules** → **New alert rule** 3. Configure your alert: * **Query**: Select your Prometheus data source and metric (e.g., `aztec_archiver_block_height`) * **Condition**: Define the threshold (e.g., `increase(aztec_archiver_block_height[15m]) == 0` to alert if no blocks in 15 minutes) * **Evaluation interval**: How often to check (e.g., 1m) 4. Click **Save** ### Step 2: Configure Contact Points[​](#step-2-configure-contact-points "Direct link to Step 2: Configure Contact Points") 1. Under **Alerting**, click **Contact points** 2. Click **Add contact point** 3. Choose your notification method: * **Email**: Configure SMTP settings * **Slack**: Add webhook URL * **PagerDuty**: Add integration key * **Webhook**: Custom HTTP endpoint 4. Click **Save** ### Step 3: Create Notification Policies[​](#step-3-create-notification-policies "Direct link to Step 3: Create Notification Policies") 1. Under **Alerting**, click **Notification policies** 2. Click **New notification policy** 3. Define routing rules to send alerts to specific contact points 4. Click **Save** ## Example Alert Rules[​](#example-alert-rules "Direct link to Example Alert Rules") ### Node Sync Alert[​](#node-sync-alert "Direct link to Node Sync Alert") Alert if the node stops syncing blocks: * **Query**: `increase(aztec_archiver_block_height[15m])` * **Condition**: `== 0` * **Description**: Node has not synced any blocks in the last 15 minutes ### High Memory Usage Alert[​](#high-memory-usage-alert "Direct link to High Memory Usage Alert") Alert if memory usage exceeds threshold: * **Query**: `process_resident_memory_bytes` * **Condition**: `> 8000000000` (8GB) * **Description**: Node memory usage exceeds 8GB ### Peer Connection Alert[​](#peer-connection-alert "Direct link to Peer Connection Alert") Alert if peer count drops too low: * **Query**: `aztec_peer_manager_peer_count_peers` * **Condition**: `< 5` * **Description**: Node has fewer than 5 peer connections ## Next Steps[​](#next-steps "Direct link to Next Steps") * Explore the [Monitoring Overview](/operate/operators/monitoring.md) for troubleshooting and metrics reference * Join the [Aztec Discord](https://discord.gg/aztec) to share dashboards with the community * Configure additional notification channels for your alerts --- # Key Metrics Reference ## Overview[​](#overview "Direct link to Overview") Your Aztec node exposes metrics through OpenTelemetry to help you monitor performance, health, and operational status. This guide covers key metrics across node types and how to use them effectively. Discovering Metrics Once your monitoring stack is running, you can discover available metrics in the Prometheus UI at `http://localhost:9090/graph`. Start typing in the query box to see autocomplete suggestions for metrics exposed by your node. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Complete monitoring stack setup following the [Monitoring Overview](/operate/operators/monitoring.md) * Ensure Prometheus is running and scraping metrics from your OTEL collector * Verify access to Prometheus UI at `http://localhost:9090` Metric Names May Vary The exact metric names and labels in this guide depend on your node type, version, and configuration. Always verify the actual metrics exposed by your node using the Prometheus UI metrics explorer at `http://localhost:9090/graph`. Common prefixes: `aztec_archiver_*`, `aztec_sequencer_*`, `aztec_prover_*`, `process_*`. ## Querying with PromQL[​](#querying-with-promql "Direct link to Querying with PromQL") Use Prometheus Query Language (PromQL) to query and analyze your metrics. Understanding these basics will help you read the alert rules throughout this guide. ### Basic Queries[​](#basic-queries "Direct link to Basic Queries") ``` # Instant vector - current value aztec_archiver_block_height # Range vector - values over time aztec_archiver_block_height[5m] ``` ### Rate and Increase[​](#rate-and-increase "Direct link to Rate and Increase") ``` # Rate of change per second (for counters) rate(process_cpu_seconds_total[5m]) # Blocks synced over time window (for gauges) increase(aztec_archiver_block_height[1h]) # Derivative - per-second change rate of gauges deriv(process_resident_memory_bytes[30m]) ``` ### Arithmetic Operations[​](#arithmetic-operations "Direct link to Arithmetic Operations") Calculate derived metrics using basic math operators: ``` # Calculate percentage (block proposal failure rate) (increase(aztec_sequencer_slot_count[15m]) - increase(aztec_sequencer_slot_filled_count[15m])) / increase(aztec_sequencer_slot_count[15m]) # Convert to percentage scale rate(process_cpu_seconds_total[5m]) * 100 ``` ### Comparison Operators[​](#comparison-operators "Direct link to Comparison Operators") Filter and alert based on thresholds: ``` # Greater than rate(process_cpu_seconds_total[5m]) > 2.8 # Less than aztec_peer_manager_peer_count_peers < 5 # Equal to increase(aztec_archiver_block_height[15m]) == 0 # Not equal to aztec_sequencer_current_state != 1 ``` ### Time Windows[​](#time-windows "Direct link to Time Windows") Choose time windows based on metric behavior and alert sensitivity: * **Short windows** (`[5m]`, `[10m]`) - Detect immediate issues, sensitive to spikes * **Medium windows** (`[15m]`, `[30m]`) - Balance between responsiveness and stability, recommended for most alerts * **Long windows** (`[1h]`, `[2h]`) - Trend analysis, capacity planning, smooth out temporary fluctuations Example: `increase(aztec_archiver_block_height[15m])` checks if blocks were processed in the last 15 minutes - long enough to avoid false alarms from brief delays, short enough to catch real problems quickly. ## Core Node Metrics[​](#core-node-metrics "Direct link to Core Node Metrics") Your node exposes these foundational metrics for monitoring blockchain synchronization and network health. Configure immediate alerting for these metrics in all deployments. ### L2 Block Height Progress[​](#l2-block-height-progress "Direct link to L2 Block Height Progress") Track whether your node is actively processing new L2 blocks: * **Metric**: `aztec_archiver_block_height` * **Description**: Current L2 block number the node has synced to **Alert rule**: ``` - alert: L2BlockHeightNotIncreasing expr: increase(aztec_archiver_block_height{aztec_status=""}[15m]) == 0 for: 5m labels: severity: critical annotations: summary: "Aztec node not processing L2 blocks" description: "No L2 blocks processed in the last 15 minutes. Node may be stuck or out of sync." ``` ### Peer Connectivity[​](#peer-connectivity "Direct link to Peer Connectivity") Track the number of active P2P peers connected to your node: * **Metric**: `aztec_peer_manager_peer_count_peers` * **Description**: Number of outbound peers currently connected to the node **Alert rule**: ``` - alert: LowPeerCount expr: aztec_peer_manager_peer_count_peers < 5 for: 10m labels: severity: warning annotations: summary: "Low peer count detected" description: "Node has only {{ $value }} peers connected. Risk of network isolation." ``` ### L1 Block Height Progress[​](#l1-block-height-progress "Direct link to L1 Block Height Progress") Monitor whether your node is seeing new L1 blocks: * **Metric**: `aztec_l1_block_height` * **Description**: Latest L1 (Ethereum) block number seen by the node **Alert rule**: ``` - alert: L1BlockHeightNotIncreasing expr: increase(aztec_l1_block_height[15m]) == 0 for: 10m labels: severity: warning annotations: summary: "Node not seeing new L1 blocks" description: "No L1 block updates in 15 minutes. Check L1 RPC connection." ``` ## Sequencer Metrics[​](#sequencer-metrics "Direct link to Sequencer Metrics") If you're running a sequencer node, monitor these metrics for consensus participation, block production, and L1 publishing. Configure alerting for critical operations. ### L1 Publisher ETH Balance[​](#l1-publisher-eth-balance "Direct link to L1 Publisher ETH Balance") Monitor the ETH balance used for publishing to L1 to prevent transaction failures: * **Metric**: `aztec_l1_publisher_balance_eth` * **Description**: Current ETH balance of the L1 publisher account **Alert rule**: ``` - alert: LowL1PublisherBalance expr: aztec_l1_publisher_balance_eth < 0.5 for: 5m labels: severity: critical annotations: summary: "L1 publisher ETH balance critically low" description: "Publisher balance is {{ $value }} ETH. Refill immediately to avoid transaction failures." ``` ### Sequencer State[​](#sequencer-state "Direct link to Sequencer State") Monitor the operational state of the sequencer module: * **Metric**: `aztec_sequencer_current_state` * **Description**: Current state of the sequencer module (1 = OK/running, 0 = stopped/error) **Alert rule**: ``` - alert: SequencerNotHealthy expr: aztec_sequencer_current_state != 1 for: 2m labels: severity: critical annotations: summary: "Sequencer module not in healthy state" description: "Sequencer state is {{ $value }} (expected 1). Check sequencer logs immediately." ``` ### Block Proposal Failures[​](#block-proposal-failures "Direct link to Block Proposal Failures") Track failed block proposals by comparing slots to filled slots: * **Metrics**: `aztec_sequencer_slot_count` and `aztec_sequencer_slot_filled_count` * **Description**: Tracks slots assigned to your sequencer versus slots successfully filled. Alert triggers when the failure rate exceeds 5% over 15 minutes. **Alert rule**: ``` - alert: HighBlockProposalFailureRate expr: | (increase(aztec_sequencer_slot_count[15m]) - increase(aztec_sequencer_slot_filled_count[15m])) / increase(aztec_sequencer_slot_count[15m]) > 0.05 for: 5m labels: severity: warning annotations: summary: "High block proposal failure rate" description: "{{ $value | humanizePercentage }} of block proposals are failing in the last 15 minutes." ``` ### Blob Publishing Failures[​](#blob-publishing-failures "Direct link to Blob Publishing Failures") Track failures when publishing blobs to L1: * **Metric**: `aztec_l1_publisher_blob_tx_failure` * **Description**: Number of failed blob transaction submissions to L1 **Alert rule**: ``` - alert: BlobPublishingFailures expr: increase(aztec_l1_publisher_blob_tx_failure[15m]) > 0 for: 5m labels: severity: warning annotations: summary: "Blob publishing failures detected" description: "{{ $value }} blob transaction failures in the last 15 minutes. Check L1 gas prices and publisher balance." ``` ### Attestation Activity[​](#attestation-activity "Direct link to Attestation Activity") Track your sequencer's participation in the consensus protocol: * **Metrics**: Attestations submitted, attestation success rate, attestation timing * **Use cases**: * Verify your sequencer is actively participating * Monitor attestation success rate * Detect missed attestation opportunities ### Block Proposals[​](#block-proposals "Direct link to Block Proposals") Monitor block proposal activity and success: * **Metrics**: Blocks proposed, proposal success rate, proposal timing * **Use cases**: * Track block production performance * Identify proposal failures and causes * Monitor proposal timing relative to slot schedule ### Committee Participation[​](#committee-participation "Direct link to Committee Participation") Track your sequencer's involvement in consensus committees: * **Metrics**: Committee assignments, participation rate, duty execution * **Use cases**: * Verify your sequencer is assigned to committees * Monitor duty execution completion rate * Track committee participation over time ### Performance Metrics[​](#performance-metrics "Direct link to Performance Metrics") Measure block production efficiency: * **Metrics**: Block production time, validation latency, processing throughput * **Use cases**: * Optimize block production pipeline * Identify performance bottlenecks * Compare performance against network averages ## Prover Metrics[​](#prover-metrics "Direct link to Prover Metrics") If you're running a prover node, track these metrics for proof generation workload and resource utilization. ### Job Queue[​](#job-queue "Direct link to Job Queue") Monitor pending proof generation work: * **Metrics**: Queue depth, queue wait time, job age * **Use cases**: * Detect proof generation backlogs * Capacity planning for prover resources * Monitor job distribution across agents ### Proof Generation[​](#proof-generation "Direct link to Proof Generation") Track proof completion metrics: * **Metrics**: Proofs completed, completion time, success rate, failure reasons * **Use cases**: * Monitor proof generation throughput * Identify failing proof types * Track generation time trends ### Agent Utilization[​](#agent-utilization "Direct link to Agent Utilization") Monitor resource usage per proof agent: * **Metrics**: CPU usage per agent, memory allocation, GPU utilization (if applicable) * **Use cases**: * Optimize agent allocation * Detect resource constraints * Load balancing across agents ### Throughput[​](#throughput "Direct link to Throughput") Measure proof generation capacity: * **Metrics**: Jobs completed per time period, proofs per second, utilization rate * **Use cases**: * Capacity planning * Performance optimization * SLA monitoring ## System Metrics[​](#system-metrics "Direct link to System Metrics") Your node exposes standard infrastructure metrics through OpenTelemetry and the runtime environment. ### CPU Usage[​](#cpu-usage "Direct link to CPU Usage") Monitor process and system CPU utilization: * **Metric**: `process_cpu_seconds_total` * **Description**: Cumulative CPU time consumed by the process in seconds **Alert rules**: ``` # Note: Adjust thresholds based on your system's CPU core count. # Example below assumes a 4-core system (70% = 2.8 cores, 85% = 3.4 cores) - alert: HighCPUUsage expr: rate(process_cpu_seconds_total[5m]) > 2.8 for: 10m labels: severity: warning annotations: summary: "High CPU usage detected" description: "Node using {{ $value }} CPU cores (above 2.8 threshold). Consider scaling resources." ``` ### Memory Usage[​](#memory-usage "Direct link to Memory Usage") Track RAM consumption: * **Metric**: `process_resident_memory_bytes` * **Description**: Resident memory size in bytes **Alert rules**: ``` - alert: HighMemoryUsage expr: process_resident_memory_bytes > 8000000000 for: 5m labels: severity: warning annotations: summary: "High memory usage detected" description: "Memory usage is {{ $value | humanize1024 }}B. Consider increasing available RAM or investigating memory leaks." ``` **Additional monitoring**: * Track memory growth rate to detect leaks * Monitor garbage collection metrics for runtime efficiency ### Disk I/O[​](#disk-io "Direct link to Disk I/O") Monitor storage operations: * **Metrics**: Disk read/write rates, I/O latency, disk utilization * **Use cases**: * Identify I/O bottlenecks * Plan storage upgrades * Detect disk performance degradation ### Network Bandwidth[​](#network-bandwidth "Direct link to Network Bandwidth") Track network throughput: * **Metrics**: Bytes sent/received, packet rates, connection counts * **Use cases**: * Monitor P2P bandwidth usage * Capacity planning for network resources * Detect unusual traffic patterns ## Creating Dashboards in Grafana[​](#creating-dashboards-in-grafana "Direct link to Creating Dashboards in Grafana") Organize your Grafana dashboards by operational focus to make monitoring efficient and actionable. For specific panel configurations and queries, see the [Grafana Setup](/operate/operators/monitoring/grafana-setup.md) guide. ### Dashboard Organization Strategy[​](#dashboard-organization-strategy "Direct link to Dashboard Organization Strategy") **Overview Dashboard** - At-a-glance health check * L2 and L1 block height progression * Peer connectivity status * Critical alerts summary * Resource utilization (CPU, memory) * Use stat panels and gauges for current values * Include time-series graphs for trends **Performance Dashboard** - Deep-dive into operational metrics * Block processing rates and latencies * Transaction throughput * Network bandwidth utilization * Query response times * Use percentile graphs (p50, p95, p99) for latency metrics * Compare current performance against historical baselines **Resource Dashboard** - Infrastructure monitoring * CPU usage per core * Memory allocation and garbage collection * Disk I/O rates and latency * Network packet rates * Set threshold warning lines at 70-80% utilization * Include growth trend projections **Role-Specific Dashboards** - Specialized metrics by node type * **Sequencer Dashboard**: Block proposals, attestations, committee participation, L1 publisher balance * **Prover Dashboard**: Job queue depth, proof generation rates, agent utilization, success rates * Focus on metrics unique to the role's responsibilities * Include SLA tracking and performance benchmarks ## Best Practices[​](#best-practices "Direct link to Best Practices") ### Metric Collection[​](#metric-collection "Direct link to Metric Collection") 1. **Appropriate Scrape Intervals**: Balance data granularity against storage costs * Standard: 15s for most metrics * High-frequency: 5s for critical real-time metrics * Low-frequency: 60s for slow-changing metrics 2. **Retention Policy**: Configure based on operational needs * Short-term: 7-15 days for detailed troubleshooting * Long-term: 30-90 days for trend analysis * Archive: Consider downsampling for longer retention 3. **Label Cardinality**: Avoid high-cardinality labels that explode metric storage * Good: `instance`, `node_type`, `region` * Avoid: `user_id`, `transaction_hash`, `timestamp` ### Monitoring Strategy[​](#monitoring-strategy "Direct link to Monitoring Strategy") 1. **Layered Monitoring**: Monitor at multiple levels * Infrastructure: CPU, memory, disk, network * Application: Block height, peers, throughput * Business: Transaction success rate, user activity 2. **Proactive Alerts**: Set alerts before problems become critical * Use warning and critical thresholds * Alert on trends, not just absolute values * Reduce alert fatigue with proper tuning 3. **Dashboard Discipline**: Keep dashboards focused and actionable * Separate dashboards by role and concern * Include relevant context in panel titles * Add threshold lines and annotations ## Next Steps[​](#next-steps "Direct link to Next Steps") * Explore advanced PromQL queries in the [Prometheus documentation](https://prometheus.io/docs/prometheus/latest/querying/basics/) * Set up alerting rules following the [Prometheus alerting guide](https://prometheus.io/docs/alerting/latest/overview/) * Configure notification channels in [Grafana](/operate/operators/monitoring/grafana-setup.md) * Return to [Monitoring Overview](/operate/operators/monitoring.md) * Join the [Aztec Discord](https://discord.gg/aztec) to share dashboards with the community --- # OpenTelemetry Collector Setup ## Overview[​](#overview "Direct link to Overview") The OpenTelemetry Collector receives metrics from your Aztec node and exports them to Prometheus for storage and analysis. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * A running Aztec node with Docker Compose * Basic understanding of Docker networking ## Setup Steps[​](#setup-steps "Direct link to Setup Steps") ### Step 1: Create Configuration File[​](#step-1-create-configuration-file "Direct link to Step 1: Create Configuration File") Create an `otel-collector-config.yml` file in the same directory as your existing `docker-compose.yml`: ``` receivers: otlp: protocols: http: endpoint: 0.0.0.0:4318 grpc: endpoint: 0.0.0.0:4317 exporters: prometheus: endpoint: "0.0.0.0:8889" metric_expiration: 5m processors: batch: service: pipelines: metrics: receivers: [otlp] exporters: - prometheus ``` This configuration: * Receives metrics via OTLP (OpenTelemetry Protocol) on ports 4317 (gRPC) and 4318 (HTTP) * Exports metrics to Prometheus format on port 8889 * Uses batch processing for efficiency ### Step 2: Add OTEL Collector to Docker Compose[​](#step-2-add-otel-collector-to-docker-compose "Direct link to Step 2: Add OTEL Collector to Docker Compose") Add the following to your existing `docker-compose.yml` file: ``` services: # ... existing services ... otel-collector: image: otel/opentelemetry-collector container_name: aztec-otel ports: - 8888:8888 # OTEL collector metrics endpoint - 8889:8889 # Prometheus exporter endpoint - 4317:4317 # OTLP gRPC receiver - 4318:4318 # OTLP HTTP receiver volumes: - ./otel-collector-config.yml:/etc/otel-collector-config.yml command: >- --config=/etc/otel-collector-config.yml networks: - aztec restart: always ``` ### Step 3: Configure Your Node to Export Metrics[​](#step-3-configure-your-node-to-export-metrics "Direct link to Step 3: Configure Your Node to Export Metrics") Configure your Aztec node to export metrics to the OTEL collector. **Step 3a: Add to .env file** Add these variables to your `.env` file: ``` OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://otel-collector:4318/v1/metrics ``` **Step 3b: Update docker-compose.yml** Add these environment variables to your node's service in `docker-compose.yml`: ``` services: aztec-node: # or aztec-sequencer, prover-node, etc. # ... existing configuration ... environment: # ... existing environment variables ... OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: ${OTEL_EXPORTER_OTLP_METRICS_ENDPOINT} ``` **Network configuration:** Since your node and OTEL collector share the same Docker Compose file and `aztec` network, use the service name `otel-collector` in the endpoint URL as shown above. ### Step 4: Start Services[​](#step-4-start-services "Direct link to Step 4: Start Services") ``` # Start or restart all services docker compose up -d ``` ### Step 5: Verify Metrics Collection[​](#step-5-verify-metrics-collection "Direct link to Step 5: Verify Metrics Collection") Check that metrics are being collected: ``` # View OTEL collector logs docker compose logs -f otel-collector # Query Prometheus endpoint curl http://localhost:8889/metrics ``` You should see metrics in Prometheus format. ## Next Steps[​](#next-steps "Direct link to Next Steps") * Proceed to [Prometheus Setup](/operate/operators/monitoring/prometheus-setup.md) to configure metric storage and querying * Return to [Monitoring Overview](/operate/operators/monitoring.md) --- # Prometheus Setup ## Overview[​](#overview "Direct link to Overview") Prometheus scrapes and stores the metrics exposed by the OTEL collector, providing a time-series database for querying and analysis. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Completed [OpenTelemetry Collector Setup](/operate/operators/monitoring/otel-setup.md) * OTEL collector running and exposing metrics on port 8889 ## Setup Steps[​](#setup-steps "Direct link to Setup Steps") ### Step 1: Create Prometheus Configuration[​](#step-1-create-prometheus-configuration "Direct link to Step 1: Create Prometheus Configuration") Create a `prometheus.yml` file: ``` global: scrape_interval: 15s evaluation_interval: 15s scrape_configs: - job_name: 'aztec-node' static_configs: - targets: ['otel-collector:8889'] labels: instance: 'aztec-node-1' ``` If you're running multiple nodes, adjust the `instance` label to uniquely identify each node. ### Step 2: Add Prometheus to Docker Compose[​](#step-2-add-prometheus-to-docker-compose "Direct link to Step 2: Add Prometheus to Docker Compose") Add Prometheus to your `docker-compose.yml`: ``` services: # ... existing services (otel-collector, etc.) ... prometheus: image: prom/prometheus:latest container_name: aztec-prometheus ports: - 9090:9090 volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml - prometheus-data:/prometheus command: - '--config.file=/etc/prometheus/prometheus.yml' - '--storage.tsdb.path=/prometheus' - '--storage.tsdb.retention.time=30d' networks: - aztec restart: always volumes: prometheus-data: ``` ### Step 3: Start Prometheus[​](#step-3-start-prometheus "Direct link to Step 3: Start Prometheus") ``` docker compose up -d ``` ### Step 4: Verify Prometheus[​](#step-4-verify-prometheus "Direct link to Step 4: Verify Prometheus") Access Prometheus UI at `http://localhost:9090` and verify: 1. Go to **Status → Targets** to check that the `aztec-node` target is up 2. Go to **Graph** and query a metric (e.g., `aztec_archiver_block_height`) ## Using Prometheus[​](#using-prometheus "Direct link to Using Prometheus") ### Query Metrics[​](#query-metrics "Direct link to Query Metrics") Use the Prometheus UI to explore and query metrics: 1. Navigate to `http://localhost:9090/graph` 2. Enter a metric name in the query box (use autocomplete to discover available metrics) 3. Click **Execute** to see the results 4. Switch between **Table** and **Graph** views ### Example Queries[​](#example-queries "Direct link to Example Queries") ``` # Current block height aztec_archiver_block_height # Blocks synced over time window increase(aztec_archiver_block_height[5m]) # Memory usage process_resident_memory_bytes # CPU usage rate rate(process_cpu_seconds_total[5m]) ``` ## Next Steps[​](#next-steps "Direct link to Next Steps") * Proceed to [Grafana Setup](/operate/operators/monitoring/grafana-setup.md) to configure visualization and alerting * Return to [Monitoring Overview](/operate/operators/monitoring.md) --- # Complete Example and Troubleshooting ## Complete Docker Compose Example[​](#complete-docker-compose-example "Direct link to Complete Docker Compose Example") Here's a complete example with all monitoring components integrated with your Aztec node: ``` services: # Your Aztec node (example for full node) aztec-node: image: "aztecprotocol/aztec:4.3.1" container_name: "aztec-node" ports: - ${AZTEC_PORT}:${AZTEC_PORT} - ${P2P_PORT}:${P2P_PORT} - ${P2P_PORT}:${P2P_PORT}/udp volumes: - ${DATA_DIRECTORY}:/var/lib/data environment: DATA_DIRECTORY: /var/lib/data LOG_LEVEL: ${LOG_LEVEL} ETHEREUM_HOSTS: ${ETHEREUM_HOSTS} L1_CONSENSUS_HOST_URLS: ${L1_CONSENSUS_HOST_URLS} P2P_IP: ${P2P_IP} P2P_PORT: ${P2P_PORT} AZTEC_PORT: ${AZTEC_PORT} OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: http://otel-collector:4318/v1/metrics entrypoint: >- node --no-warnings /usr/src/yarn-project/aztec/dest/bin/index.js start --node --archiver --network mainnet networks: - aztec restart: always # OpenTelemetry Collector otel-collector: image: otel/opentelemetry-collector container_name: aztec-otel ports: - 8888:8888 - 8889:8889 - 4317:4317 - 4318:4318 volumes: - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml command: >- --config=/etc/otel-collector-config.yaml networks: - aztec restart: always # Prometheus prometheus: image: prom/prometheus:latest container_name: aztec-prometheus ports: - 9090:9090 volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml - prometheus-data:/prometheus command: - "--config.file=/etc/prometheus/prometheus.yml" - "--storage.tsdb.path=/prometheus" - "--storage.tsdb.retention.time=30d" networks: - aztec restart: always # Grafana grafana: image: grafana/grafana:latest container_name: aztec-grafana ports: - 3000:3000 volumes: - grafana-data:/var/lib/grafana environment: - GF_SECURITY_ADMIN_PASSWORD=your-secure-password - GF_USERS_ALLOW_SIGN_UP=false networks: - aztec restart: always volumes: prometheus-data: grafana-data: networks: aztec: name: aztec ``` This configuration includes: * Your Aztec node configured to export metrics to the OTEL collector * OpenTelemetry Collector to receive and process metrics * Prometheus to store time-series data with 30-day retention * Grafana for visualization and alerting * Persistent volumes for Prometheus and Grafana data * All services on the same Docker network for easy communication ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Metrics not appearing[​](#metrics-not-appearing "Direct link to Metrics not appearing") **Issue**: No metrics showing in Prometheus or Grafana. **Solutions**: * Verify OTEL collector is running: `docker compose ps otel-collector` * Check OTEL collector logs: `docker compose logs otel-collector` * Verify node is configured with correct OTEL endpoints * Test OTEL collector endpoint: `curl http://localhost:8889/metrics` * Ensure all containers are on the same Docker network ### Prometheus target down[​](#prometheus-target-down "Direct link to Prometheus target down") **Issue**: Prometheus shows target as "down" in Status → Targets. **Solutions**: * Verify OTEL collector is running and exposing port 8889 * Check Prometheus configuration in `prometheus.yml` * Ensure target address is correct (use service name if in same Docker network) * Review Prometheus logs: `docker compose logs prometheus` ### Grafana cannot connect to Prometheus[​](#grafana-cannot-connect-to-prometheus "Direct link to Grafana cannot connect to Prometheus") **Issue**: Grafana shows "Bad Gateway" or cannot query Prometheus. **Solutions**: * Verify Prometheus is running: `docker compose ps prometheus` * Check data source URL in Grafana (should be `http://prometheus:9090`) * Test Prometheus endpoint: `curl http://localhost:9090/api/v1/query?query=up` * Ensure Grafana and Prometheus are on the same Docker network ## Next Steps[​](#next-steps "Direct link to Next Steps") * Set up alerting rules in Prometheus for critical conditions * Create custom dashboards for your specific monitoring needs * Configure notification channels (Slack, PagerDuty, email) in Grafana * Explore advanced PromQL queries for deeper insights * Join the [Aztec Discord](https://discord.gg/aztec) to share dashboards with the community --- # FAQs & Common Issues ## Overview[​](#overview "Direct link to Overview") This guide addresses common issues node operators encounter when running Aztec nodes. Each entry includes the issue symptoms, possible causes, and step-by-step solutions. If your issue isn't listed here, visit the [Aztec Discord](https://discord.gg/aztec) in the `#operator-faq` channel for community support. ## Node Sync Issues[​](#node-sync-issues "Direct link to Node Sync Issues") ### SYNC\_BLOCK Failed Error[​](#sync_block-failed-error "Direct link to SYNC_BLOCK Failed Error") **Symptom**: You see this error in your node logs: ``` ERROR: world-state:database Call SYNC_BLOCK failed: Error: Can't synch block: block state does not match world state ``` **Cause**: Your local database state is corrupted or out of sync with the network. **Solution**: 1. Stop your node: ``` docker compose down ``` 2. Remove the archiver data directory: ``` rm -rf ~/.aztec/v4.3.1/data/archiver ``` 3. Restart your node: ``` docker compose up -d ``` Data Loss and Resync This process removes local state and requires full resynchronization. Consider using snapshot sync mode (`SYNC_MODE=snapshot`) to speed up recovery. See the [syncing best practices guide](/operate/operators/setup/syncing_best_practices.md) for more information. ### Error Getting Slot Number[​](#error-getting-slot-number "Direct link to Error Getting Slot Number") **Symptom**: Your logs show "Error getting slot number" related to beacon or execution endpoints. **Cause**: * **Beacon-related errors**: Failed to connect to your L1 consensus (beacon) RPC endpoint * **Execution-related errors**: Failed to connect to your L1 execution RPC endpoint or reporting routine issue **Solutions**: 1. **Verify L1 endpoint configuration**: * Check your `L1_CONSENSUS_HOST_URLS` setting points to your beacon node * Check your `ETHEREUM_HOSTS` setting points to your execution client * Ensure URLs are formatted correctly (e.g., `http://localhost:5052` for beacon) 2. **Test endpoint connectivity**: ``` # Test beacon endpoint curl [YOUR_BEACON_ENDPOINT]/eth/v1/beacon/headers # Test execution endpoint curl -X POST -H "Content-Type: application/json" \ --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \ [YOUR_EXECUTION_ENDPOINT] ``` 3. **Verify L1 clients are synced**: * Check that your beacon node is fully synced * Check that your execution client is fully synced * Use `docker compose logs` or check L1 client logs for sync status 4. **Check for rate limiting** (if using third-party RPC): * See the "RPC and Rate Limiting" section below * Consider using your own L1 node for better reliability ## RPC and Rate Limiting[​](#rpc-and-rate-limiting "Direct link to RPC and Rate Limiting") ### RPC Rate Limit or Quota Exceeded[​](#rpc-rate-limit-or-quota-exceeded "Direct link to RPC Rate Limit or Quota Exceeded") **Symptom**: Your logs show errors like: ``` Error: quota limit exceeded Error: rate limit exceeded Error: too many requests ``` **Cause**: Your RPC provider is throttling requests due to rate limits or quota restrictions. **Solutions**: 1. **Register for an API key with your RPC provider**: * Most providers (Infura, Alchemy, QuickNode) offer higher limits with authenticated requests * Update your configuration to include the API key in your RPC URL * Example: `https://mainnet.infura.io/v3/YOUR_API_KEY` 2. **Use your own L1 node** (recommended for sequencers): * Running your own Ethereum node eliminates rate limits entirely * Provides better performance, reliability, and privacy * See [Eth Docker's guide](https://ethdocker.com/Usage/QuickStart) for setup instructions * Ensure you're running both execution and consensus clients 3. **Configure multiple RPC endpoints for failover**: * Aztec nodes support comma-separated RPC URLs * Example: `ETHEREUM_HOSTS=https://rpc1.example.com,https://rpc2.example.com` * The node will automatically fail over if one endpoint is unavailable Run Your Own L1 Infrastructure Sequencer operators should always run their own L1 infrastructure to ensure reliability, avoid rate limits, and maintain optimal performance. Third-party RPC providers are suitable for testing but not recommended for production sequencer operations. ### Blob Retrieval Errors[​](#blob-retrieval-errors "Direct link to Blob Retrieval Errors") **Symptom**: Your logs show errors like: ``` Error: No blob bodies found Error: Unable to get blob sidecar, Gateway Time-out (504) ``` **Cause**: Your beacon node endpoint is slow, overloaded, rate-limited, or not synced properly. **Solutions**: 1. **Verify beacon endpoint configuration**: ``` # Check L1_CONSENSUS_HOST_URLS in your configuration # Should point to your beacon node's API endpoint ``` 2. **Test beacon endpoint health**: ``` # Check if beacon node is responding curl [YOUR_BEACON_ENDPOINT]/eth/v1/node/health # Check sync status curl [YOUR_BEACON_ENDPOINT]/eth/v1/node/syncing ``` 3. **Ensure beacon node is fully synced**: * Check your beacon client logs * Verify the sync status shows as synced * Blob data is only available for recent blocks (typically 18 days) 4. **Run your own beacon node** (recommended): * Using a third-party beacon endpoint may have rate limits * Running your own provides better reliability and eliminates timeouts * See the [prerequisites guide](/operate/operators/prerequisites.md) for L1 infrastructure setup ## L1 Node Requirements[​](#l1-node-requirements "Direct link to L1 Node Requirements") ### Do I Need an L1 Archive Node?[​](#do-i-need-an-l1-archive-node "Direct link to Do I Need an L1 Archive Node?") No. You do not need an L1 archive node to run an Aztec node. Snapshot sync is the recommended approach and works with standard L1 full nodes. To use snapshot sync, set `SYNC_MODE=snapshot` in your configuration. ## Funding and Resources[​](#funding-and-resources "Direct link to Funding and Resources") ### Insufficient L1 Funds[​](#insufficient-l1-funds "Direct link to Insufficient L1 Funds") **Symptom**: Your sequencer cannot publish blocks, and logs show: ``` Error: Insufficient L1 funds Error: insufficient funds for gas * price + value ``` **Cause**: Your publisher address doesn't have enough ETH to pay for L1 gas fees. **Solutions**: 1. **Maintain sufficient balance**: * Keep at least **0.1 ETH** in your publisher account at all times * Monitor your balance regularly to avoid running out * Falling below the minimum balance may result in slashing 2. **Set up balance monitoring**: ``` # Check your publisher balance cast balance [YOUR_PUBLISHER_ADDRESS] --rpc-url [YOUR_RPC_URL] ``` 3. **Configure alerts**: * Set up monitoring to alert you when balance drops below 0.15 ETH * This gives you time to top up before hitting the critical threshold Slashing Risk Sequencers with insufficient funds in their publisher account risk being slashed. Always maintain at least 0.1 ETH to ensure uninterrupted operation and avoid penalties. ## Updates and Maintenance[​](#updates-and-maintenance "Direct link to Updates and Maintenance") #### Version-Specific Updates:[​](#version-specific-updates "Direct link to Version-Specific Updates:") To update to a specific version: ``` # Change the image tag from: image: "aztecprotocol/aztec:latest" # To: image: "aztecprotocol/aztec:4.3.1" ``` Then run: ``` docker compose pull docker compose down docker compose up -d ``` Stay Informed About Updates Join the [Aztec Discord](https://discord.gg/aztec) and follow the announcements channel to stay informed about new releases and required updates. ## Network and Connectivity[​](#network-and-connectivity "Direct link to Network and Connectivity") ### Port Forwarding Not Working[​](#port-forwarding-not-working "Direct link to Port Forwarding Not Working") **Symptom**: Your node cannot discover peers or shows "0 peers connected" in logs. **Cause**: Firewall rules or router configuration are blocking P2P connections. **Solutions**: 1. **Verify your external IP address**: ``` curl ipv4.icanhazip.com ``` Confirm this matches your `P2P_IP` configuration. 2. **Test port connectivity**: ``` # From another machine, test if your P2P port is accessible nc -zv [YOUR_EXTERNAL_IP] 40400 ``` 3. **Configure router port forwarding**: * Log into your router's admin interface * Forward port 40400 (TCP and UDP) to your node's local IP address * Save and restart router if needed 4. **Check local firewall rules**: ``` # Linux: Allow P2P port through firewall sudo ufw allow 40400/tcp sudo ufw allow 40400/udp # Verify rules sudo ufw status ``` 5. **Verify Docker network settings**: * Ensure ports are properly mapped in docker-compose.yml * Check that `P2P_PORT` environment variable matches the exposed ports ## Other Common Issues[​](#other-common-issues "Direct link to Other Common Issues") ### CodeError: Stream Reset[​](#codeerror-stream-reset "Direct link to CodeError: Stream Reset") **Symptom**: You occasionally see this error in logs: ``` CodeError: stream reset ``` **Cause**: Temporary P2P connection disruption. This is normal network behavior and occurs when peer connections are interrupted. **Impact**: This is safe to ignore. Your node automatically reconnects to peers and maintains network connectivity. **Action Required**: None. This is expected behavior in P2P networks. ### Keystore Not Loading[​](#keystore-not-loading "Direct link to Keystore Not Loading") **Symptom**: Your sequencer fails to start with errors about invalid keys or missing keystore. **Cause**: Keystore file is improperly formatted, missing, or has incorrect permissions. **Solutions**: 1. **Verify keystore.json format**: ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "0xYOUR_ETH_PRIVATE_KEY_HERE", "bls": "0xYOUR_BLS_PRIVATE_KEY_HERE" }, "publisher": ["0xYOUR_PUBLISHER_KEY_HERE"], "coinbase": "0xYOUR_COINBASE_ADDRESS", "feeRecipient": "0xYOUR_AZTEC_ADDRESS" } ] } ``` 2. **Validate private key format**: * Keys should start with `0x` * Keys should be 64 hexadecimal characters (plus the `0x` prefix) * No spaces or extra characters * The attester must contain both `eth` and `bls` keys 3. **Check file permissions**: ``` # Ensure keystore is readable chmod 600 ~/.aztec/keys/keystore.json # Verify ownership ls -la ~/.aztec/keys/ ``` 4. **Verify keystore directory path**: * Ensure `KEY_STORE_DIRECTORY` environment variable is set in your `.env` file * Verify the volume mount in `docker-compose.yml` points to the correct directory For more information on keystore configuration and creation, see the [Creating Validator Keystores guide](/operate/operators/keystore/creating_keystores.md) and the [Advanced Keystore Usage guide](/operate/operators/keystore.md). ### Docker Container Won't Start[​](#docker-container-wont-start "Direct link to Docker Container Won't Start") **Symptom**: Docker container crashes immediately after starting or won't start at all. **Cause**: Various issues including configuration errors, insufficient resources, or port conflicts. **Solutions**: 1. **Check container logs**: ``` docker compose logs aztec-sequencer ``` Look for specific error messages that indicate the problem. 2. **Verify Docker resources**: * Ensure sufficient disk space: `df -h` * Check Docker has adequate memory allocated (16GB+ recommended) * Verify CPU resources are available 3. **Check environment file format**: ``` # Verify .env file exists and is properly formatted cat .env # No spaces around = signs # No quotes around values (unless necessary) ``` 4. **Verify port availability**: ``` # Check if ports are already in use lsof -i :8080 lsof -i :40400 ``` 5. **Update Docker and Docker Compose**: ``` # Check versions docker --version docker compose version # Update if needed sudo apt-get update && sudo apt-get upgrade docker-ce docker-compose-plugin ``` 6. **Try a clean restart**: ``` docker compose down docker compose pull docker compose up -d ``` ## Getting Additional Help[​](#getting-additional-help "Direct link to Getting Additional Help") If you've tried the solutions above and are still experiencing issues: 1. **Gather diagnostic information**: * Recent log output from your node * Your configuration (remove private keys!) * Aztec version you're running * Operating system and hardware specs 2. **Check existing issues**: * Browse the [Aztec GitHub issues](https://github.com/AztecProtocol/aztec-packages/issues) * Search for similar problems and solutions 3. **Ask for help**: * Join the [Aztec Discord](https://discord.gg/aztec) * Post in the `#operator-faq` or `#operator-support` channel * Include your diagnostic information * Be specific about what you've already tried ## Next Steps[​](#next-steps "Direct link to Next Steps") * Review [monitoring setup](/operate/operators/monitoring.md) to catch issues early with metrics and alerts * Check the [CLI reference](/operate/operators/reference/cli-reference.md) for all configuration options * Join the [Aztec Discord](https://discord.gg/aztec) for real-time operator support --- # Prerequisites ## Overview[​](#overview "Direct link to Overview") This guide covers the prerequisites and setup requirements for running nodes on the Aztec network. ## Common Prerequisites[​](#common-prerequisites "Direct link to Common Prerequisites") The following prerequisites apply to all node types. ### Operating System[​](#operating-system "Direct link to Operating System") The node software can be run on any Unix system released after 2020. * Linux (common flavors) * MacOS (ARM and intel) ### Docker and Docker Compose[​](#docker-and-docker-compose "Direct link to Docker and Docker Compose") Docker and Docker Compose are required for all node types. All Aztec nodes run in Docker containers managed by Docker Compose. **On Linux:** Install Docker Engine and Docker Compose separately: 1. Install Docker: ``` curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh ``` 2. Add your user to the docker group so `sudo` is not needed: ``` sudo groupadd docker sudo usermod -aG docker $USER newgrp docker # Test without sudo docker run hello-world ``` 3. Install Docker Compose by following the [Docker Compose installation guide](https://docs.docker.com/compose/install/). **On macOS:** Install [Docker Desktop](https://docs.docker.com/desktop/install/mac-install/), which includes both Docker and Docker Compose. ### Aztec Toolchain[​](#aztec-toolchain "Direct link to Aztec Toolchain") The Aztec toolchain provides CLI utilities for key generation, validator registration, and other operational tasks. While not required for running nodes (which use Docker Compose), it is needed for: * Generating validator keystores and creating staking registration data (`aztec validator-keys`) * Registering sequencers on L1 (`aztec add-l1-validator`) Install the Aztec toolchain using the official installer: ``` VERSION=4.3.1 bash -i <(curl -sL https://install.aztec.network/4.3.1) ``` macOS users macOS ships with an outdated version of Bash (v3.2) that is known to cause issues with the installer. Install a modern version with [Homebrew](https://brew.sh/): ``` brew install bash ``` Even if you use zsh as your default shell, the installer explicitly invokes `bash`. If the installer still picks up the old version, add the Homebrew `bash` to your `$PATH` or [set it as your default shell](https://support.apple.com/en-gb/guide/terminal/trml113/mac). ### L1 Ethereum Node Access[​](#l1-ethereum-node-access "Direct link to L1 Ethereum Node Access") All Aztec nodes require access to Ethereum L1 node endpoints: * **Execution client endpoint** (e.g., Geth, Nethermind, Besu, Erigon) * **Consensus client endpoint** (e.g., Prysm, Lighthouse, Teku, Nimbus) **Options:** 1. **Run your own L1 node** (recommended for best performance): * Better performance and lower latency * No rate limiting or request throttling * Greater reliability and uptime control * Enhanced privacy for your node operations * See [Eth Docker's guide](https://ethdocker.com/Usage/QuickStart) for setup instructions 2. **Use a third-party RPC provider**: * Easier to set up initially * May have rate limits and throttling * Ensure the provider supports beacon apis High Throughput Required Your L1 endpoints must support high throughput to avoid degraded node performance. ### Port Forwarding and Connectivity[​](#port-forwarding-and-connectivity "Direct link to Port Forwarding and Connectivity") For nodes participating in the P2P network (full nodes, sequencers, provers), proper port configuration is essential: **Required steps:** 1. Configure your router to forward both UDP and TCP traffic on your P2P port (default: 40400) to your node's local IP address 2. Ensure your firewall allows traffic on the required ports: * P2P port: 40400 (default, both TCP and UDP) * HTTP API port: 8080 (default) 3. Set the `P2P_IP` environment variable to your external IP address 4. Verify the P2P port is accessible from the internet **Find your public IP address:** ``` curl ipv4.icanhazip.com ``` **Verify port connectivity:** ``` # For TCP traffic on port 40400 nc -zv [YOUR_EXTERNAL_IP] 40400 # For UDP traffic on port 40400 nc -zuv [YOUR_EXTERNAL_IP] 40400 ``` Port Forwarding Required If port forwarding isn't properly configured, your node may not be able to participate in P2P duties. ## Next Steps[​](#next-steps "Direct link to Next Steps") Once you have met the prerequisites, proceed to set up your desired node type: * [Run a Full Node →](/operate/operators/setup/running_a_node.md) * [Run a Sequencer Node →](/operate/operators/setup/sequencer_management.md) * [Run a Prover Node →](/operate/operators/setup/running_a_prover.md) --- # Changelog ## Overview[​](#overview "Direct link to Overview") This changelog documents all configuration changes, new features, and breaking changes across Aztec node versions. Each version has a dedicated page with detailed migration instructions. ## Version history[​](#version-history "Direct link to Version history") ### [v4.3.x](/operate/operators/reference/changelog/v4.3.md)[​](#v43x "Direct link to v43x") Bundled binaries renamed under an `aztec-` prefix on `PATH`. v4.3.1 is a bug-fix release. **Key changes:** * `aztec-up` no longer places bare-named binaries (`forge`, `cast`, `nargo`, `bb`, `pxe`, `txe`, `validator-client`, `blob-client`, ...) on `PATH`; use the `aztec-` prefixed names instead * v4.3.1: fixes a block-stream error loop after finalization, prover proof-submission ordering, and released contract artifact version stamping **Migration difficulty**: Low [View full changelog →](/operate/operators/reference/changelog/v4.3.md) *** ### [v4.2.0](/operate/operators/reference/changelog/v4.2.md)[​](#v420 "Direct link to v420") New features and configuration options for node operators. **Key changes:** * Blob retrieval improvements with unified retry loop **Migration difficulty**: Low [View full changelog →](/operate/operators/reference/changelog/v4.2.md) *** ### [v4.x (Upgrade from Ignition)](/operate/operators/reference/changelog/v4.md)[​](#v4x-upgrade-from-ignition "Direct link to v4x-upgrade-from-ignition") Major upgrade from Ignition (v2.x) to Alpha (v4.x) with significant architectural changes. **Key changes:** * Checkpoint-based block architecture (multiple L2 blocks per slot) * Blob-only data publication (EIP-4844), calldata fallback removed * Double signing slashing infrastructure * HA signing with PostgreSQL for redundant sequencer nodes * Admin API key authentication * Sequencer environment variable renames * Withdrawal delay increase (7 to 30 days) **Migration difficulty**: High [View full changelog →](/operate/operators/reference/changelog/v4.md) *** ### [v2.0.2 (from v1.2.1)](/operate/operators/reference/changelog/v2.0.2.md)[​](#v202-from-v121 "Direct link to v202-from-v121") Major release with significant configuration simplification, keystore integration, and feature updates. **Key changes:** * Simplified L1 contract address configuration (registry-only) * Integrated keystore system for key management * Removed component-specific settings in favor of global configuration * Enhanced P2P transaction collection capabilities * New invalidation controls for sequencers **Migration difficulty**: Moderate to High [View full changelog →](/operate/operators/reference/changelog/v2.0.2.md) *** ## Migration guides[​](#migration-guides "Direct link to Migration guides") When upgrading between versions: 1. Review the version-specific changelog for breaking changes 2. Follow the migration checklist for your node type 3. Test in a non-production environment first 4. Check the troubleshooting section for common upgrade issues 5. Join [Aztec Discord](https://discord.gg/aztec) for upgrade support ## Related resources[​](#related-resources "Direct link to Related resources") * [CLI Reference](/operate/operators/reference/cli-reference.md) - Current command-line options * [Node API Reference](/operate/operators/reference/node_api_reference.md) - API documentation * [Ethereum RPC Reference](/operate/operators/reference/ethereum_rpc_reference.md) - L1 RPC usage --- # v2.0.2 (from v1.2.1) ## Overview[​](#overview "Direct link to Overview") Version 2.0.2 introduces significant configuration simplification, an integrated keystore system, and enhanced P2P capabilities. This release includes breaking changes that require migration from v1.2.1. **Migration difficulty**: Moderate to High ## Breaking changes[​](#breaking-changes "Direct link to Breaking changes") ### L1 contract addresses[​](#l1-contract-addresses "Direct link to L1 contract addresses") **v1.2.1:** ``` --rollup-address ($ROLLUP_CONTRACT_ADDRESS) --inbox-address ($INBOX_CONTRACT_ADDRESS) --outbox-address ($OUTBOX_CONTRACT_ADDRESS) --fee-juice-address ($FEE_JUICE_CONTRACT_ADDRESS) --staking-asset-address ($STAKING_ASSET_CONTRACT_ADDRESS) --fee-juice-portal-address ($FEE_JUICE_PORTAL_CONTRACT_ADDRESS) --registry-address ($REGISTRY_CONTRACT_ADDRESS) ``` **v2.0.2:** ``` --registry-address ($REGISTRY_CONTRACT_ADDRESS) --rollup-version ($ROLLUP_VERSION) # Default: canonical ``` **Migration**: Only registry address is required. All other contract addresses are derived automatically. ### Keystore integration[​](#keystore-integration "Direct link to Keystore integration") **v1.2.1:** ``` --sequencer.publisherPrivateKey ($SEQ_PUBLISHER_PRIVATE_KEY) --proverNode.publisherPrivateKey ($PROVER_PUBLISHER_PRIVATE_KEY) ``` **v2.0.2:** ``` --proverNode.keyStoreDirectory ($KEY_STORE_DIRECTORY) # Multiple publishers supported --sequencer.publisherPrivateKeys ($SEQ_PUBLISHER_PRIVATE_KEYS) --sequencer.publisherAddresses ($SEQ_PUBLISHER_ADDRESSES) --proverNode.publisherPrivateKeys ($PROVER_PUBLISHER_PRIVATE_KEYS) --proverNode.publisherAddresses ($PROVER_PUBLISHER_ADDRESSES) ``` **Migration**: Create keystore directory, change singular to plural. Use `*_ADDRESSES` for remote signers. See [Advanced Keystore Guide](/operate/operators/keystore.md). ### Validator configuration[​](#validator-configuration "Direct link to Validator configuration") **v1.2.1:** ``` --sequencer.validatorPrivateKeys ($VALIDATOR_PRIVATE_KEYS) ``` **v2.0.2:** ``` --sequencer.validatorPrivateKeys ($VALIDATOR_PRIVATE_KEYS) --sequencer.validatorAddresses ($VALIDATOR_ADDRESSES) # For remote signers --sequencer.disabledValidators # Temporarily disable ``` ### Sync mode relocated[​](#sync-mode-relocated "Direct link to Sync mode relocated") **v1.2.1:** Component-specific ``` --node.syncMode ($SYNC_MODE) --node.snapshotsUrl ($SYNC_SNAPSHOTS_URL) --proverNode.syncMode ($SYNC_MODE) --proverNode.snapshotsUrl ($SYNC_SNAPSHOTS_URL) ``` **v2.0.2:** Global setting ``` --sync-mode ($SYNC_MODE) # Options: full, snapshot, force-snapshot --snapshots-url ($SYNC_SNAPSHOTS_URL) ``` ### World state separation[​](#world-state-separation "Direct link to World state separation") **v1.2.1:** Prover-node-specific ``` --proverNode.worldStateBlockCheckIntervalMS ($WS_BLOCK_CHECK_INTERVAL_MS) --proverNode.worldStateProvenBlocksOnly ($WS_PROVEN_BLOCKS_ONLY) --proverNode.worldStateBlockRequestBatchSize ($WS_BLOCK_REQUEST_BATCH_SIZE) --proverNode.worldStateDbMapSizeKb ($WS_DB_MAP_SIZE_KB) --proverNode.archiveTreeMapSizeKb ($ARCHIVE_TREE_MAP_SIZE_KB) --proverNode.nullifierTreeMapSizeKb ($NULLIFIER_TREE_MAP_SIZE_KB) --proverNode.noteHashTreeMapSizeKb ($NOTE_HASH_TREE_MAP_SIZE_KB) --proverNode.messageTreeMapSizeKb ($MESSAGE_TREE_MAP_SIZE_KB) --proverNode.publicDataTreeMapSizeKb ($PUBLIC_DATA_TREE_MAP_SIZE_KB) --proverNode.worldStateDataDirectory ($WS_DATA_DIRECTORY) --proverNode.worldStateBlockHistory ($WS_NUM_HISTORIC_BLOCKS) ``` **v2.0.2:** Global settings only ``` --world-state-data-directory ($WS_DATA_DIRECTORY) --world-state-db-map-size-kb ($WS_DB_MAP_SIZE_KB) --world-state-block-history ($WS_NUM_HISTORIC_BLOCKS) ``` **Migration**: Move to global WORLD STATE section. Tree-specific map sizes and other world state settings removed. ## Removed features[​](#removed-features "Direct link to Removed features") ### Faucet service[​](#faucet-service "Direct link to Faucet service") ``` # All removed in v2.0.2 --faucet --faucet.apiServer --faucet.apiServerPort ($FAUCET_API_SERVER_PORT) --faucet.viemPollingIntervalMS ($L1_READER_VIEM_POLLING_INTERVAL_MS) --faucet.l1Mnemonic ($MNEMONIC) --faucet.mnemonicAddressIndex ($FAUCET_MNEMONIC_ADDRESS_INDEX) --faucet.interval ($FAUCET_INTERVAL_MS) --faucet.ethAmount ($FAUCET_ETH_AMOUNT) --faucet.l1Assets ($FAUCET_L1_ASSETS) ``` ### L1 transaction monitoring[​](#l1-transaction-monitoring "Direct link to L1 transaction monitoring") All removed from archiver and sequencer: ``` --archiver.gasLimitBufferPercentage ($L1_GAS_LIMIT_BUFFER_PERCENTAGE) --archiver.maxGwei ($L1_GAS_PRICE_MAX) --archiver.maxBlobGwei ($L1_BLOB_FEE_PER_GAS_MAX) --archiver.priorityFeeBumpPercentage ($L1_PRIORITY_FEE_BUMP_PERCENTAGE) --archiver.priorityFeeRetryBumpPercentage ($L1_PRIORITY_FEE_RETRY_BUMP_PERCENTAGE) --archiver.fixedPriorityFeePerGas ($L1_FIXED_PRIORITY_FEE_PER_GAS) --archiver.maxAttempts ($L1_TX_MONITOR_MAX_ATTEMPTS) --archiver.checkIntervalMs ($L1_TX_MONITOR_CHECK_INTERVAL_MS) --archiver.stallTimeMs ($L1_TX_MONITOR_STALL_TIME_MS) --archiver.txTimeoutMs ($L1_TX_MONITOR_TX_TIMEOUT_MS) --archiver.txPropagationMaxQueryAttempts ($L1_TX_PROPAGATION_MAX_QUERY_ATTEMPTS) --archiver.cancelTxOnTimeout ($L1_TX_MONITOR_CANCEL_TX_ON_TIMEOUT) # Same settings removed from --sequencer.* ``` **Migration**: L1 transaction management now uses optimized internal defaults. ### Rollup constants from archiver[​](#rollup-constants-from-archiver "Direct link to Rollup constants from archiver") All rollup constants now derived from L1 contracts: ``` # All removed in v2.0.2 --archiver.ethereumSlotDuration ($ETHEREUM_SLOT_DURATION) --archiver.aztecSlotDuration ($AZTEC_SLOT_DURATION) --archiver.aztecEpochDuration ($AZTEC_EPOCH_DURATION) --archiver.aztecTargetCommitteeSize ($AZTEC_TARGET_COMMITTEE_SIZE) --archiver.aztecProofSubmissionEpochs ($AZTEC_PROOF_SUBMISSION_EPOCHS) --archiver.depositAmount ($AZTEC_DEPOSIT_AMOUNT) --archiver.minimumStake ($AZTEC_MINIMUM_STAKE) --archiver.slashingQuorum ($AZTEC_SLASHING_QUORUM) --archiver.slashingRoundSize ($AZTEC_SLASHING_ROUND_SIZE) --archiver.governanceProposerQuorum ($AZTEC_GOVERNANCE_PROPOSER_QUORUM) --archiver.governanceProposerRoundSize ($AZTEC_GOVERNANCE_PROPOSER_ROUND_SIZE) --archiver.manaTarget ($AZTEC_MANA_TARGET) --archiver.provingCostPerMana ($AZTEC_PROVING_COST_PER_MANA) --archiver.exitDelaySeconds ($AZTEC_EXIT_DELAY_SECONDS) # Same settings removed from --sequencer.* ``` ### Node deployment options[​](#node-deployment-options "Direct link to Node deployment options") ``` # All removed in v2.0.2 (moved to sandbox only) --node.deployAztecContracts ($DEPLOY_AZTEC_CONTRACTS) --node.deployAztecContractsSalt ($DEPLOY_AZTEC_CONTRACTS_SALT) --node.assumeProvenThroughBlockNumber ($ASSUME_PROVEN_THROUGH_BLOCK_NUMBER) --node.publisherPrivateKey ($L1_PRIVATE_KEY) ``` **Migration**: Contract deployment now sandbox-only via `--sandbox.deployAztecContractsSalt`. For production, deploy contracts separately. ### Other removed settings[​](#other-removed-settings "Direct link to Other removed settings") ``` # Prover coordination --proverNode.proverCoordinationNodeUrls ($PROVER_COORDINATION_NODE_URLS) # Custom forwarder --sequencer.customForwarderContractAddress ($CUSTOM_FORWARDER_CONTRACT_ADDRESS) --proverNode.customForwarderContractAddress ($CUSTOM_FORWARDER_CONTRACT_ADDRESS) # Component-specific settings now global --archiver.viemPollingIntervalMS ($ARCHIVER_VIEM_POLLING_INTERVAL_MS) --sequencer.viemPollingIntervalMS ($L1_READER_VIEM_POLLING_INTERVAL_MS) --blobSink.viemPollingIntervalMS ($L1_READER_VIEM_POLLING_INTERVAL_MS) --proverBroker.viemPollingIntervalMS ($L1_READER_VIEM_POLLING_INTERVAL_MS) --archiver.rollupVersion ($ROLLUP_VERSION) --sequencer.rollupVersion ($ROLLUP_VERSION) --blobSink.rollupVersion ($ROLLUP_VERSION) --proverBroker.rollupVersion ($ROLLUP_VERSION) --pxe.rollupVersion ($ROLLUP_VERSION) --archiver.dataStoreMapSizeKB ($DATA_STORE_MAP_SIZE_KB) --pxe.dataStoreMapSizeKB ($DATA_STORE_MAP_SIZE_KB) --blobSink.dataStoreMapSizeKB ($DATA_STORE_MAP_SIZE_KB) --proverBroker.dataStoreMapSizeKB ($DATA_STORE_MAP_SIZE_KB) --p2pBootstrap.dataStoreMapSizeKB ($DATA_STORE_MAP_SIZE_KB) # Aztec node specific --node.worldStateBlockCheckIntervalMS ($WS_BLOCK_CHECK_INTERVAL_MS) --node.archiverUrl ($ARCHIVER_URL) ``` ## New features[​](#new-features "Direct link to New features") ### P2P transaction collection[​](#p2p-transaction-collection "Direct link to P2P transaction collection") ``` --p2p.txCollectionNodeRpcUrls ($TX_COLLECTION_NODE_RPC_URLS) --p2p.txCollectionFastNodeIntervalMs ($TX_COLLECTION_FAST_NODE_INTERVAL_MS) --p2p.txCollectionFastMaxParallelRequestsPerNode ($TX_COLLECTION_FAST_MAX_PARALLEL_REQUESTS_PER_NODE) --p2p.txCollectionNodeRpcMaxBatchSize ($TX_COLLECTION_NODE_RPC_MAX_BATCH_SIZE) --p2p.txCollectionFastNodesTimeoutBeforeReqRespMs ($TX_COLLECTION_FAST_NODES_TIMEOUT_BEFORE_REQ_RESP_MS) --p2p.txCollectionSlowNodesIntervalMs ($TX_COLLECTION_SLOW_NODES_INTERVAL_MS) --p2p.txCollectionSlowReqRespIntervalMs ($TX_COLLECTION_SLOW_REQ_RESP_INTERVAL_MS) --p2p.txCollectionSlowReqRespTimeoutMs ($TX_COLLECTION_SLOW_REQ_RESP_TIMEOUT_MS) --p2p.txCollectionReconcileIntervalMs ($TX_COLLECTION_RECONCILE_INTERVAL_MS) --p2p.txCollectionDisableSlowDuringFastRequests ($TX_COLLECTION_DISABLE_SLOW_DURING_FAST_REQUESTS) ``` ### P2P security and testing[​](#p2p-security-and-testing "Direct link to P2P security and testing") ``` # Discovery and security --p2p.p2pDiscoveryDisabled ($P2P_DISCOVERY_DISABLED) --p2p.p2pAllowOnlyValidators ($P2P_ALLOW_ONLY_VALIDATORS) --p2p.p2pMaxFailedAuthAttemptsAllowed ($P2P_MAX_AUTH_FAILED_ATTEMPTS_ALLOWED) # Testing features --p2p.dropTransactions ($P2P_DROP_TX) --p2p.dropTransactionsProbability ($P2P_DROP_TX_CHANCE) # Transaction handling --p2p.disableTransactions ($TRANSACTIONS_DISABLED) --p2p.txPoolDeleteTxsAfterReorg ($P2P_TX_POOL_DELETE_TXS_AFTER_REORG) # Preferred peers --p2p.preferredPeers ($P2P_PREFERRED_PEERS) ``` ### Sequencer invalidation controls[​](#sequencer-invalidation-controls "Direct link to Sequencer invalidation controls") ``` --sequencer.attestationPropagationTime ($SEQ_ATTESTATION_PROPAGATION_TIME) --sequencer.secondsBeforeInvalidatingBlockAsCommitteeMember ($SEQ_SECONDS_BEFORE_INVALIDATING_BLOCK_AS_COMMITTEE_MEMBER) --sequencer.secondsBeforeInvalidatingBlockAsNonCommitteeMember ($SEQ_SECONDS_BEFORE_INVALIDATING_BLOCK_AS_NON_COMMITTEE_MEMBER) ``` ### Other new features[​](#other-new-features "Direct link to Other new features") ``` # Archiver - skip validation (testing only) --archiver.skipValidateBlockAttestations # Prover - transaction gathering timeout --proverNode.txGatheringTimeoutMs ($PROVER_NODE_TX_GATHERING_TIMEOUT_MS) ``` ## Changed defaults[​](#changed-defaults "Direct link to Changed defaults") | Flag | Environment Variable | v1.2.1 | v2.0.2 | | ----------------------------------------- | -------------------------------------------- | ----------- | ----------------- | | `--p2p.overallRequestTimeoutMs` | `$P2P_REQRESP_OVERALL_REQUEST_TIMEOUT_MS` | 4000 | **10000** | | `--p2p.individualRequestTimeoutMs` | `$P2P_REQRESP_INDIVIDUAL_REQUEST_TIMEOUT_MS` | 2000 | **10000** | | `--p2p.dialTimeoutMs` | `$P2P_REQRESP_DIAL_TIMEOUT_MS` | 1000 | **5000** | | `--proverAgent.proverAgentPollIntervalMs` | `$PROVER_AGENT_POLL_INTERVAL_MS` | 100 | **1000** | | `--bot.l1ToL2MessageTimeoutSeconds` | `$BOT_L1_TO_L2_TIMEOUT_SECONDS` | 60 | **3600** | | `--bot.recipientEncryptionSecret` | `$BOT_RECIPIENT_ENCRYPTION_SECRET` | \[Redacted] | **0x...cafecafe** | ## Migration checklist[​](#migration-checklist "Direct link to Migration checklist") ### All nodes[​](#all-nodes "Direct link to All nodes") * Update to `--registry-address` only (remove all other contract addresses) * Add `--rollup-version canonical` if needed * Move `--sync-mode` and `--snapshots-url` to global config * Remove component-specific `--*.rollupVersion`, `--*.dataStoreMapSizeKB` * Set global `--data-store-map-size-kb` if needed (default: 134217728 KB) ### Sequencer nodes[​](#sequencer-nodes "Direct link to Sequencer nodes") * Create and configure `--sequencer.keyStoreDirectory` (actually `--proverNode.keyStoreDirectory`) * Change `--sequencer.publisherPrivateKey` → `--sequencer.publisherPrivateKeys` * Update `--sequencer.validatorPrivateKeys` or add `--sequencer.validatorAddresses` * Remove all `--sequencer.gasLimitBufferPercentage` and related L1 settings * Remove `--sequencer.customForwarderContractAddress`, `--sequencer.viemPollingIntervalMS` * Consider using `--sequencer.disabledValidators` for temporary disabling ### Prover nodes[​](#prover-nodes "Direct link to Prover nodes") * Create and configure `--proverNode.keyStoreDirectory` * Change `--proverNode.publisherPrivateKey` → `--proverNode.publisherPrivateKeys` * Move world state settings to global WORLD STATE section * Remove `--proverNode.archiveTreeMapSizeKb` and other tree-specific sizes * Remove `--proverNode.proverCoordinationNodeUrls`, `--proverNode.customForwarderContractAddress` * Set `--proverNode.txGatheringTimeoutMs` if needed ### Archiver nodes[​](#archiver-nodes "Direct link to Archiver nodes") * Remove all `--archiver.gasLimitBufferPercentage` and related L1 settings * Remove `--archiver.ethereumSlotDuration`, `--archiver.aztecSlotDuration`, etc. * Remove `--archiver.viemPollingIntervalMS` ### P2P configuration[​](#p2p-configuration "Direct link to P2P configuration") * Configure `--p2p.txCollectionNodeRpcUrls` if using external nodes * Review `--p2p.p2pAllowOnlyValidators` security settings * Consider using `--p2p.preferredPeers` ### Sandbox/development[​](#sandboxdevelopment "Direct link to Sandbox/development") * Move deployment to `--sandbox.deployAztecContractsSalt` * Configure `--sandbox.l1Mnemonic` if needed * Remove all faucet flags ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Node fails with contract address errors[​](#node-fails-with-contract-address-errors "Direct link to Node fails with contract address errors") **Solution**: Remove all individual contract addresses, keep only `--registry-address`, add `--rollup-version canonical` ### Publisher key not found[​](#publisher-key-not-found "Direct link to Publisher key not found") **Solution**: * Check `--proverNode.keyStoreDirectory` ($KEY\_STORE\_DIRECTORY) is set * Change `--*.publisherPrivateKey` to `--*.publisherPrivateKeys` (plural) * See [Advanced Keystore Guide](/operate/operators/keystore.md) ### World state sync failures (prover)[​](#world-state-sync-failures-prover "Direct link to World state sync failures (prover)") **Solution**: * Move `--proverNode.worldStateDataDirectory` to `--world-state-data-directory` * Remove prover-specific world state settings * Use global `--world-state-db-map-size-kb` ### Slow P2P after upgrade[​](#slow-p2p-after-upgrade "Direct link to Slow P2P after upgrade") **Solution**: * Configure `--p2p.txCollectionNodeRpcUrls` ($TX\_COLLECTION\_NODE\_RPC\_URLS) * Adjust `--p2p.txCollectionFastNodeIntervalMs` ### Validator not attesting[​](#validator-not-attesting "Direct link to Validator not attesting") **Solution**: * Check not in `--sequencer.disabledValidators` list * Verify `--sequencer.validatorPrivateKeys` or `--sequencer.validatorAddresses` * Check keystore permissions ### Missing sync snapshots[​](#missing-sync-snapshots "Direct link to Missing sync snapshots") **Solution**: * Move `--node.syncMode` to `--sync-mode` (global) * Set `--snapshots-url` at global level ## Next steps[​](#next-steps "Direct link to Next steps") * [How to Run a Sequencer Node](/operate/operators/setup/sequencer_management.md) - Updated setup instructions * [Advanced Keystore Usage](/operate/operators/keystore.md) - Keystore configuration * [Ethereum RPC Calls Reference](/operate/operators/reference/ethereum_rpc_reference.md) - Infrastructure requirements * [Aztec Discord](https://discord.gg/aztec) - Upgrade support --- # v4.x (Upgrade from Ignition) ## Overview[​](#overview "Direct link to Overview") **Migration difficulty**: High ## Breaking changes[​](#breaking-changes "Direct link to Breaking changes") ### Node.js upgraded to v24[​](#nodejs-upgraded-to-v24 "Direct link to Node.js upgraded to v24") Node.js minimum version changed from v22 to v24.12.0. ### Bot fee padding configuration renamed[​](#bot-fee-padding-configuration-renamed "Direct link to Bot fee padding configuration renamed") The bot configuration for fee padding has been renamed from "base fee" to "min fee". **v3.x:** ``` --bot.baseFeePadding ($BOT_BASE_FEE_PADDING) ``` **v4.0.0:** ``` --bot.minFeePadding ($BOT_MIN_FEE_PADDING) ``` **Migration**: Update your configuration to use the new flag name and environment variable. ### L2Tips API restructured with checkpoint information[​](#l2tips-api-restructured-with-checkpoint-information "Direct link to L2Tips API restructured with checkpoint information") The `getL2Tips()` RPC endpoint now returns a restructured response with additional checkpoint tracking. **v3.x response:** ``` { "latest": { "number": 100, "hash": "0x..." }, "proven": { "number": 98, "hash": "0x..." }, "finalized": { "number": 95, "hash": "0x..." } } ``` **v4.0.0 response:** ``` { "proposed": { "number": 100, "hash": "0x..." }, "checkpointed": { "block": { "number": 99, "hash": "0x..." }, "checkpoint": { "number": 10, "hash": "0x..." } }, "proven": { "block": { "number": 98, "hash": "0x..." }, "checkpoint": { "number": 9, "hash": "0x..." } }, "finalized": { "block": { "number": 95, "hash": "0x..." }, "checkpoint": { "number": 8, "hash": "0x..." } } } ``` **Migration**: * Replace `tips.latest` with `tips.proposed` * For `checkpointed`, `proven`, and `finalized` tips, access block info via `.block` (e.g., `tips.proven.block.number`) ### Block gas limits reworked[​](#block-gas-limits-reworked "Direct link to Block gas limits reworked") The byte-based block size limit has been removed and replaced with field-based blob limits and automatic gas budget computation from L1 rollup limits. **Removed:** ``` --maxBlockSizeInBytes ($SEQ_MAX_BLOCK_SIZE_IN_BYTES) ``` **Changed to optional (now auto-computed from L1 if not set):** ``` --maxL2BlockGas ($SEQ_MAX_L2_BLOCK_GAS) --maxDABlockGas ($SEQ_MAX_DA_BLOCK_GAS) ``` **New (proposer):** ``` --perBlockAllocationMultiplier ($SEQ_PER_BLOCK_ALLOCATION_MULTIPLIER) --maxTxsPerCheckpoint ($SEQ_MAX_TX_PER_CHECKPOINT) ``` **New (validator):** ``` --validateMaxL2BlockGas ($VALIDATOR_MAX_L2_BLOCK_GAS) --validateMaxDABlockGas ($VALIDATOR_MAX_DA_BLOCK_GAS) --validateMaxTxsPerBlock ($VALIDATOR_MAX_TX_PER_BLOCK) --validateMaxTxsPerCheckpoint ($VALIDATOR_MAX_TX_PER_CHECKPOINT) ``` **Migration**: Remove `SEQ_MAX_BLOCK_SIZE_IN_BYTES` from your configuration. Per-block L2 and DA gas budgets are now derived automatically as `(checkpointLimit / maxBlocks) * multiplier`, where the multiplier defaults to 2. You can still override `SEQ_MAX_L2_BLOCK_GAS` and `SEQ_MAX_DA_BLOCK_GAS` explicitly, but they will be capped at the checkpoint-level limits. Validators can now set independent per-block and per-checkpoint limits via the `VALIDATOR_` env vars; when not set, only checkpoint-level protocol limits are enforced. ### Setup phase allow list requires function selectors[​](#setup-phase-allow-list-requires-function-selectors "Direct link to Setup phase allow list requires function selectors") The transaction setup phase allow list now enforces function selectors, restricting which specific functions can run during setup on whitelisted contracts. Previously, any public function on a whitelisted contract or class was permitted. The semantics of the environment variable `TX_PUBLIC_SETUP_ALLOWLIST` have changed: **v3.x:** ``` --txPublicSetupAllowList ($TX_PUBLIC_SETUP_ALLOWLIST) ``` The variable fully **replaced** the hardcoded defaults. Format allowed entries without selectors: `I:address`, `C:classId`. **v4.0.0:** ``` --txPublicSetupAllowListExtend ($TX_PUBLIC_SETUP_ALLOWLIST) ``` The variable now **extends** the hardcoded defaults (which are always present). Selectors are now mandatory. An optional flags segment can be appended for additional validation: ``` I:address:selector[:flags] C:classId:selector[:flags] ``` Where `flags` is a `+`-separated list of: * `os` — `onlySelf`: only allow calls where msg\_sender == contract address * `rn` — `rejectNullMsgSender`: reject calls with a null msg\_sender * `cl=N` — `calldataLength`: enforce exact calldata length of N fields Example: `C:0xabc:0x1234:os+cl=4` **Migration**: If you were using `TX_PUBLIC_SETUP_ALLOWLIST`, ensure all entries include function selectors. Note the variable now adds to defaults rather than replacing them. If you were not setting this variable, no action is needed — the hardcoded defaults now include the correct selectors automatically. ### Token removed from default setup allowlist[​](#token-removed-from-default-setup-allowlist "Direct link to Token removed from default setup allowlist") Token class-based entries (`_increase_public_balance` and `transfer_in_public`) have been removed from the default public setup allowlist. FPC-based fee payments using custom tokens no longer work out of the box. This change was made because Token class IDs change with aztec-nr releases, making the allowlist impossible to keep up to date with new library releases. In addition, `transfer_in_public` requires complex additional logic to be built into the node to prevent mass transaction invalidation attacks. **FPC-based fee payment with custom tokens won't work on mainnet alpha**. **Migration**: Node operators who need FPC support must manually add Token entries via `TX_PUBLIC_SETUP_ALLOWLIST`. Example: ``` TX_PUBLIC_SETUP_ALLOWLIST="C:::os+cl=3,C:::cl=5" ``` Replace `` with the deployed Token contract class ID and ``/`` with the respective function selectors. Keep in mind that this will only work on local network setups, since even if you as an operator add these entries, other nodes will not have them and will not pick up these transactions. ### Sequencer environment variable renames[​](#sequencer-environment-variable-renames "Direct link to Sequencer environment variable renames") Several sequencer environment variables have been renamed: | Old variable | New variable | | ---------------------------------------- | --------------------------------------------------------------------- | | `SEQ_TX_POLLING_INTERVAL_MS` | `SEQ_POLLING_INTERVAL_MS` | | `SEQ_MAX_L1_TX_INCLUSION_TIME_INTO_SLOT` | `SEQ_L1_PUBLISHING_TIME_ALLOWANCE_IN_SLOT` | | `SEQ_MAX_TX_PER_BLOCK` | `SEQ_MAX_TX_PER_CHECKPOINT` | | `SEQ_MAX_BLOCK_SIZE_IN_BYTES` | Removed (see [Block gas limits reworked](#block-gas-limits-reworked)) | **Migration**: Search your configuration for the old variable names and replace them. The node will not recognize the old names. ### Double signing slashing[​](#double-signing-slashing "Direct link to Double signing slashing") New slashable offenses have been introduced for duplicate proposals and duplicate attestations. Penalty amounts are currently set to 0, but the detection infrastructure is active. If you run redundant sequencer nodes, you **must** enable high-availability signing with PostgreSQL to prevent accidental double signing: ``` VALIDATOR_HA_SIGNING_ENABLED=true VALIDATOR_HA_DATABASE_URL=postgresql://:@:/ VALIDATOR_HA_NODE_ID= ``` Run the database migration before starting your nodes: ``` aztec migrate-ha-db up --database-url ``` **Migration**: If you run a single node, no action is required. If you run redundant nodes for high availability, configure HA signing immediately. See the [High Availability Sequencers](/operate/operators/setup/high_availability_sequencers.md) guide for details. ### Blob-only data publication[​](#blob-only-data-publication "Direct link to Blob-only data publication") Transaction data is now published entirely via EIP-4844 blobs. The calldata fallback has been removed. Your consensus client (e.g., Lighthouse, Prysm) must run as a **supernode** or **semi-supernode** to make blobs available for retrieval. Standard pruning configurations will not retain blobs long enough. You should also configure blob file stores for redundancy: ``` BLOB_FILE_STORE_URLS= BLOB_FILE_STORE_UPLOAD_URL= BLOB_ARCHIVE_API_URL= ``` **Migration**: Ensure your consensus client is configured as a supernode. If you previously relied on calldata for data availability, switch to blob-based retrieval. See the [Blob Storage](/operate/operators/setup/blob_storage.md) guide for configuration details. ### Withdrawal delay increase[​](#withdrawal-delay-increase "Direct link to Withdrawal delay increase") The governance execution delay has increased from 7 days to 30 days. This extends the time required for staker withdrawals from approximately 15 days to approximately 38 days. **Migration**: No configuration changes needed. Be aware that withdrawal processing will take longer after the upgrade. ### Prover architecture change[​](#prover-architecture-change "Direct link to Prover architecture change") The prover now runs as a node subsystem rather than a separate standalone process. Start it alongside your node using the `--prover-node` flag: ``` aztec start --node --prover-node ``` **Migration**: If you were running the prover as a separate process, update your deployment to run it as part of the node with `--prover-node`. ## Removed features[​](#removed-features "Direct link to Removed features") ## New features[​](#new-features "Direct link to New features") ### Initial ETH per fee asset configuration[​](#initial-eth-per-fee-asset-configuration "Direct link to Initial ETH per fee asset configuration") A new environment variable `AZTEC_INITIAL_ETH_PER_FEE_ASSET` has been added to configure the initial exchange rate between ETH and the fee asset (AZTEC) at contract deployment. This value uses 1e12 precision. **Default**: `10000000` (0.00001 ETH per AZTEC) **Configuration:** ``` --initialEthPerFeeAsset ($AZTEC_INITIAL_ETH_PER_FEE_ASSET) ``` This replaces the previous hardcoded default and allows network operators to set the starting price point for the fee asset. ### `reloadKeystore` admin RPC endpoint[​](#reloadkeystore-admin-rpc-endpoint "Direct link to reloadkeystore-admin-rpc-endpoint") Node operators can now update validator attester keys, coinbase, and fee recipient without restarting the node by calling the new `reloadKeystore` admin RPC endpoint. What is updated on reload: * Validator attester keys (add, remove, or replace) * Coinbase and fee recipient per validator * Publisher-to-validator mapping What is NOT updated (requires restart): * L1 publisher signers * Prover keys * HA signer connections New validators must use a publisher key already initialized at startup. Reload is rejected with a clear error if validation fails. ### Admin API key authentication[​](#admin-api-key-authentication "Direct link to Admin API key authentication") The admin JSON-RPC endpoint now supports auto-generated API key authentication. **Behavior:** * A cryptographically secure API key is auto-generated at first startup and displayed once via stdout * Only the SHA-256 hash is persisted to `/admin/api_key_hash` * The key is reused across restarts when `--data-directory` is set * Supports both `x-api-key` and `Authorization: Bearer ` headers * Health check endpoint (`GET /status`) is excluded from auth (for k8s probes) **Configuration:** ``` --admin-api-key-hash ($AZTEC_ADMIN_API_KEY_HASH) # Use a pre-generated SHA-256 key hash --disable-admin-api-key ($AZTEC_DISABLE_ADMIN_API_KEY) # Disable auth entirely --reset-admin-api-key ($AZTEC_RESET_ADMIN_API_KEY) # Force key regeneration ``` **Helm charts**: Admin API key auth is disabled by default (`disableAdminApiKey: true`). Set to `false` in production values to enable. **Migration**: No action required — auth is opt-out. To enable, ensure `--disable-admin-api-key` is not set and note the key printed at startup. ### Transaction pool error codes for RPC callers[​](#transaction-pool-error-codes-for-rpc-callers "Direct link to Transaction pool error codes for RPC callers") Transaction submission via RPC now returns structured rejection codes when a transaction is rejected by the mempool: * `LOW_PRIORITY_FEE` — tx priority fee is too low * `INSUFFICIENT_FEE_PAYER_BALANCE` — fee payer doesn't have enough balance * `NULLIFIER_CONFLICT` — conflicting nullifier already in pool **Impact**: Improved developer experience — callers can now programmatically handle specific rejection reasons. ### RPC transaction replacement price bump[​](#rpc-transaction-replacement-price-bump "Direct link to RPC transaction replacement price bump") Transactions submitted via RPC that clash on nullifiers with existing pool transactions must now pay at least X% more in priority fee to replace them. The same bump applies when the pool is full and the incoming tx needs to evict the lowest-priority tx. P2P gossip behavior is unchanged. **Configuration:** ``` P2P_RPC_PRICE_BUMP_PERCENTAGE=10 # default: 10 (percent) ``` Set to `0` to disable the percentage-based bump (still requires strictly higher fee). ### Validator-specific block limits[​](#validator-specific-block-limits "Direct link to Validator-specific block limits") Validators can now enforce per-block and per-checkpoint limits independently from the sequencer (proposer) limits. This allows operators to accept proposals that exceed their own proposer settings, or to reject proposals that are too large even if the proposer's limits allow them. **Configuration:** ``` VALIDATOR_MAX_L2_BLOCK_GAS= # Max L2 gas per block for validation VALIDATOR_MAX_DA_BLOCK_GAS= # Max DA gas per block for validation VALIDATOR_MAX_TX_PER_BLOCK= # Max txs per block for validation VALIDATOR_MAX_TX_PER_CHECKPOINT= # Max txs per checkpoint for validation ``` When not set, no per-block limit is enforced for that dimension — only checkpoint-level protocol limits apply. These do not fall back to the `SEQ_` values. ### Setup allow list extendable via network config[​](#setup-allow-list-extendable-via-network-config "Direct link to Setup allow list extendable via network config") The setup phase allow list can now be extended via the network configuration JSON (`txPublicSetupAllowListExtend` field). This allows network operators to distribute additional allowed setup functions to all nodes without requiring code changes. The local environment variable takes precedence over the network-json value. ## Changed defaults[​](#changed-defaults "Direct link to Changed defaults") ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ## Next steps[​](#next-steps "Direct link to Next steps") * [How to Run a Sequencer Node](/operate/operators/setup/sequencer_management.md) - Updated setup instructions * [Advanced Keystore Usage](/operate/operators/keystore.md) - Keystore configuration * [Ethereum RPC Calls Reference](/operate/operators/reference/ethereum_rpc_reference.md) - Infrastructure requirements * [Aztec Discord](https://discord.gg/aztec) - Upgrade support --- # v4.2.0 ## Overview[​](#overview "Direct link to Overview") **Migration difficulty**: Low. No breaking changes; this release ships blob retrieval improvements and a couple of related configuration knobs. ## Breaking changes[​](#breaking-changes "Direct link to Breaking changes") ## New features[​](#new-features "Direct link to New features") ### Blob retrieval improvements[​](#blob-retrieval-improvements "Direct link to Blob retrieval improvements") Blob retrieval now uses a unified retry loop that alternates between consensus clients and file stores, replacing the previous multi-phase approach. This reduces retrieval latency from \~12s to \~1.5-3s when blobs aren't immediately available in file stores. Non-supernode consensus hosts are automatically detected at startup and skipped during blob fetching, avoiding wasted requests. **Configuration:** ``` BLOB_PREFER_FILESTORES=false # Try file stores before consensus (default: false) BLOB_FILE_STORE_TIMEOUT_MS=10000 # HTTP timeout for blob file store requests in ms (default: 10000) ``` Set `BLOB_PREFER_FILESTORES=true` if your file stores are faster or more reliable than your consensus clients. ## Changed defaults[​](#changed-defaults "Direct link to Changed defaults") ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") --- # v4.3 ## v4.3.1[​](#v431 "Direct link to v4.3.1") **Migration difficulty:** none. v4.3.1 is a bug-fix release with no configuration changes and no operator action required beyond upgrading. * **Tip-store finalize guarded against deleting live tips** ([#23505](https://github.com/AztecProtocol/aztec-packages/pull/23505), backport of [#23295](https://github.com/AztecProtocol/aztec-packages/pull/23295)): fixes a bug where finalization could delete data still referenced by a live tip, locking the node's block stream into a persistent error loop that required wiping the archiver database to recover. * **Prover waits for the previous epoch to be proven** ([#23457](https://github.com/AztecProtocol/aztec-packages/pull/23457)): prover nodes now wait for the previous epoch's proof to land before submitting a proof that builds on it, instead of failing the submission. * **Released contract artifacts stamped with the release version** ([#23851](https://github.com/AztecProtocol/aztec-packages/pull/23851), backport of [#23470](https://github.com/AztecProtocol/aztec-packages/pull/23470)): released `noir-contracts` JSON artifacts no longer carry a `"dev"` aztec version. ## v4.3.0[​](#v430 "Direct link to v4.3.0") **Migration difficulty:** low. The only change likely to require operator action is the renaming of bundled binaries under an `aztec-` prefix. ### Breaking changes[​](#breaking-changes "Direct link to Breaking changes") #### Bundled binaries renamed under `aztec-` prefix on `PATH`[​](#bundled-binaries-renamed-under-aztec--prefix-on-path "Direct link to bundled-binaries-renamed-under-aztec--prefix-on-path") `aztec-up` previously placed bundled tooling directly into `$HOME/.aztec/current/bin` under bare names (`forge`, `cast`, `nargo`, `bb`, `pxe`, `txe`, `validator-client`, `blob-client`, ...). Operators with their own `forge` or `nargo` install elsewhere on `PATH` could end up silently using the wrong binary depending on resolution order, and the bundle could shadow unrelated projects. In v4.3.0, every bundled binary is exposed **only** under its `aztec-`-prefixed name. Bare names are no longer placed on `PATH` by `aztec-up`. | Was on `PATH` | Now | | ------------------ | ------------------------ | | `forge` | `aztec-forge` | | `cast` | `aztec-cast` | | `anvil` | `aztec-anvil` | | `chisel` | `aztec-chisel` | | `nargo` | `aztec-nargo` | | `noir-profiler` | `aztec-noir-profiler` | | `bb` | `aztec-bb` | | `bb-cli` | `aztec-bb-cli` | | `pxe` | `aztec-pxe` | | `txe` | `aztec-txe` | | `validator-client` | `aztec-validator-client` | | `blob-client` | `aztec-blob-client` | `aztec`, `aztec-wallet`, and `aztec-up` keep their existing names. **Operator action:** any operator scripts, systemd units, dockerfiles, or run-books that invoke `forge`, `cast`, `anvil`, `nargo`, `bb`, `pxe`, `txe`, `validator-client`, or `blob-client` directly from the bundle path must switch to the `aztec-` prefixed names. References to `aztec`, `aztec-wallet`, and `aztec-up` are unaffected. References: [#22902](https://github.com/AztecProtocol/aztec-packages/pull/22902), [#22709](https://github.com/AztecProtocol/aztec-packages/pull/22709). ### Other notable changes[​](#other-notable-changes "Direct link to Other notable changes") These items do not require operator action but are called out in the [v4.3.0 release notes](https://github.com/AztecProtocol/aztec-packages/releases/tag/v4.3.0): * **Sequencer signs the last block before archiver sync** ([#22117](https://github.com/AztecProtocol/aztec-packages/pull/22117)) — correctness and ordering improvement around block signing. * **Release image stamps `stdlib/package.json` with the release version** ([#23393](https://github.com/AztecProtocol/aztec-packages/pull/23393)) — fixes a published-artifact metadata mismatch that affected downstream consumers of the stdlib package. * **macOS `aztec-up` install ergonomics** ([#23310](https://github.com/AztecProtocol/aztec-packages/pull/23310), [#23335](https://github.com/AztecProtocol/aztec-packages/pull/23335)) — `aztec-up` now falls back to no-timeout when `/usr/bin/timeout` is absent, and `add_crate.sh` uses `perl -i` instead of GNU-specific `sed -i`. Affects operators who bootstrap nodes via `aztec-up` on macOS. ### Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") If a previously-working operator command suddenly errors with `command not found: forge` (or `cast`, `nargo`, `bb`, etc.) after upgrading to v4.3.0, switch the call site to the `aztec-` prefixed binary or install the standalone tool separately. --- # Cli Reference **Configuration notes:** * The environment variable name corresponding to each flag is shown as $ENV\_VAR on the right hand side. * If two subsystems can contain the same configuration option, only one needs to be provided. For example, `--archiver.blobSinkUrl` and `--sequencer.blobSinkUrl` point to the same value. ``` MISC --network ($NETWORK) Network to run Aztec on --enable-version-check (default: true) ($ENABLE_VERSION_CHECK) Check if the node is running the latest version and is following the latest rollup --sync-mode (default: snapshot) ($SYNC_MODE) Set sync mode to `full` to always sync via L1, `snapshot` to download a snapshot if there is no local data, `force-snapshot` to download even if there is local data. --snapshots-urls (default: ) ($SYNC_SNAPSHOTS_URLS) Base URLs for snapshots index, comma-separated. --fisherman-mode ($FISHERMAN_MODE) Whether to run in fisherman mode. LOCAL_NETWORK --local-network Starts Aztec Local Network --local-network.l1Mnemonic (default: test test test test test test test test test test test junk)($MNEMONIC) Mnemonic for L1 accounts. Will be used --local-network.testAccounts (default: true) ($TEST_ACCOUNTS) Deploy test accounts on local network start API --port (default: 8080) ($AZTEC_PORT) Port to run the Aztec Services on --admin-port (default: 8880) ($AZTEC_ADMIN_PORT) Port to run admin APIs of Aztec Services on --admin-api-key-hash ($AZTEC_ADMIN_API_KEY_HASH) SHA-256 hex hash of a pre-generated admin API key. When set, the node uses this hash for authentication instead of auto-generating a key. --disable-admin-api-key ($AZTEC_DISABLE_ADMIN_API_KEY) Disable API key authentication on the admin RPC endpoint. By default, a key is auto-generated, displayed once, and its hash is persisted. --reset-admin-api-key ($AZTEC_RESET_ADMIN_API_KEY) Force-generate a new admin API key, replacing any previously persisted key hash. The new key is displayed once at startup. --node-debug ($AZTEC_NODE_DEBUG) Expose debug endpoints (e.g. mineBlock) on the main RPC port --api-prefix ($API_PREFIX) Prefix for API routes on any service that is started --rpcMaxBatchSize (default: 100) ($RPC_MAX_BATCH_SIZE) Maximum allowed batch size for JSON RPC batch requests. --rpcMaxBodySize (default: 1mb) ($RPC_MAX_BODY_SIZE) Maximum allowed batch size for JSON RPC batch requests. ETHEREUM --l1-chain-id ($L1_CHAIN_ID) The chain ID of the ethereum host. --l1-rpc-urls ($ETHEREUM_HOSTS) List of URLs of Ethereum RPC nodes that services will connect to (comma separated). --l1-consensus-host-urls ($L1_CONSENSUS_HOST_URLS) List of URLs of the Ethereum consensus nodes that services will connect to (comma separated) --l1-consensus-host-api-keys ($L1_CONSENSUS_HOST_API_KEYS) List of API keys for the corresponding L1 consensus clients, if needed. Added to the end of the corresponding URL as "?key=" unless a header is defined --l1-consensus-host-api-key-headers ($L1_CONSENSUS_HOST_API_KEY_HEADERS) List of header names for the corresponding L1 consensus client API keys, if needed. Added to the corresponding request as ": " L1 CONTRACTS --registry-address ($REGISTRY_CONTRACT_ADDRESS) The deployed L1 registry contract address. --rollup-version ($ROLLUP_VERSION) The version of the rollup. STORAGE --data-directory ($DATA_DIRECTORY) Optional dir to store data. If omitted will store in memory. --data-store-map-size-kb (default: 134217728) ($DATA_STORE_MAP_SIZE_KB) The maximum possible size of a data store DB in KB. Can be overridden by component-specific options. WORLD STATE --world-state-data-directory ($WS_DATA_DIRECTORY) Optional directory for the world state database --world-state-db-map-size-kb ($WS_DB_MAP_SIZE_KB) The maximum possible size of the world state DB in KB. Overwrites the general dataStoreMapSizeKb. --world-state-checkpoint-history (default: 64) ($WS_NUM_HISTORIC_CHECKPOINTS) The number of historic checkpoints worth of blocks to maintain. Values less than 1 mean all history is maintained AZTEC NODE --node Starts Aztec Node with options ARCHIVER --archiver Starts Aztec Archiver with options --archiver.blobSinkMapSizeKb ($BLOB_SINK_MAP_SIZE_KB) The maximum possible size of the blob sink DB in KB. Overwrites the general dataStoreMapSizeKb. --archiver.blobAllowEmptySources ($BLOB_ALLOW_EMPTY_SOURCES) Whether to allow having no blob sources configured during startup --archiver.blobFileStoreUrls ($BLOB_FILE_STORE_URLS) URLs for filestore blob archive, comma-separated. Tried in order until blobs are found. --archiver.blobFileStoreUploadUrl ($BLOB_FILE_STORE_UPLOAD_URL) URL for uploading blobs to filestore (s3://, gs://, file://) --archiver.blobHealthcheckUploadIntervalMinutes ($BLOB_HEALTHCHECK_UPLOAD_INTERVAL_MINUTES) Interval in minutes for uploading healthcheck file to file store (default: 60 = 1 hour) --archiver.archiveApiUrl ($BLOB_ARCHIVE_API_URL) The URL of the archive API --archiver.archiverPollingIntervalMS (default: 500) ($ARCHIVER_POLLING_INTERVAL_MS) The polling interval in ms for retrieving new L2 blocks and encrypted logs. --archiver.archiverBatchSize (default: 100) ($ARCHIVER_BATCH_SIZE) The number of L2 blocks the archiver will attempt to download at a time. --archiver.maxLogs (default: 1000) ($ARCHIVER_MAX_LOGS) The max number of logs that can be obtained in 1 "getPublicLogs" call. --archiver.archiverStoreMapSizeKb ($ARCHIVER_STORE_MAP_SIZE_KB) The maximum possible size of the archiver DB in KB. Overwrites the general dataStoreMapSizeKb. --archiver.skipValidateCheckpointAttestations Skip validating checkpoint attestations (for testing purposes only) --archiver.maxAllowedEthClientDriftSeconds (default: 300) ($MAX_ALLOWED_ETH_CLIENT_DRIFT_SECONDS) Maximum allowed drift in seconds between the Ethereum client and current time. --archiver.ethereumAllowNoDebugHosts (default: true) ($ETHEREUM_ALLOW_NO_DEBUG_HOSTS) Whether to allow starting the archiver without debug/trace method support on Ethereum hosts SEQUENCER --sequencer Starts Aztec Sequencer with options --sequencer.validatorPrivateKeys (default: [Redacted]) ($VALIDATOR_PRIVATE_KEYS) List of private keys of the validators participating in attestation duties --sequencer.validatorAddresses (default: ) ($VALIDATOR_ADDRESSES) List of addresses of the validators to use with remote signers --sequencer.disableValidator ($VALIDATOR_DISABLED) Do not run the validator --sequencer.disabledValidators (default: ) Temporarily disable these specific validator addresses --sequencer.attestationPollingIntervalMs (default: 200) ($VALIDATOR_ATTESTATIONS_POLLING_INTERVAL_MS) Interval between polling for new attestations --sequencer.validatorReexecute (default: true) ($VALIDATOR_REEXECUTE) Re-execute transactions before attesting --sequencer.alwaysReexecuteBlockProposals (default: true) Whether to always reexecute block proposals, even for non-validator nodes (useful for monitoring network status). --sequencer.skipCheckpointProposalValidation Skip checkpoint proposal validation and always attest (default: false) --sequencer.skipPushProposedBlocksToArchiver Skip pushing proposed blocks to archiver (default: true) --sequencer.attestToEquivocatedProposals Agree to attest to equivocated checkpoint proposals (for testing purposes only) --sequencer.validateMaxL2BlockGas ($VALIDATOR_MAX_L2_BLOCK_GAS) Maximum L2 block gas for validation. Proposals exceeding this limit are rejected. --sequencer.validateMaxDABlockGas ($VALIDATOR_MAX_DA_BLOCK_GAS) Maximum DA block gas for validation. Proposals exceeding this limit are rejected. --sequencer.validateMaxTxsPerBlock ($VALIDATOR_MAX_TX_PER_BLOCK) Maximum transactions per block for validation. Proposals exceeding this limit are rejected. --sequencer.validateMaxTxsPerCheckpoint ($VALIDATOR_MAX_TX_PER_CHECKPOINT) Maximum transactions per checkpoint for validation. Proposals exceeding this limit are rejected. --sequencer.haSigningEnabled ($VALIDATOR_HA_SIGNING_ENABLED) Whether HA signing / slashing protection is enabled --sequencer.nodeId ($VALIDATOR_HA_NODE_ID) The unique identifier for this node --sequencer.pollingIntervalMs (default: 100) ($VALIDATOR_HA_POLLING_INTERVAL_MS) The number of ms to wait between polls when a duty is being signed --sequencer.signingTimeoutMs (default: 3000) ($VALIDATOR_HA_SIGNING_TIMEOUT_MS) The maximum time to wait for a duty being signed to complete --sequencer.maxStuckDutiesAgeMs ($VALIDATOR_HA_MAX_STUCK_DUTIES_AGE_MS) The maximum age of a stuck duty in ms (defaults to 2x Aztec slot duration) --sequencer.cleanupOldDutiesAfterHours ($VALIDATOR_HA_OLD_DUTIES_MAX_AGE_H) Optional: clean up old duties after this many hours (disabled if not set) --sequencer.databaseUrl ($VALIDATOR_HA_DATABASE_URL) PostgreSQL connection string for validator HA signer (format: postgresql://user:password@host:port/database) --sequencer.poolMaxCount (default: 10) ($VALIDATOR_HA_POOL_MAX) Maximum number of clients in the pool --sequencer.poolMinCount ($VALIDATOR_HA_POOL_MIN) Minimum number of clients in the pool --sequencer.poolIdleTimeoutMs (default: 10000) ($VALIDATOR_HA_POOL_IDLE_TIMEOUT_MS) Idle timeout in milliseconds --sequencer.poolConnectionTimeoutMs ($VALIDATOR_HA_POOL_CONNECTION_TIMEOUT_MS) Connection timeout in milliseconds (0 means no timeout) --sequencer.sequencerPollingIntervalMS (default: 500) ($SEQ_POLLING_INTERVAL_MS) The number of ms to wait between polling for checking to build on the next slot. --sequencer.maxTxsPerCheckpoint ($SEQ_MAX_TX_PER_CHECKPOINT) The maximum number of txs across all blocks in a checkpoint. --sequencer.minTxsPerBlock (default: 1) ($SEQ_MIN_TX_PER_BLOCK) The minimum number of txs to include in a block. --sequencer.minValidTxsPerBlock The minimum number of valid txs (after execution) to include in a block. If not set, falls back to minTxsPerBlock. --sequencer.publishTxsWithProposals ($SEQ_PUBLISH_TXS_WITH_PROPOSALS) Whether to publish txs with proposals. --sequencer.maxL2BlockGas ($SEQ_MAX_L2_BLOCK_GAS) The maximum L2 block gas. --sequencer.maxDABlockGas ($SEQ_MAX_DA_BLOCK_GAS) The maximum DA block gas. --sequencer.perBlockAllocationMultiplier (default: 1.2) ($SEQ_PER_BLOCK_ALLOCATION_MULTIPLIER) Per-block gas budget multiplier for both L2 and DA gas. Budget per block is (checkpointLimit / maxBlocks) * multiplier. Values greater than one allow early blocks to use more than their even share, relying on checkpoint-level capping for later blocks. --sequencer.redistributeCheckpointBudget (default: true) ($SEQ_REDISTRIBUTE_CHECKPOINT_BUDGET) Redistribute remaining checkpoint budget evenly across remaining blocks instead of allowing a single block to consume the entire remaining budget. --sequencer.coinbase ($COINBASE) Recipient of block reward. --sequencer.feeRecipient ($FEE_RECIPIENT) Address to receive fees. --sequencer.acvmWorkingDirectory ($ACVM_WORKING_DIRECTORY) The working directory to use for simulation/proving --sequencer.acvmBinaryPath ($ACVM_BINARY_PATH) The path to the ACVM binary --sequencer.enforceTimeTable (default: true) ($SEQ_ENFORCE_TIME_TABLE) Whether to enforce the time table when building blocks --sequencer.governanceProposerPayload ($GOVERNANCE_PROPOSER_PAYLOAD_ADDRESS) The address of the payload for the governanceProposer --sequencer.l1PublishingTime ($SEQ_L1_PUBLISHING_TIME_ALLOWANCE_IN_SLOT) How much time (in seconds) we allow in the slot for publishing the L1 tx (defaults to 1 L1 slot). --sequencer.attestationPropagationTime (default: 2) ($SEQ_ATTESTATION_PROPAGATION_TIME) How many seconds it takes for proposals and attestations to travel across the p2p layer (one-way) --sequencer.secondsBeforeInvalidatingBlockAsCommitteeMember (default: 144) ($SEQ_SECONDS_BEFORE_INVALIDATING_BLOCK_AS_COMMITTEE_MEMBER) How many seconds to wait before trying to invalidate a block from the pending chain as a committee member (zero to never invalidate). The next proposer is expected to invalidate, so the committee acts as a fallback. --sequencer.secondsBeforeInvalidatingBlockAsNonCommitteeMember (default: 432) ($SEQ_SECONDS_BEFORE_INVALIDATING_BLOCK_AS_NON_COMMITTEE_MEMBER) How many seconds to wait before trying to invalidate a block from the pending chain as a non-committee member (zero to never invalidate). The next proposer is expected to invalidate, then the committee, so other sequencers act as a fallback. --sequencer.broadcastInvalidBlockProposal Broadcast invalid block proposals with corrupted state (for testing only) --sequencer.injectFakeAttestation Inject a fake attestation (for testing only) --sequencer.injectHighSValueAttestation Inject a malleable attestation with a high-s value (for testing only) --sequencer.injectUnrecoverableSignatureAttestation Inject an attestation with an unrecoverable signature (for testing only) --sequencer.shuffleAttestationOrdering Shuffle attestation ordering to create invalid ordering (for testing only) --sequencer.blockDurationMs ($SEQ_BLOCK_DURATION_MS) Duration per block in milliseconds when building multiple blocks per slot. If undefined (default), builds a single block per slot using the full slot duration. --sequencer.expectedBlockProposalsPerSlot ($SEQ_EXPECTED_BLOCK_PROPOSALS_PER_SLOT) Expected number of block proposals per slot for P2P peer scoring. 0 (default) disables block proposal scoring. Set to a positive value to enable. --sequencer.maxTxsPerBlock ($SEQ_MAX_TX_PER_BLOCK) The maximum number of txs to include in a block. --sequencer.buildCheckpointIfEmpty ($SEQ_BUILD_CHECKPOINT_IF_EMPTY) Have sequencer build and publish an empty checkpoint if there are no txs --sequencer.minBlocksForCheckpoint Minimum number of blocks required for a checkpoint proposal (test only) --sequencer.skipPublishingCheckpointsPercent ($SEQ_SKIP_CHECKPOINT_PUBLISH_PERCENT) Percent probability (0 - 100) of sequencer skipping checkpoint publishing (testing only) --sequencer.txPublicSetupAllowListExtend ($TX_PUBLIC_SETUP_ALLOWLIST) Additional entries to extend the default setup allow list. Format: I:address:selector[:flags],C:classId:selector[:flags]. Flags: os (onlySelf), rn (rejectNullMsgSender), cl=N (calldataLength), joined with +. --sequencer.keyStoreDirectory ($KEY_STORE_DIRECTORY) Location of key store directory --sequencer.sequencerPublisherPrivateKeys (default: ) ($SEQ_PUBLISHER_PRIVATE_KEYS) The private keys to be used by the sequencer publisher. --sequencer.sequencerPublisherAddresses (default: ) ($SEQ_PUBLISHER_ADDRESSES) The addresses of the publishers to use with remote signers --sequencer.blobAllowEmptySources ($BLOB_ALLOW_EMPTY_SOURCES) Whether to allow having no blob sources configured during startup --sequencer.blobFileStoreUrls ($BLOB_FILE_STORE_URLS) URLs for filestore blob archive, comma-separated. Tried in order until blobs are found. --sequencer.blobFileStoreUploadUrl ($BLOB_FILE_STORE_UPLOAD_URL) URL for uploading blobs to filestore (s3://, gs://, file://) --sequencer.blobHealthcheckUploadIntervalMinutes ($BLOB_HEALTHCHECK_UPLOAD_INTERVAL_MINUTES) Interval in minutes for uploading healthcheck file to file store (default: 60 = 1 hour) --sequencer.archiveApiUrl ($BLOB_ARCHIVE_API_URL) The URL of the archive API --sequencer.sequencerPublisherAllowInvalidStates (default: true) ($SEQ_PUBLISHER_ALLOW_INVALID_STATES) True to use publishers in invalid states (timed out, cancelled, etc) if no other is available --sequencer.sequencerPublisherForwarderAddress ($SEQ_PUBLISHER_FORWARDER_ADDRESS) Address of the forwarder contract to wrap all L1 transactions through (for testing purposes only) PROVER NODE --prover-node Starts Aztec Prover Node with options --proverNode.keyStoreDirectory ($KEY_STORE_DIRECTORY) Location of key store directory --proverNode.acvmWorkingDirectory ($ACVM_WORKING_DIRECTORY) The working directory to use for simulation/proving --proverNode.acvmBinaryPath ($ACVM_BINARY_PATH) The path to the ACVM binary --proverNode.bbWorkingDirectory ($BB_WORKING_DIRECTORY) The working directory to use for proving --proverNode.bbBinaryPath ($BB_BINARY_PATH) The path to the bb binary --proverNode.bbSkipCleanup ($BB_SKIP_CLEANUP) Whether to skip cleanup of bb temporary files --proverNode.numConcurrentIVCVerifiers (default: 8) ($BB_NUM_IVC_VERIFIERS) Max number of chonk verifiers to run concurrently --proverNode.bbIVCConcurrency (default: 1) ($BB_IVC_CONCURRENCY) Number of threads to use for IVC verification --proverNode.nodeUrl ($AZTEC_NODE_URL) The URL to the Aztec node to take proving jobs from --proverNode.proverId ($PROVER_ID) Hex value that identifies the prover. Defaults to the address used for submitting proofs if not set. --proverNode.failedProofStore ($PROVER_FAILED_PROOF_STORE) Store for failed proof inputs. Google cloud storage is only supported at the moment. Set this value as gs://bucket-name/path/to/store. --proverNode.enqueueConcurrency (default: 50) ($PROVER_ENQUEUE_CONCURRENCY) Max concurrent jobs the orchestrator serializes and enqueues to the broker. --proverNode.blobSinkMapSizeKb ($BLOB_SINK_MAP_SIZE_KB) The maximum possible size of the blob sink DB in KB. Overwrites the general dataStoreMapSizeKb. --proverNode.blobAllowEmptySources ($BLOB_ALLOW_EMPTY_SOURCES) Whether to allow having no blob sources configured during startup --proverNode.blobFileStoreUrls ($BLOB_FILE_STORE_URLS) URLs for filestore blob archive, comma-separated. Tried in order until blobs are found. --proverNode.blobFileStoreUploadUrl ($BLOB_FILE_STORE_UPLOAD_URL) URL for uploading blobs to filestore (s3://, gs://, file://) --proverNode.blobHealthcheckUploadIntervalMinutes ($BLOB_HEALTHCHECK_UPLOAD_INTERVAL_MINUTES) Interval in minutes for uploading healthcheck file to file store (default: 60 = 1 hour) --proverNode.archiveApiUrl ($BLOB_ARCHIVE_API_URL) The URL of the archive API --proverNode.proverPublisherAllowInvalidStates (default: true) ($PROVER_PUBLISHER_ALLOW_INVALID_STATES) True to use publishers in invalid states (timed out, cancelled, etc) if no other is available --proverNode.proverPublisherForwarderAddress ($PROVER_PUBLISHER_FORWARDER_ADDRESS) Address of the forwarder contract to wrap all L1 transactions through (for testing purposes only) --proverNode.proverPublisherPrivateKeys (default: ) ($PROVER_PUBLISHER_PRIVATE_KEYS) The private keys to be used by the prover publisher. --proverNode.proverPublisherAddresses (default: ) ($PROVER_PUBLISHER_ADDRESSES) The addresses of the publishers to use with remote signers --proverNode.proverNodeMaxPendingJobs (default: 10) ($PROVER_NODE_MAX_PENDING_JOBS) The maximum number of pending jobs for the prover node --proverNode.proverNodePollingIntervalMs (default: 1000) ($PROVER_NODE_POLLING_INTERVAL_MS) The interval in milliseconds to poll for new jobs --proverNode.proverNodeMaxParallelBlocksPerEpoch ($PROVER_NODE_MAX_PARALLEL_BLOCKS_PER_EPOCH) The Maximum number of blocks to process in parallel while proving an epoch --proverNode.proverNodeFailedEpochStore ($PROVER_NODE_FAILED_EPOCH_STORE) File store where to upload node state when an epoch fails to be proven --proverNode.proverNodeEpochProvingDelayMs Optional delay in milliseconds to wait before proving a new epoch --proverNode.txGatheringIntervalMs (default: 1000) ($PROVER_NODE_TX_GATHERING_INTERVAL_MS) How often to check that tx data is available --proverNode.txGatheringBatchSize (default: 10) ($PROVER_NODE_TX_GATHERING_BATCH_SIZE) How many transactions to gather from a node in a single request --proverNode.txGatheringMaxParallelRequestsPerNode (default: 100) ($PROVER_NODE_TX_GATHERING_MAX_PARALLEL_REQUESTS_PER_NODE) How many tx requests to make in parallel to each node --proverNode.txGatheringTimeoutMs (default: 120000) ($PROVER_NODE_TX_GATHERING_TIMEOUT_MS) How long to wait for tx data to be available before giving up --proverNode.proverNodeDisableProofPublish ($PROVER_NODE_DISABLE_PROOF_PUBLISH) Whether the prover node skips publishing proofs to L1 --proverNode.web3SignerUrl ($WEB3_SIGNER_URL) URL of the Web3Signer instance PROVER BROKER --prover-broker Starts Aztec proving job broker --proverBroker.proverBrokerJobTimeoutMs (default: 30000) ($PROVER_BROKER_JOB_TIMEOUT_MS) Jobs are retried if not kept alive for this long --proverBroker.proverBrokerPollIntervalMs (default: 1000) ($PROVER_BROKER_POLL_INTERVAL_MS) The interval to check job health status --proverBroker.proverBrokerJobMaxRetries (default: 3) ($PROVER_BROKER_JOB_MAX_RETRIES) If starting a prover broker locally, the max number of retries per proving job --proverBroker.proverBrokerBatchSize (default: 100) ($PROVER_BROKER_BATCH_SIZE) The prover broker writes jobs to disk in batches --proverBroker.proverBrokerBatchIntervalMs (default: 50) ($PROVER_BROKER_BATCH_INTERVAL_MS) How often to flush batches to disk --proverBroker.proverBrokerMaxEpochsToKeepResultsFor (default: 1) ($PROVER_BROKER_MAX_EPOCHS_TO_KEEP_RESULTS_FOR) The maximum number of epochs to keep results for --proverBroker.proverBrokerStoreMapSizeKb ($PROVER_BROKER_STORE_MAP_SIZE_KB) The size of the prover broker's database. Will override the dataStoreMapSizeKb if set. --proverBroker.proverBrokerDebugReplayEnabled ($PROVER_BROKER_DEBUG_REPLAY_ENABLED) Enable debug replay mode for replaying proving jobs from stored inputs PROVER AGENT --prover-agent Starts Aztec Prover Agent with options --proverAgent.proverAgentCount (default: 1) ($PROVER_AGENT_COUNT) Whether this prover has a local prover agent --proverAgent.proverAgentPollIntervalMs (default: 1000) ($PROVER_AGENT_POLL_INTERVAL_MS) The interval agents poll for jobs at --proverAgent.proverAgentProofTypes ($PROVER_AGENT_PROOF_TYPES) The types of proofs the prover agent can generate --proverAgent.proverBrokerUrl ($PROVER_BROKER_HOST) The URL where this agent takes jobs from --proverAgent.realProofs (default: true) ($PROVER_REAL_PROOFS) Whether to construct real proofs --proverAgent.proverTestDelayType (default: fixed) ($PROVER_TEST_DELAY_TYPE) The type of artificial delay to introduce --proverAgent.proverTestDelayMs ($PROVER_TEST_DELAY_MS) Artificial delay to introduce to all operations to the test prover. --proverAgent.proverTestDelayFactor (default: 1) ($PROVER_TEST_DELAY_FACTOR) If using realistic delays, what percentage of realistic times to apply. --proverAgent.proverTestVerificationDelayMs (default: 10) ($PROVER_TEST_VERIFICATION_DELAY_MS) The delay (ms) to inject during fake proof verification --proverAgent.cancelJobsOnStop ($PROVER_CANCEL_JOBS_ON_STOP) Whether to abort pending proving jobs when the orchestrator is cancelled. When false (default), jobs remain in the broker queue and can be reused on restart/reorg. --proverAgent.proofStore ($PROVER_PROOF_STORE) Optional proof input store for the prover P2P SUBSYSTEM --p2p-enabled [value] ($P2P_ENABLED) Enable P2P subsystem --p2p.validateMaxTxsPerBlock ($VALIDATOR_MAX_TX_PER_BLOCK) Maximum transactions per block for validation. Overrides maxTxsPerBlock for gossip validation when set. --p2p.validateMaxTxsPerCheckpoint ($VALIDATOR_MAX_TX_PER_CHECKPOINT) Maximum transactions per checkpoint for validation. Used as fallback for maxTxsPerBlock when that is not set. --p2p.validateMaxL2BlockGas ($VALIDATOR_MAX_L2_BLOCK_GAS) Maximum L2 gas per block for validation. When set, txs exceeding this limit are rejected. --p2p.validateMaxDABlockGas ($VALIDATOR_MAX_DA_BLOCK_GAS) Maximum DA gas per block for validation. When set, txs exceeding this limit are rejected. --p2p.p2pDiscoveryDisabled ($P2P_DISCOVERY_DISABLED) A flag dictating whether the P2P discovery system should be disabled. --p2p.blockCheckIntervalMS (default: 100) ($P2P_BLOCK_CHECK_INTERVAL_MS) The frequency in which to check for new L2 blocks. --p2p.slotCheckIntervalMS (default: 1000) ($P2P_SLOT_CHECK_INTERVAL_MS) The frequency in which to check for new L2 slots. --p2p.debugDisableColocationPenalty ($DEBUG_P2P_DISABLE_COLOCATION_PENALTY) DEBUG: Disable colocation penalty - NEVER set to true in production --p2p.peerCheckIntervalMS (default: 30000) ($P2P_PEER_CHECK_INTERVAL_MS) The frequency in which to check for new peers. --p2p.l2QueueSize (default: 1000) ($P2P_L2_QUEUE_SIZE) Size of queue of L2 blocks to store. --p2p.listenAddress (default: 0.0.0.0) ($P2P_LISTEN_ADDR) The listen address. ipv4 address. --p2p.p2pPort (default: 40400) ($P2P_PORT) The port for the P2P service. Defaults to 40400 --p2p.p2pBroadcastPort ($P2P_BROADCAST_PORT) The port to broadcast the P2P service on (included in the node's ENR). Defaults to P2P_PORT. --p2p.p2pIp ($P2P_IP) The IP address for the P2P service. ipv4 address. --p2p.peerIdPrivateKey ($PEER_ID_PRIVATE_KEY) An optional peer id private key. If blank, will generate a random key. --p2p.peerIdPrivateKeyPath ($PEER_ID_PRIVATE_KEY_PATH) An optional path to store generated peer id private keys. If blank, will default to storing any generated keys in the root of the data directory. --p2p.bootstrapNodes (default: ) ($BOOTSTRAP_NODES) A list of bootstrap peer ENRs to connect to. Separated by commas. --p2p.bootstrapNodeEnrVersionCheck ($P2P_BOOTSTRAP_NODE_ENR_VERSION_CHECK) Whether to check the version of the bootstrap node ENR. --p2p.bootstrapNodesAsFullPeers ($P2P_BOOTSTRAP_NODES_AS_FULL_PEERS) Whether to consider our configured bootnodes as full peers --p2p.maxPeerCount (default: 100) ($P2P_MAX_PEERS) The maximum number of peers to connect to. --p2p.queryForIp ($P2P_QUERY_FOR_IP) If announceUdpAddress or announceTcpAddress are not provided, query for the IP address of the machine. Default is false. --p2p.gossipsubInterval (default: 700) ($P2P_GOSSIPSUB_INTERVAL_MS) The interval of the gossipsub heartbeat to perform maintenance tasks. --p2p.gossipsubD (default: 8) ($P2P_GOSSIPSUB_D) The D parameter for the gossipsub protocol. --p2p.gossipsubDlo (default: 4) ($P2P_GOSSIPSUB_DLO) The Dlo parameter for the gossipsub protocol. --p2p.gossipsubDhi (default: 12) ($P2P_GOSSIPSUB_DHI) The Dhi parameter for the gossipsub protocol. --p2p.gossipsubDLazy (default: 8) ($P2P_GOSSIPSUB_DLAZY) The Dlazy parameter for the gossipsub protocol. --p2p.gossipsubFloodPublish ($P2P_GOSSIPSUB_FLOOD_PUBLISH) Whether to flood publish messages. - For testing purposes only --p2p.gossipsubMcacheLength (default: 6) ($P2P_GOSSIPSUB_MCACHE_LENGTH) The number of gossipsub interval message cache windows to keep. --p2p.gossipsubMcacheGossip (default: 3) ($P2P_GOSSIPSUB_MCACHE_GOSSIP) How many message cache windows to include when gossiping with other peers. --p2p.gossipsubSeenTTL (default: 1200000) ($P2P_GOSSIPSUB_SEEN_TTL) How long to keep message IDs in the seen cache. --p2p.gossipsubTxTopicWeight (default: 1) ($P2P_GOSSIPSUB_TX_TOPIC_WEIGHT) The weight of the tx topic for the gossipsub protocol. --p2p.gossipsubTxInvalidMessageDeliveriesWeight (default: -20) ($P2P_GOSSIPSUB_TX_INVALID_MESSAGE_DELIVERIES_WEIGHT) The weight of the tx invalid message deliveries for the gossipsub protocol. --p2p.gossipsubTxInvalidMessageDeliveriesDecay (default: 0.5) ($P2P_GOSSIPSUB_TX_INVALID_MESSAGE_DELIVERIES_DECAY) Determines how quickly the penalty for invalid message deliveries decays over time. Between 0 and 1. --p2p.peerPenaltyValues (default: 2,10,50) ($P2P_PEER_PENALTY_VALUES) The values for the peer scoring system. Passed as a comma separated list of values in order: low, mid, high tolerance errors. --p2p.doubleSpendSeverePeerPenaltyWindow (default: 30) ($P2P_DOUBLE_SPEND_SEVERE_PEER_PENALTY_WINDOW) The "age" (in L2 blocks) of a tx after which we heavily penalize a peer for sending it. --p2p.blockRequestBatchSize (default: 20) ($P2P_BLOCK_REQUEST_BATCH_SIZE) The number of blocks to fetch in a single batch. --p2p.archivedTxLimit ($P2P_ARCHIVED_TX_LIMIT) The number of transactions that will be archived. If the limit is set to 0 then archiving will be disabled. --p2p.trustedPeers (default: ) ($P2P_TRUSTED_PEERS) A list of trusted peer ENRs that will always be persisted. Separated by commas. --p2p.privatePeers (default: ) ($P2P_PRIVATE_PEERS) A list of private peer ENRs that will always be persisted and not be used for discovery. Separated by commas. --p2p.preferredPeers (default: ) ($P2P_PREFERRED_PEERS) A list of preferred peer ENRs that will always be persisted and not be used for discovery. Separated by commas. --p2p.p2pStoreMapSizeKb ($P2P_STORE_MAP_SIZE_KB) The maximum possible size of the P2P DB in KB. Overwrites the general dataStoreMapSizeKb. --p2p.txPublicSetupAllowListExtend ($TX_PUBLIC_SETUP_ALLOWLIST) Additional entries to extend the default setup allow list. Format: I:address:selector[:flags],C:classId:selector[:flags]. Flags: os (onlySelf), rn (rejectNullMsgSender), cl=N (calldataLength), joined with +. --p2p.maxPendingTxCount (default: 1000) ($P2P_MAX_PENDING_TX_COUNT) The maximum number of pending txs before evicting lower priority txs. --p2p.seenMessageCacheSize (default: 100000) ($P2P_SEEN_MSG_CACHE_SIZE) The number of messages to keep in the seen message cache --p2p.p2pDisableStatusHandshake ($P2P_DISABLE_STATUS_HANDSHAKE) True to disable the status handshake on peer connected. --p2p.p2pAllowOnlyValidators ($P2P_ALLOW_ONLY_VALIDATORS) True to only permit validators to connect. --p2p.p2pMaxFailedAuthAttemptsAllowed (default: 3) ($P2P_MAX_AUTH_FAILED_ATTEMPTS_ALLOWED) Number of auth attempts to allow before peer is banned. Number is inclusive --p2p.dropTransactions ($P2P_DROP_TX) True to simulate discarding transactions. - For testing purposes only --p2p.dropTransactionsProbability ($P2P_DROP_TX_CHANCE) The probability that a transaction is discarded (0 - 1). - For testing purposes only --p2p.disableTransactions ($TRANSACTIONS_DISABLED) Whether transactions are disabled for this node. This means transactions will be rejected at the RPC and P2P layers. --p2p.txPoolDeleteTxsAfterReorg ($P2P_TX_POOL_DELETE_TXS_AFTER_REORG) Whether to delete transactions from the pool after a reorg instead of moving them back to pending. --p2p.debugP2PInstrumentMessages ($DEBUG_P2P_INSTRUMENT_MESSAGES) Alters the format of p2p messages to include things like broadcast timestamp FOR TESTING ONLY --p2p.broadcastEquivocatedProposals Broadcast block proposals even when a conflicting proposal for the same slot already exists in the pool (for testing purposes only). --p2p.minTxPoolAgeMs (default: 2000) ($P2P_MIN_TX_POOL_AGE_MS) Minimum age (ms) a transaction must have been in the pool before it is eligible for block building. --p2p.priceBumpPercentage (default: 10) ($P2P_RPC_PRICE_BUMP_PERCENTAGE) Minimum percentage fee increase required to replace an existing tx via RPC. Even at 0%, replacement still requires paying at least 1 unit more. --p2p.blockDurationMs ($SEQ_BLOCK_DURATION_MS) Duration per block in milliseconds when building multiple blocks per slot. If undefined (default), builds a single block per slot using the full slot duration. --p2p.expectedBlockProposalsPerSlot ($SEQ_EXPECTED_BLOCK_PROPOSALS_PER_SLOT) Expected number of block proposals per slot for P2P peer scoring. 0 (default) disables block proposal scoring. Set to a positive value to enable. --p2p.maxTxsPerBlock ($SEQ_MAX_TX_PER_BLOCK) The maximum number of txs to include in a block. --p2p.overallRequestTimeoutMs (default: 10000) ($P2P_REQRESP_OVERALL_REQUEST_TIMEOUT_MS) The overall timeout for a request response operation. --p2p.individualRequestTimeoutMs (default: 10000) ($P2P_REQRESP_INDIVIDUAL_REQUEST_TIMEOUT_MS) The timeout for an individual request response peer interaction. --p2p.dialTimeoutMs (default: 5000) ($P2P_REQRESP_DIAL_TIMEOUT_MS) How long to wait for the dial protocol to establish a connection --p2p.p2pOptimisticNegotiation ($P2P_REQRESP_OPTIMISTIC_NEGOTIATION) Whether to use optimistic protocol negotiation when dialing to another peer (opposite of `negotiateFully`). --p2p.batchTxRequesterSmartParallelWorkerCount (default: 10) ($P2P_BATCH_TX_REQUESTER_SMART_PARALLEL_WORKER_COUNT) Max concurrent requests to smart peers for batch tx requester. --p2p.batchTxRequesterDumbParallelWorkerCount (default: 10) ($P2P_BATCH_TX_REQUESTER_DUMB_PARALLEL_WORKER_COUNT) Max concurrent requests to dumb peers for batch tx requester. --p2p.batchTxRequesterTxBatchSize (default: 8) ($P2P_BATCH_TX_REQUESTER_TX_BATCH_SIZE) Max transactions per request / chunk size for batch tx requester. --p2p.batchTxRequesterBadPeerThreshold (default: 2) ($P2P_BATCH_TX_REQUESTER_BAD_PEER_THRESHOLD) Failures before a peer is considered bad (see > threshold logic). --p2p.txCollectionFastNodesTimeoutBeforeReqRespMs (default: 200) ($TX_COLLECTION_FAST_NODES_TIMEOUT_BEFORE_REQ_RESP_MS) How long to wait before starting reqresp for fast collection --p2p.txCollectionSlowNodesIntervalMs (default: 12000) ($TX_COLLECTION_SLOW_NODES_INTERVAL_MS) How often to collect from configured nodes in the slow collection loop --p2p.txCollectionSlowReqRespIntervalMs (default: 12000) ($TX_COLLECTION_SLOW_REQ_RESP_INTERVAL_MS) How often to collect from peers via reqresp in the slow collection loop --p2p.txCollectionSlowReqRespTimeoutMs (default: 20000) ($TX_COLLECTION_SLOW_REQ_RESP_TIMEOUT_MS) How long to wait for a reqresp response during slow collection --p2p.txCollectionReconcileIntervalMs (default: 60000) ($TX_COLLECTION_RECONCILE_INTERVAL_MS) How often to reconcile found txs from the tx pool --p2p.txCollectionDisableSlowDuringFastRequests (default: true) ($TX_COLLECTION_DISABLE_SLOW_DURING_FAST_REQUESTS) Whether to disable the slow collection loop if we are dealing with any immediate requests --p2p.txCollectionFastNodeIntervalMs (default: 500) ($TX_COLLECTION_FAST_NODE_INTERVAL_MS) How many ms to wait between retried request to a node via RPC during fast collection --p2p.txCollectionNodeRpcUrls (default: ) ($TX_COLLECTION_NODE_RPC_URLS) A comma-separated list of Aztec node RPC URLs to use for tx collection --p2p.txCollectionFastMaxParallelRequestsPerNode (default: 4) ($TX_COLLECTION_FAST_MAX_PARALLEL_REQUESTS_PER_NODE) Maximum number of parallel requests to make to a node during fast collection --p2p.txCollectionNodeRpcMaxBatchSize (default: 50) ($TX_COLLECTION_NODE_RPC_MAX_BATCH_SIZE) Maximum number of transactions to request from a node in a single batch --p2p.txCollectionMissingTxsCollectorType (default: new) ($TX_COLLECTION_MISSING_TXS_COLLECTOR_TYPE) Which collector implementation to use for missing txs collection (new or old) --p2p.txCollectionFileStoreUrls (default: ) ($TX_COLLECTION_FILE_STORE_URLS) A comma-separated list of file store URLs (s3://, gs://, file://, http://) for tx collection --p2p.txCollectionFileStoreSlowDelayMs (default: 24000) ($TX_COLLECTION_FILE_STORE_SLOW_DELAY_MS) Delay before file store collection starts after slow collection --p2p.txCollectionFileStoreFastDelayMs (default: 2000) ($TX_COLLECTION_FILE_STORE_FAST_DELAY_MS) Delay before file store collection starts after fast collection --p2p.txCollectionFileStoreFastWorkerCount (default: 5) ($TX_COLLECTION_FILE_STORE_FAST_WORKER_COUNT) Number of concurrent workers for fast file store collection --p2p.txCollectionFileStoreSlowWorkerCount (default: 2) ($TX_COLLECTION_FILE_STORE_SLOW_WORKER_COUNT) Number of concurrent workers for slow file store collection --p2p.txCollectionFileStoreFastBackoffBaseMs (default: 1000) ($TX_COLLECTION_FILE_STORE_FAST_BACKOFF_BASE_MS) Base backoff time in ms for fast file store collection retries --p2p.txCollectionFileStoreSlowBackoffBaseMs (default: 5000) ($TX_COLLECTION_FILE_STORE_SLOW_BACKOFF_BASE_MS) Base backoff time in ms for slow file store collection retries --p2p.txCollectionFileStoreFastBackoffMaxMs (default: 5000) ($TX_COLLECTION_FILE_STORE_FAST_BACKOFF_MAX_MS) Max backoff time in ms for fast file store collection retries --p2p.txCollectionFileStoreSlowBackoffMaxMs (default: 30000) ($TX_COLLECTION_FILE_STORE_SLOW_BACKOFF_MAX_MS) Max backoff time in ms for slow file store collection retries --p2p.txFileStoreUrl ($TX_FILE_STORE_URL) URL for uploading txs to file storage (s3://, gs://, file://) --p2p.txFileStoreUploadConcurrency (default: 10) ($TX_FILE_STORE_UPLOAD_CONCURRENCY) Maximum number of concurrent tx uploads --p2p.txFileStoreMaxQueueSize (default: 1000) ($TX_FILE_STORE_MAX_QUEUE_SIZE) Maximum queue size for pending uploads (oldest dropped when exceeded) --p2p.txFileStoreEnabled ($TX_FILE_STORE_ENABLED) Enable uploading transactions to file storage P2P BOOTSTRAP --p2p-bootstrap Starts Aztec P2P Bootstrap with options --p2pBootstrap.p2pBroadcastPort ($P2P_BROADCAST_PORT) The port to broadcast the P2P service on (included in the node's ENR). Defaults to P2P_PORT. --p2pBootstrap.peerIdPrivateKeyPath ($PEER_ID_PRIVATE_KEY_PATH) An optional path to store generated peer id private keys. If blank, will default to storing any generated keys in the root of the data directory. --p2pBootstrap.queryForIp ($P2P_QUERY_FOR_IP) If announceUdpAddress or announceTcpAddress are not provided, query for the IP address of the machine. Default is false. TELEMETRY --tel.metricsCollectorUrl ($OTEL_EXPORTER_OTLP_METRICS_ENDPOINT) The URL of the telemetry collector for metrics --tel.tracesCollectorUrl ($OTEL_EXPORTER_OTLP_TRACES_ENDPOINT) The URL of the telemetry collector for traces --tel.logsCollectorUrl ($OTEL_EXPORTER_OTLP_LOGS_ENDPOINT) The URL of the telemetry collector for logs --tel.otelCollectIntervalMs (default: 60000) ($OTEL_COLLECT_INTERVAL_MS) The interval at which to collect metrics --tel.otelExportTimeoutMs (default: 30000) ($OTEL_EXPORT_TIMEOUT_MS) The timeout for exporting metrics --tel.otelExcludeMetrics (default: ) ($OTEL_EXCLUDE_METRICS) A list of metric prefixes to exclude from export --tel.otelIncludeMetrics (default: ) ($OTEL_INCLUDE_METRICS) A list of metric prefixes to include in export (ignored if OTEL_EXCLUDE_METRICS is set) --tel.publicMetricsCollectorUrl ($PUBLIC_OTEL_EXPORTER_OTLP_METRICS_ENDPOINT) A URL to publish a subset of metrics for public consumption --tel.publicMetricsCollectFrom (default: ) ($PUBLIC_OTEL_COLLECT_FROM) The role types to collect metrics from --tel.publicIncludeMetrics (default: ) ($PUBLIC_OTEL_INCLUDE_METRICS) A list of metric prefixes to publicly export --tel.publicMetricsOptOut (default: true) ($PUBLIC_OTEL_OPT_OUT) Whether to opt out of sharing optional telemetry BOT --bot Starts Aztec Bot with options --bot.nodeUrl ($AZTEC_NODE_URL) The URL to the Aztec node to check for tx pool status. --bot.nodeAdminUrl ($AZTEC_NODE_ADMIN_URL) The URL to the Aztec node admin API to force-flush txs if configured. --bot.l1Mnemonic ($BOT_L1_MNEMONIC) The mnemonic for the account to bridge fee juice from L1. --bot.l1PrivateKey ($BOT_L1_PRIVATE_KEY) The private key for the account to bridge fee juice from L1. --bot.l1ToL2MessageTimeoutSeconds (default: 3600) ($BOT_L1_TO_L2_TIMEOUT_SECONDS) How long to wait for L1 to L2 messages to become available on L2 --bot.senderPrivateKey ($BOT_PRIVATE_KEY) Signing private key for the sender account. --bot.senderSalt ($BOT_ACCOUNT_SALT) The salt to use to deploy the sender account. --bot.tokenSalt (default: 0x0000000000000000000000000000000000000000000000000000000000000001)($BOT_TOKEN_SALT) The salt to use to deploy the token contract. --bot.txIntervalSeconds (default: 60) ($BOT_TX_INTERVAL_SECONDS) Every how many seconds should a new tx be sent. --bot.privateTransfersPerTx (default: 1) ($BOT_PRIVATE_TRANSFERS_PER_TX) How many private token transfers are executed per tx. --bot.publicTransfersPerTx (default: 1) ($BOT_PUBLIC_TRANSFERS_PER_TX) How many public token transfers are executed per tx. --bot.feePaymentMethod (default: fee_juice) ($BOT_FEE_PAYMENT_METHOD) How to handle fee payments. (Options: fee_juice) --bot.minFeePadding (default: 3) ($BOT_MIN_FEE_PADDING) How much is the bot willing to overpay vs. the current base fee --bot.noStart ($BOT_NO_START) True to not automatically setup or start the bot on initialization. --bot.txMinedWaitSeconds (default: 180) ($BOT_TX_MINED_WAIT_SECONDS) How long to wait for a tx to be mined before reporting an error. --bot.followChain (default: NONE) ($BOT_FOLLOW_CHAIN) Which chain the bot follows --bot.maxPendingTxs (default: 128) ($BOT_MAX_PENDING_TXS) Do not send a tx if the node's tx pool already has this many pending txs. --bot.flushSetupTransactions ($BOT_FLUSH_SETUP_TRANSACTIONS) Make a request for the sequencer to build a block after each setup transaction. --bot.l2GasLimit ($BOT_L2_GAS_LIMIT) L2 gas limit for the tx (empty to let the bot's wallet estimate). --bot.daGasLimit ($BOT_DA_GAS_LIMIT) DA gas limit for the tx (empty to let the bot's wallet estimate). --bot.contract (default: TokenContract) ($BOT_TOKEN_CONTRACT) Token contract to use --bot.maxConsecutiveErrors ($BOT_MAX_CONSECUTIVE_ERRORS) The maximum number of consecutive errors before the bot shuts down --bot.stopWhenUnhealthy ($BOT_STOP_WHEN_UNHEALTHY) Stops the bot if service becomes unhealthy --bot.botMode (default: transfer) ($BOT_MODE) Bot mode: transfer, amm, or crosschain --bot.l2ToL1MessagesPerTx (default: 1) ($BOT_L2_TO_L1_MESSAGES_PER_TX) Number of L2→L1 messages per tx (crosschain mode) --bot.l1ToL2SeedCount (default: 1) ($BOT_L1_TO_L2_SEED_COUNT) Max L1→L2 messages to keep in-flight (crosschain mode) PXE --pxe.l2BlockBatchSize (default: 50) ($PXE_L2_BLOCK_BATCH_SIZE) Maximum amount of blocks to pull from the stream in one request when synchronizing --pxe.proverEnabled (default: true) ($PXE_PROVER_ENABLED) Enable real proofs --pxe.syncChainTip (default: proposed) ($PXE_SYNC_CHAIN_TIP) Which chain tip to sync to (proposed, checkpointed, proven, finalized) --pxe.nodeUrl ($AZTEC_NODE_URL) Custom Aztec Node URL to connect to TXE --txe Starts Aztec TXE with options ``` --- # Ethereum RPC call reference This guide provides a comprehensive reference of Ethereum RPC calls used by different Aztec node components. Understanding these calls helps with infrastructure planning, monitoring, and debugging. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before proceeding, you should: * Understand how Aztec nodes interact with Ethereum L1 * Be familiar with Ethereum JSON-RPC API specifications * Have basic knowledge of the viem library (Aztec's Ethereum client library) ## Overview[​](#overview "Direct link to Overview") Aztec nodes interact with Ethereum L1 through the [viem](https://viem.sh) library, which provides a type-safe interface to Ethereum JSON-RPC methods. Different node components make different RPC calls based on their responsibilities: * **Archiver**: Monitors L1 for new blocks and events * **Sequencer**: Proposes blocks and submits them to L1 * **Prover**: Submits proofs to L1 * **Validator**: Reads L1 state for validation * **Slasher**: Monitors for misbehavior and submits slashing payloads ## RPC call mapping[​](#rpc-call-mapping "Direct link to RPC call mapping") This table shows the Ethereum JSON-RPC calls used by Aztec nodes: | Ethereum RPC Call | Description | | --------------------------- | ---------------------------- | | `eth_getBlockByNumber` | Retrieve block information | | `eth_blockNumber` | Get latest block number | | `eth_getTransactionByHash` | Get transaction details | | `eth_getTransactionReceipt` | Get transaction receipt | | `eth_getTransactionCount` | Get account nonce | | `eth_getLogs` | Retrieve event logs | | `eth_getBalance` | Get account ETH balance | | `eth_getCode` | Get contract bytecode | | `eth_getStorageAt` | Read contract storage slot | | `eth_chainId` | Get chain identifier | | `eth_estimateGas` | Estimate gas for transaction | | `eth_call` | Execute read-only call | | `eth_sendRawTransaction` | Broadcast signed transaction | | `eth_gasPrice` | Get current gas price | | `eth_maxPriorityFeePerGas` | Get priority fee (EIP-1559) | ## Archiver node[​](#archiver-node "Direct link to Archiver node") The archiver continuously monitors L1 for new blocks and retrieves historical data. ### Block retrieval[​](#block-retrieval "Direct link to Block retrieval") **Purpose**: Sync L2 block data published to L1 **RPC calls used**: * `eth_blockNumber` - Get latest L1 block number * `eth_getLogs` - Retrieve rollup contract events * `eth_getBlockByNumber` - Get block timestamps and metadata **Example RPC calls**: ``` // eth_blockNumber {"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1} // eth_getLogs {"jsonrpc":"2.0","method":"eth_getLogs","params":[{ "fromBlock":"0x100", "toBlock":"0x200", "address":"0x...", "topics":["0x..."] }],"id":2} // eth_getBlockByNumber {"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["0x100",false],"id":3} ``` ### L1 to L2 message retrieval[​](#l1-to-l2-message-retrieval "Direct link to L1 to L2 message retrieval") **Purpose**: Track messages sent from L1 to L2 **RPC calls used**: * `eth_getLogs` - Retrieve `MessageSent` events from Inbox contract ### Contract event monitoring[​](#contract-event-monitoring "Direct link to Contract event monitoring") **Purpose**: Monitor contract deployments and updates **RPC calls used**: * `eth_getLogs` - Retrieve events from ClassRegistry and InstanceRegistry **Events monitored**: * `ContractClassPublished` * `ContractInstancePublished` * `ContractInstanceUpdated` * `PrivateFunctionBroadcasted` * `UtilityFunctionBroadcasted` ## Sequencer node[​](#sequencer-node "Direct link to Sequencer node") Sequencers propose blocks and submit them to L1, they also read L1 state to validate blocks and participate in consensus. ### Transaction broadcasting[​](#transaction-broadcasting "Direct link to Transaction broadcasting") **Purpose**: Submit block proposals to L1 **RPC calls used**: * `eth_getTransactionCount` - Get nonce for sender account * `eth_estimateGas` - Estimate gas for proposal transaction * `eth_sendRawTransaction` - Broadcast signed transaction * `eth_getTransactionReceipt` - Verify transaction inclusion **Example RPC calls**: ``` // eth_getTransactionCount {"jsonrpc":"2.0","method":"eth_getTransactionCount","params":["0x...","latest"],"id":1} // eth_estimateGas {"jsonrpc":"2.0","method":"eth_estimateGas","params":[{ "from":"0x...", "to":"0x...", "data":"0x..." }],"id":2} // eth_sendRawTransaction {"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x..."],"id":3} // eth_getTransactionReceipt {"jsonrpc":"2.0","method":"eth_getTransactionReceipt","params":["0x..."],"id":4} ``` ### State reading[​](#state-reading "Direct link to State reading") **Purpose**: Read rollup state and validate proposals **RPC calls used**: * `eth_call` - Read contract state * `eth_getStorageAt` - Read specific storage slots * `eth_blockNumber` - Get current L1 block for validation context * `eth_getBlockByNumber` - Get block timestamps **Example RPC calls**: ``` // eth_call {"jsonrpc":"2.0","method":"eth_call","params":[{ "to":"0x...", "data":"0x..." },"latest"],"id":1} // eth_getStorageAt {"jsonrpc":"2.0","method":"eth_getStorageAt","params":["0x...","0x0","latest"],"id":2} ``` ### Gas management[​](#gas-management "Direct link to Gas management") **Purpose**: Monitor gas prices and publisher account balances **RPC calls used**: * `eth_getBalance` - Check publisher account balance * `eth_gasPrice` / `eth_maxPriorityFeePerGas` - Get current gas prices ### Block simulation[​](#block-simulation "Direct link to Block simulation") **Purpose**: Validate block proposals before submission **RPC calls used**: * `eth_call` - Simulate contract call to validate proposals ## Prover node[​](#prover-node "Direct link to Prover node") The prover submits validity proofs to L1. ### Proof submission[​](#proof-submission "Direct link to Proof submission") **Purpose**: Submit epoch proofs to the Rollup contract **RPC calls used**: * `eth_getTransactionCount` - Get nonce for prover publisher * `eth_estimateGas` - Estimate gas for proof submission * `eth_sendRawTransaction` - Broadcast proof transaction * `eth_getTransactionReceipt` - Confirm proof inclusion **Note**: Uses the same transaction flow as sequencer broadcasting ### Chain state monitoring[​](#chain-state-monitoring "Direct link to Chain state monitoring") **Purpose**: Track L1 state for attestation validation **RPC calls used**: * `eth_getBlockByNumber` - Get L1 timestamps for epoch calculations * `eth_chainId` - Verify connected to correct chain ## Slasher node[​](#slasher-node "Direct link to Slasher node") The slasher monitors for validator misbehavior and submits slashing payloads. ### Misbehavior detection[​](#misbehavior-detection "Direct link to Misbehavior detection") **Purpose**: Monitor for slashable offenses and create slash payloads **RPC calls used**: * `eth_getLogs` - Retrieve rollup events for analysis * `eth_getBlockByNumber` - Get block timestamps for slashing proofs * `eth_call` - Read validator state ### Slashing payload submission[​](#slashing-payload-submission "Direct link to Slashing payload submission") **Purpose**: Submit slash payloads to L1 **RPC calls used**: * `eth_getTransactionCount` - Get nonce for slasher account * `eth_sendRawTransaction` - Broadcast slashing transaction * `eth_getTransactionReceipt` - Verify slash transaction inclusion **Note**: Uses the same transaction flow as sequencer broadcasting ## Shared infrastructure[​](#shared-infrastructure "Direct link to Shared infrastructure") Aztec provides shared transaction management utilities for all components that submit to L1. ### Core functionality[​](#core-functionality "Direct link to Core functionality") **RPC calls used**: * `eth_getTransactionCount` - Nonce management * `eth_estimateGas` - Gas estimation * `eth_gasPrice` / `eth_maxPriorityFeePerGas` - Gas pricing (EIP-1559) * `eth_sendRawTransaction` - Transaction broadcasting * `eth_getTransactionReceipt` - Transaction status checking * `eth_getTransactionByHash` - Transaction lookup for replacement * `eth_getBlockByNumber` - Block timestamp for timeout checks * `eth_getBalance` - Publisher balance monitoring ### Transaction lifecycle[​](#transaction-lifecycle "Direct link to Transaction lifecycle") 1. **Preparation**: Estimate gas and get gas price 2. **Nonce management**: Get and track nonce via `NonceManager` 3. **Signing**: Sign transaction with keystore 4. **Broadcasting**: Send via `eth_sendRawTransaction` 5. **Monitoring**: Poll with `eth_getTransactionReceipt` 6. **Replacement**: Replace stuck transactions if needed 7. **Cancellation**: Send zero-value transaction to cancel ## RPC endpoint configuration[​](#rpc-endpoint-configuration "Direct link to RPC endpoint configuration") ### Environment variables[​](#environment-variables "Direct link to Environment variables") Configure L1 RPC endpoints using: ``` # Single endpoint ETHEREUM_HOSTS=https://eth-mainnet.example.com # Multiple endpoints (fallback) ETHEREUM_HOSTS=https://eth-mainnet-1.example.com,https://eth-mainnet-2.example.com # Consensus endpoints for archiver L1_CONSENSUS_HOST_URLS=https://beacon-node.example.com ``` ### Fallback configuration[​](#fallback-configuration "Direct link to Fallback configuration") Aztec automatically retries failed requests on alternative endpoints when multiple RPC URLs are configured. This provides reliability and redundancy for critical operations. ## Monitoring and debugging[​](#monitoring-and-debugging "Direct link to Monitoring and debugging") ### RPC call logging[​](#rpc-call-logging "Direct link to RPC call logging") Enable detailed RPC logging: ``` LOG_LEVEL="debug; info: json-rpc, simulator" # or verbose ``` Look for log entries related to: * Transaction lifecycle and nonce management * Block sync and event retrieval * Block proposal submissions * Contract interactions ### Common issues[​](#common-issues "Direct link to Common issues") **Issue**: `eth_getLogs` query exceeds limits **Solution**: * Reduce block range in queries * Use archive node with higher limits * Implement chunked log retrieval **Issue**: Transaction replacement failures **Solution**: * Ensure `eth_getTransactionCount` returns consistent nonces * Configure appropriate gas price bumps * Monitor transaction pool status **Issue**: Stale state reads **Solution**: * Use specific block tags (not `latest`) * Disable caching with `cacheTime: 0` * Ensure RPC node is fully synced ## Next steps[​](#next-steps "Direct link to Next steps") * Review [How to Run a Sequencer Node](/operate/operators/setup/sequencer_management.md) for operational guidance * Learn about [High Availability Sequencers](/operate/operators/setup/high_availability_sequencers.md) for production redundancy configurations * Explore [Advanced Keystore Patterns](/operate/operators/keystore/advanced-patterns.md) for complex key management * Check [Useful Commands](/operate/operators/sequencer-management/useful-commands.md) for monitoring tools * Join the [Aztec Discord](https://discord.gg/aztec) for infrastructure support --- # Glossary This glossary defines key terms used throughout the Aztec network documentation. Terms are organized alphabetically with cross-references to related concepts. ## A[​](#a "Direct link to A") ### Agent[​](#agent "Direct link to Agent") See [Prover Agent](#prover-agent). ### Archiver[​](#archiver "Direct link to Archiver") A component that monitors Ethereum L1 for rollup events and synchronizes L2 state. The archiver retrieves block data, contract deployments, and L1-to-L2 messages from the data availability layer. ### Attestation[​](#attestation "Direct link to Attestation") A cryptographic signature from a sequencer committee member confirming the validity of a proposed block. Blocks require attestations from two-thirds of the committee plus one before submission to L1. ### Attester[​](#attester "Direct link to Attester") The identity of a sequencer node in the network. The Ethereum address derived from the attester private key uniquely identifies the sequencer and is used to sign block proposals and attestations. ## B[​](#b "Direct link to B") ### BIP44[​](#bip44 "Direct link to BIP44") Bitcoin Improvement Proposal 44 defines a standard derivation path for hierarchical deterministic wallets. Aztec uses BIP44 to derive multiple Ethereum addresses from a single mnemonic seed phrase. ### Block Proposal[​](#block-proposal "Direct link to Block Proposal") A candidate block assembled by a sequencer containing ordered transactions. Proposals must be validated by the sequencer committee before submission to L1. ### Bootnode[​](#bootnode "Direct link to Bootnode") A network node that facilitates peer discovery by maintaining lists of active peers. New nodes connect to bootnodes to discover and join the P2P network. ### Broker[​](#broker "Direct link to Broker") See [Prover Broker](#prover-broker). ## C[​](#c "Direct link to C") ### Coinbase[​](#coinbase "Direct link to Coinbase") The Ethereum address that receives L1 rewards and fees for a sequencer. If not specified in the keystore, defaults to the attester address. ### Committee[​](#committee "Direct link to Committee") See [Sequencer Committee](#sequencer-committee). ### Consensus[​](#consensus "Direct link to Consensus") The process by which sequencer nodes agree on the validity of proposed blocks through attestations and signatures. ### Contract Class[​](#contract-class "Direct link to Contract Class") A published smart contract definition containing bytecode and function signatures. Multiple contract instances can be deployed from a single contract class. ### Contract Instance[​](#contract-instance "Direct link to Contract Instance") A deployed instance of a contract class with a unique address and storage state. ## D[​](#d "Direct link to D") ### Data Availability[​](#data-availability "Direct link to Data Availability") The guarantee that block data is accessible to network participants. Aztec publishes data to Ethereum L1 to ensure data availability for state reconstruction. ### Derivation Path[​](#derivation-path "Direct link to Derivation Path") A hierarchical path used to derive cryptographic keys from a master seed. Follows the BIP44 standard for deterministic key generation. ## E[​](#e "Direct link to E") ### EIP-1559[​](#eip-1559 "Direct link to EIP-1559") Ethereum Improvement Proposal 1559 introduces a base fee mechanism for transaction pricing. Aztec nodes use EIP-1559 gas pricing when submitting transactions to L1. ### ENR (Ethereum Node Record)[​](#enr-ethereum-node-record "Direct link to ENR (Ethereum Node Record)") A signed record containing information about a network node, used for peer discovery in the P2P network. Bootnodes share their ENR for other nodes to connect. ### Epoch[​](#epoch "Direct link to Epoch") A period of multiple L2 blocks that are proven together. Prover nodes generate a single validity proof for an entire epoch and submit it to the rollup contract. ### Execution Layer[​](#execution-layer "Direct link to Execution Layer") The Ethereum L1 execution client (e.g., Geth, Nethermind) that processes transactions. Aztec nodes require access to an execution layer RPC endpoint. ## F[​](#f "Direct link to F") ### Fee Recipient[​](#fee-recipient "Direct link to Fee Recipient") The Aztec address that receives unburnt transaction fees from blocks produced by a sequencer. Must be a deployed Aztec account. ### Full Node[​](#full-node "Direct link to Full Node") A node that maintains a complete copy of the Aztec blockchain state and provides RPC interfaces for users to interact with the network without relying on third parties. ## G[​](#g "Direct link to G") ### Gas Estimation[​](#gas-estimation "Direct link to Gas Estimation") The process of calculating the expected gas cost for an Ethereum transaction before submission. Aztec nodes estimate gas for L1 transactions like block proposals and proof submissions. ## I[​](#i "Direct link to I") ### Inbox[​](#inbox "Direct link to Inbox") The L1 contract that receives messages sent from Ethereum to Aztec L2. The archiver monitors the Inbox for new L1-to-L2 messages. ## J[​](#j "Direct link to J") ### JSON V3 Keystore[​](#json-v3-keystore "Direct link to JSON V3 Keystore") An Ethereum standard for encrypted key storage using AES-128-CTR encryption and scrypt key derivation. Aztec supports JSON V3 keystores for secure key management. ## K[​](#k "Direct link to K") ### Keystore[​](#keystore "Direct link to Keystore") A configuration file or encrypted store containing private keys for sequencer operations. Keystores define attester keys, publisher keys, coinbase addresses, and fee recipients. ## L[​](#l "Direct link to L") ### L1 (Layer 1)[​](#l1-layer-1 "Direct link to L1 (Layer 1)") Ethereum mainnet, serving as the base layer for Aztec's rollup. L1 provides data availability, settlement, and consensus for the L2. ### L2 (Layer 2)[​](#l2-layer-2 "Direct link to L2 (Layer 2)") The Aztec network, a rollup scaling solution built on top of Ethereum L1. L2 processes transactions offchain and submits validity proofs to L1. ### L1 Sync[​](#l1-sync "Direct link to L1 Sync") A synchronization mode where nodes reconstruct state by querying the rollup contract and data availability layer on Ethereum L1 directly. ## M[​](#m "Direct link to M") ### Mempool[​](#mempool "Direct link to Mempool") The pool of unprocessed transactions waiting to be included in a block. Sequencers select transactions from the mempool when proposing blocks. ### Merkle Tree[​](#merkle-tree "Direct link to Merkle Tree") A cryptographic data structure that enables efficient verification of data integrity and membership. Aztec uses Merkle trees for state commitments, note storage, and nullifier tracking. ### Mnemonic[​](#mnemonic "Direct link to Mnemonic") A human-readable seed phrase (typically 12 or 24 words) used to generate deterministic cryptographic keys. Follows BIP39 standard for encoding. ## N[​](#n "Direct link to N") ### Node[​](#node "Direct link to Node") A participant in the Aztec network. See [Full Node](#full-node), [Sequencer Node](#sequencer-node), [Prover Node](#prover-node), or [Bootnode](#bootnode). ### Nonce[​](#nonce "Direct link to Nonce") A sequential number used to order transactions from an Ethereum account. Aztec nodes manage nonces when submitting transactions to L1. ### Note Tree[​](#note-tree "Direct link to Note Tree") A Merkle tree containing encrypted notes representing private state in Aztec contracts. ### Nullifier[​](#nullifier "Direct link to Nullifier") A unique value that marks a note as consumed, preventing double-spending. Nullifiers are published to L1 and tracked in the nullifier tree. ## O[​](#o "Direct link to O") ### Outbox[​](#outbox "Direct link to Outbox") The L1 contract that receives messages sent from Aztec L2 to Ethereum. Used for withdrawals and cross-chain communication. ## P[​](#p "Direct link to P") ### P2P (Peer-to-Peer)[​](#p2p-peer-to-peer "Direct link to P2P (Peer-to-Peer)") The network protocol used by Aztec nodes to discover peers, exchange transactions, and propagate blocks without central coordination. ### Proof-of-Stake[​](#proof-of-stake "Direct link to Proof-of-Stake") The consensus mechanism where sequencers lock collateral (stake) to participate in block production. Misbehavior results in stake slashing. ### Prover Agent[​](#prover-agent "Direct link to Prover Agent") A stateless worker that executes proof generation jobs. Multiple agents can run in parallel to distribute proving workload. ### Prover Broker[​](#prover-broker "Direct link to Prover Broker") A coordinator that manages the prover job queue, distributing work to agents and collecting results. ### Prover Node[​](#prover-node "Direct link to Prover Node") Infrastructure that generates validity proofs for epochs of L2 blocks. Consists of a prover node coordinator, broker, and one or more agents. ### Publisher[​](#publisher "Direct link to Publisher") The Ethereum account used by a sequencer to submit block proposals to L1. Must be funded with ETH to pay gas fees. If not specified, the attester key is used. ### PXE (Private Execution Environment)[​](#pxe-private-execution-environment "Direct link to PXE (Private Execution Environment)") The client-side component that executes private functions, manages user keys, and constructs privacy-preserving transactions. ## R[​](#r "Direct link to R") ### Registry[​](#registry "Direct link to Registry") The L1 contract that tracks deployed contract classes and instances. The archiver monitors Registry events to maintain a database of available contracts. ### Remote Signer[​](#remote-signer "Direct link to Remote Signer") An external service (e.g., Web3Signer) that stores private keys and signs transactions remotely. Used for enhanced security in production deployments. ### Rollup[​](#rollup "Direct link to Rollup") A scaling solution that processes transactions offchain and submits compressed data and validity proofs to L1. Aztec is a zkRollup with privacy features. ### RPC (Remote Procedure Call)[​](#rpc-remote-procedure-call "Direct link to RPC (Remote Procedure Call)") A protocol for remote communication. Aztec nodes expose JSON-RPC interfaces for client interaction and use RPC to communicate with Ethereum L1. ## S[​](#s "Direct link to S") ### Sequencer Committee[​](#sequencer-committee "Direct link to Sequencer Committee") A rotating group of validators responsible for validating proposed blocks through attestations during a specific time period. ### Sequencer Node[​](#sequencer-node "Direct link to Sequencer Node") A validator that assembles transactions into blocks, executes public functions, and participates in consensus through attestations. ### Slashing[​](#slashing "Direct link to Slashing") The penalty mechanism that reduces or confiscates a sequencer's stake for provable misbehavior such as double-signing or prolonged downtime. ### Slasher Node[​](#slasher-node "Direct link to Slasher Node") Infrastructure that monitors for validator misbehavior and submits slashing payloads to L1 when violations are detected. ### Snapshot[​](#snapshot "Direct link to Snapshot") A pre-built database containing blockchain state at a specific block height. Nodes can download snapshots for faster synchronization. ### Snapshot Sync[​](#snapshot-sync "Direct link to Snapshot Sync") A synchronization mode where nodes download pre-built state snapshots instead of reconstructing state from L1. Significantly faster than L1 sync. ### Stake[​](#stake "Direct link to Stake") Collateral locked by a sequencer to participate in block production. Higher stake increases selection probability as block proposer. ### State Tree[​](#state-tree "Direct link to State Tree") A Merkle tree representing the current world state of all Aztec contracts and accounts. ## T[​](#t "Direct link to T") ### Transaction Receipt[​](#transaction-receipt "Direct link to Transaction Receipt") A record of a transaction's execution on Ethereum, including status, gas used, and emitted events. Aztec nodes poll for receipts to confirm L1 transaction inclusion. ## V[​](#v "Direct link to V") ### Validator[​](#validator "Direct link to Validator") See [Sequencer Node](#sequencer-node). The terms are used interchangeably in Aztec documentation. ### Viem[​](#viem "Direct link to Viem") A TypeScript library providing type-safe interfaces to Ethereum JSON-RPC methods. Aztec nodes use viem for all L1 interactions. ## W[​](#w "Direct link to W") ### Web3Signer[​](#web3signer "Direct link to Web3Signer") An open-source remote signing service that stores keys securely and provides signing APIs. Commonly used for production sequencer deployments. ### World State[​](#world-state "Direct link to World State") The complete state of the Aztec network at a given block height, including all contract storage, notes, and nullifiers. ## Related Resources[​](#related-resources "Direct link to Related Resources") * [Node API Reference](/operate/operators/reference/node_api_reference.md) - Complete API documentation for node JSON-RPC methods * [Ethereum RPC Reference](/operate/operators/reference/ethereum_rpc_reference.md) - L1 RPC calls used by Aztec components * [Advanced Keystore Guide](/operate/operators/keystore.md) - Detailed keystore configuration options * [CLI Reference](/operate/operators/reference/cli-reference.md) - Complete command-line interface documentation --- # Node JSON RPC API reference This document provides a complete reference for the Aztec Node JSON RPC API. All methods are exposed via JSON RPC on the node's configured ports. ## API endpoint[​](#api-endpoint "Direct link to API endpoint") **Public RPC URL**: `http://localhost:8080` **Admin URL**: `http://localhost:8880` Note that the above ports are only defaults, and can be modified by setting `--port` and `--admin-port` flags upon startup. All methods use standard JSON RPC 2.0 format with methods prefixed by `node_` or `nodeAdmin_`. ## Block queries[​](#block-queries "Direct link to Block queries") ### node\_getBlockNumber[​](#node_getblocknumber "Direct link to node_getBlockNumber") Method to fetch the latest block number synchronized by the node. **Parameters**: None **Returns**: `number` - The block number. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getBlockNumber","params":[],"id":1}' ``` ### node\_getProvenBlockNumber[​](#node_getprovenblocknumber "Direct link to node_getProvenBlockNumber") Fetches the latest proven block number. **Parameters**: None **Returns**: `number` - The block number. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getProvenBlockNumber","params":[],"id":1}' ``` ### node\_getCheckpointedBlockNumber[​](#node_getcheckpointedblocknumber "Direct link to node_getCheckpointedBlockNumber") Fetches the latest checkpointed block number. **Parameters**: None **Returns**: `number` - The block number. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getCheckpointedBlockNumber","params":[],"id":1}' ``` ### node\_getCheckpointNumber[​](#node_getcheckpointnumber "Direct link to node_getCheckpointNumber") Method to fetch the latest checkpoint number synchronized by the node. **Parameters**: None **Returns**: `number` - The checkpoint number. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getCheckpointNumber","params":[],"id":1}' ``` ### node\_getL2Tips[​](#node_getl2tips "Direct link to node_getL2Tips") Returns the tips of the L2 chain. **Parameters**: None **Returns**: `L2Tips` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getL2Tips","params":[],"id":1}' ``` ### node\_getBlock[​](#node_getblock "Direct link to node_getBlock") Get a block specified by its block number or 'latest'. **Parameters**: 1. `blockParameter` - `BlockHash | number | "latest"` - The block parameter (block number, block hash, or 'latest'). **Returns**: `L2Block | undefined` - The requested block. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getBlock","params":["latest"],"id":1}' ``` ### node\_getBlockByHash[​](#node_getblockbyhash "Direct link to node_getBlockByHash") Get a block specified by its hash. **Parameters**: 1. `blockHash` - `BlockHash` - The block hash being requested. **Returns**: `L2Block | undefined` - The requested block. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getBlockByHash","params":["0x1234..."],"id":1}' ``` ### node\_getBlockByArchive[​](#node_getblockbyarchive "Direct link to node_getBlockByArchive") Get a block specified by its archive root. **Parameters**: 1. `archive` - `Fr` - The archive root being requested. **Returns**: `L2Block | undefined` - The requested block. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getBlockByArchive","params":["0x1234..."],"id":1}' ``` ### node\_getBlocks[​](#node_getblocks "Direct link to node_getBlocks") Method to request blocks. Will attempt to return all requested blocks but will return only those available. **Parameters**: 1. `from` - `number` - The start of the range of blocks to return. 2. `limit` - `number` - The maximum number of blocks to return. **Returns**: `L2Block[]` - The blocks requested. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getBlocks","params":[1,100],"id":1}' ``` ### node\_getBlockHeader[​](#node_getblockheader "Direct link to node_getBlockHeader") Returns the block header for a given block number, block hash, or 'latest'. **Parameters**: 1. `block` - `BlockHash | number | "latest" | undefined` - The block parameter (block number, block hash, or 'latest'). Defaults to 'latest'. **Returns**: `BlockHeader | undefined` - The requested block header. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getBlockHeader","params":["latest"],"id":1}' ``` ### node\_getBlockHeaderByArchive[​](#node_getblockheaderbyarchive "Direct link to node_getBlockHeaderByArchive") Get a block header specified by its archive root. **Parameters**: 1. `archive` - `Fr` - The archive root being requested. **Returns**: `BlockHeader | undefined` - The requested block header. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getBlockHeaderByArchive","params":["0x1234..."],"id":1}' ``` ### node\_getCheckpoints[​](#node_getcheckpoints "Direct link to node_getCheckpoints") Retrieves a collection of checkpoints. **Parameters**: 1. `checkpointNumber` - `number` - The first checkpoint to be retrieved. 2. `limit` - `number` - The number of checkpoints to be retrieved. **Returns**: `PublishedCheckpoint[]` - The collection of complete checkpoints. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getCheckpoints","params":[1,100],"id":1}' ``` ### node\_getCheckpointedBlocks[​](#node_getcheckpointedblocks "Direct link to node_getCheckpointedBlocks") **Parameters**: 1. `from` - `number` 2. `limit` - `number` **Returns**: `CheckpointedL2Block[]` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getCheckpointedBlocks","params":[1,100],"id":1}' ``` ### node\_getCheckpointsDataForEpoch[​](#node_getcheckpointsdataforepoch "Direct link to node_getCheckpointsDataForEpoch") Gets lightweight checkpoint metadata for a given epoch, without fetching full block data. **Parameters**: 1. `epochNumber` - `number` - Epoch for which we want checkpoint data **Returns**: `CheckpointData[]` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getCheckpointsDataForEpoch","params":[12345],"id":1}' ``` ## Transaction operations[​](#transaction-operations "Direct link to Transaction operations") ### node\_sendTx[​](#node_sendtx "Direct link to node_sendTx") Method to submit a transaction to the p2p pool. **Parameters**: 1. `tx` - `Tx` - The transaction to be submitted. **Returns**: `void` - Nothing. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_sendTx","params":[{"data":"0x..."}],"id":1}' ``` ### node\_getTxReceipt[​](#node_gettxreceipt "Direct link to node_getTxReceipt") Fetches a transaction receipt for a given transaction hash. Returns a mined receipt if it was added to the chain, a pending receipt if it's still in the mempool of the connected Aztec node, or a dropped receipt if not found in the connected Aztec node. **Parameters**: 1. `txHash` - `TxHash` - The transaction hash. **Returns**: `TxReceipt` - A receipt of the transaction. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getTxReceipt","params":["0x1234..."],"id":1}' ``` ### node\_getTxEffect[​](#node_gettxeffect "Direct link to node_getTxEffect") Gets a tx effect. **Parameters**: 1. `txHash` - `TxHash` - The hash of the tx corresponding to the tx effect. **Returns**: `IndexedTxEffect | undefined` - The requested tx effect with block info (or undefined if not found). **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getTxEffect","params":["0x1234..."],"id":1}' ``` ### node\_getTxByHash[​](#node_gettxbyhash "Direct link to node_getTxByHash") Method to retrieve a single pending tx. **Parameters**: 1. `txHash` - `TxHash` - The transaction hash to return. **Returns**: `Tx | undefined` - The pending tx if it exists. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getTxByHash","params":["0x1234..."],"id":1}' ``` ### node\_getTxsByHash[​](#node_gettxsbyhash "Direct link to node_getTxsByHash") Method to retrieve multiple pending txs. **Parameters**: 1. `txHashes` - `TxHash[]` **Returns**: `Tx[]` - The pending txs if exist. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getTxsByHash","params":[["0x1234..."]],"id":1}' ``` ### node\_getPendingTxs[​](#node_getpendingtxs "Direct link to node_getPendingTxs") Method to retrieve pending txs. **Parameters**: 1. `limit` - `number | undefined` 2. `after` - `TxHash | undefined` **Returns**: `Tx[]` - The pending txs. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getPendingTxs","params":[100,"0x1234..."],"id":1}' ``` ### node\_getPendingTxCount[​](#node_getpendingtxcount "Direct link to node_getPendingTxCount") Retrieves the number of pending txs **Parameters**: None **Returns**: `number` - The number of pending txs. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getPendingTxCount","params":[],"id":1}' ``` ### node\_isValidTx[​](#node_isvalidtx "Direct link to node_isValidTx") Returns true if the transaction is valid for inclusion at the current state. Valid transactions can be made invalid by *other* transactions if e.g. they emit the same nullifiers, or come become invalid due to e.g. the expiration\_timestamp property. **Parameters**: 1. `tx` - `Tx` - The transaction to validate for correctness. 2. `options` - `object | undefined` **Returns**: `TxValidationResult` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_isValidTx","params":[{"data":"0x..."},{}],"id":1}' ``` ### node\_simulatePublicCalls[​](#node_simulatepubliccalls "Direct link to node_simulatePublicCalls") Simulates the public part of a transaction with the current state. This currently just checks that the transaction execution succeeds. **Parameters**: 1. `tx` - `Tx` - The transaction to simulate. 2. `skipFeeEnforcement` - `boolean | undefined` **Returns**: `PublicSimulationOutput` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_simulatePublicCalls","params":[{"data":"0x..."},true],"id":1}' ``` ## State queries[​](#state-queries "Direct link to State queries") ### node\_getPublicStorageAt[​](#node_getpublicstorageat "Direct link to node_getPublicStorageAt") Gets the storage value at the given contract storage slot. **Remarks**: The storage slot here refers to the slot as it is defined in Noir not the index in the merkle tree. Aztec's version of `eth_getStorageAt`. **Parameters**: 1. `referenceBlock` - `BlockHash | number | "latest"` - The block parameter (block number, block hash, or 'latest') at which to get the data. 2. `contract` - `AztecAddress` - Address of the contract to query. 3. `slot` - `Fr` - Slot to query. **Returns**: `Fr` - Storage value at the given contract slot. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getPublicStorageAt","params":["latest","0x1234...","0x1234..."],"id":1}' ``` ### node\_getWorldStateSyncStatus[​](#node_getworldstatesyncstatus "Direct link to node_getWorldStateSyncStatus") Returns the sync status of the node's world state **Parameters**: None **Returns**: `WorldStateSyncStatus` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getWorldStateSyncStatus","params":[],"id":1}' ``` ## Membership witnesses[​](#membership-witnesses "Direct link to Membership witnesses") ### node\_findLeavesIndexes[​](#node_findleavesindexes "Direct link to node_findLeavesIndexes") Find the indexes of the given leaves in the given tree along with a block metadata pointing to the block in which the leaves were inserted. **Parameters**: 1. `referenceBlock` - `BlockHash | number | "latest"` - The block parameter (block number, block hash, or 'latest') at which to get the data. 2. `treeId` - `MerkleTreeId` - The tree to search in. 3. `leafValues` - `Fr[]` - The values to search for. **Returns**: `(DataInBlock | undefined)[]` - The indices of leaves and the block metadata of a block in which the leaves were inserted. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_findLeavesIndexes","params":["latest",1,["0x1234..."]],"id":1}' ``` ### node\_getNullifierMembershipWitness[​](#node_getnullifiermembershipwitness "Direct link to node_getNullifierMembershipWitness") Returns a nullifier membership witness for a given nullifier at a given block. **Parameters**: 1. `referenceBlock` - `BlockHash | number | "latest"` - The block parameter (block number, block hash, or 'latest') at which to get the data. 2. `nullifier` - `Fr` - Nullifier we try to find witness for. **Returns**: `NullifierMembershipWitness | undefined` - The nullifier membership witness (if found). **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getNullifierMembershipWitness","params":["latest","0x1234..."],"id":1}' ``` ### node\_getLowNullifierMembershipWitness[​](#node_getlownullifiermembershipwitness "Direct link to node_getLowNullifierMembershipWitness") Returns a low nullifier membership witness for a given nullifier at a given block. **Remarks**: Low nullifier witness can be used to perform a nullifier non-inclusion proof by leveraging the "linked list structure" of leaves and proving that a lower nullifier is pointing to a bigger next value than the nullifier we are trying to prove non-inclusion for. **Parameters**: 1. `referenceBlock` - `BlockHash | number | "latest"` - The block parameter (block number, block hash, or 'latest') at which to get the data. 2. `nullifier` - `Fr` - Nullifier we try to find the low nullifier witness for. **Returns**: `NullifierMembershipWitness | undefined` - The low nullifier membership witness (if found). **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getLowNullifierMembershipWitness","params":["latest","0x1234..."],"id":1}' ``` ### node\_getPublicDataWitness[​](#node_getpublicdatawitness "Direct link to node_getPublicDataWitness") Returns a public data tree witness for a given leaf slot at a given block. **Remarks**: The witness can be used to compute the current value of the public data tree leaf. If the low leaf preimage corresponds to an "in range" slot, means that the slot doesn't exist and the value is 0. If the low leaf preimage corresponds to the exact slot, the current value is contained in the leaf preimage. **Parameters**: 1. `referenceBlock` - `BlockHash | number | "latest"` - The block parameter (block number, block hash, or 'latest') at which to get the data. 2. `leafSlot` - `Fr` - The leaf slot we try to find the witness for. **Returns**: `PublicDataWitness | undefined` - The public data witness (if found). **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getPublicDataWitness","params":["latest","0x1234..."],"id":1}' ``` ### node\_getBlockHashMembershipWitness[​](#node_getblockhashmembershipwitness "Direct link to node_getBlockHashMembershipWitness") Returns a membership witness for a given block hash in the archive tree. 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 to the archive tree. This method finds the membership witness (leaf index and sibling path) for a given block hash, which can be used to prove that a specific block exists in the chain's history. **Parameters**: 1. `referenceBlock` - `BlockHash | number | "latest"` - The block parameter (block number, block hash, or 'latest') at which to get the data (which contains the root of the archive tree in which we are searching for the block hash). 2. `blockHash` - `BlockHash` - The block hash to find in the archive tree. **Returns**: `MembershipWitness | undefined` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getBlockHashMembershipWitness","params":["latest","0x1234..."],"id":1}' ``` ### node\_getNoteHashMembershipWitness[​](#node_getnotehashmembershipwitness "Direct link to node_getNoteHashMembershipWitness") Returns a membership witness for a given note hash at a given block. **Parameters**: 1. `referenceBlock` - `BlockHash | number | "latest"` - The block parameter (block number, block hash, or 'latest') at which to get the data. 2. `noteHash` - `Fr` - The note hash we try to find the witness for. **Returns**: `MembershipWitness | undefined` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getNoteHashMembershipWitness","params":["latest","0x1234..."],"id":1}' ``` ## L1 to L2 messages[​](#l1-to-l2-messages "Direct link to L1 to L2 messages") ### node\_getL1ToL2MessageMembershipWitness[​](#node_getl1tol2messagemembershipwitness "Direct link to node_getL1ToL2MessageMembershipWitness") Returns the index and a sibling path for a leaf in the committed l1 to l2 data tree. **Parameters**: 1. `referenceBlock` - `BlockHash | number | "latest"` - The block parameter (block number, block hash, or 'latest') at which to get the data. 2. `l1ToL2Message` - `Fr` - The l1ToL2Message to get the index / sibling path for. **Returns**: `[bigint, SiblingPath] | undefined` - A tuple of the index and the sibling path of the L1ToL2Message (undefined if not found). **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getL1ToL2MessageMembershipWitness","params":["latest","0x1234..."],"id":1}' ``` ### node\_getL1ToL2MessageCheckpoint[​](#node_getl1tol2messagecheckpoint "Direct link to node_getL1ToL2MessageCheckpoint") Returns the L2 checkpoint number in which this L1 to L2 message becomes available, or undefined if not found. **Parameters**: 1. `l1ToL2Message` - `Fr` **Returns**: `number | undefined` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getL1ToL2MessageCheckpoint","params":["0x1234..."],"id":1}' ``` ### node\_isL1ToL2MessageSynced[​](#node_isl1tol2messagesynced "Direct link to node_isL1ToL2MessageSynced") Returns whether an L1 to L2 message is synced by archiver. **Deprecated**: Use `getL1ToL2MessageCheckpoint` instead. This method may return true even if the message is not ready to use. **Parameters**: 1. `l1ToL2Message` - `Fr` - The L1 to L2 message to check. **Returns**: `boolean` - Whether the message is synced. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_isL1ToL2MessageSynced","params":["0x1234..."],"id":1}' ``` ### node\_getL2ToL1Messages[​](#node_getl2tol1messages "Direct link to node_getL2ToL1Messages") Returns all the L2 to L1 messages in an epoch. **Parameters**: 1. `epoch` - `number` - The epoch at which to get the data. **Returns**: `Fr[][][][]` - A nested array of the L2 to L1 messages in each tx of each block in each checkpoint in the epoch (empty array if the epoch is not found). **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getL2ToL1Messages","params":[12345],"id":1}' ``` ## Log queries[​](#log-queries "Direct link to Log queries") ### node\_getPublicLogs[​](#node_getpubliclogs "Direct link to node_getPublicLogs") Gets public logs based on the provided filter. **Parameters**: 1. `filter` - `LogFilter` - The filter to apply to the logs. **Returns**: `GetPublicLogsResponse` - The requested logs. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getPublicLogs","params":[{"fromBlock":100,"toBlock":200}],"id":1}' ``` ### node\_getContractClassLogs[​](#node_getcontractclasslogs "Direct link to node_getContractClassLogs") Gets contract class logs based on the provided filter. **Parameters**: 1. `filter` - `LogFilter` - The filter to apply to the logs. **Returns**: `GetContractClassLogsResponse` - The requested logs. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getContractClassLogs","params":[{"fromBlock":100,"toBlock":200}],"id":1}' ``` ### node\_getPrivateLogsByTags[​](#node_getprivatelogsbytags "Direct link to node_getPrivateLogsByTags") Gets private logs that match any of the `tags`. For each tag, an array of matching logs is returned. An empty array implies no logs match that tag. **Parameters**: 1. `tags` - `SiloedTag[]` - The tags to search for. 2. `page` - `number | undefined` - The page number (0-indexed) for pagination. 3. `referenceBlock` - `BlockHash | undefined` - Optional block hash used to ensure the block still exists before logs are retrieved. This block is expected to represent the latest block to which the client has synced (called anchor block in PXE). If specified and the block is not found, an error is thrown. This helps detect reorgs, which could result in undefined behavior in the client's code. **Returns**: `TxScopedL2Log[][]` - An array of log arrays, one per tag. Returns at most 10 logs per tag per page. If 10 logs are returned for a tag, the caller should fetch the next page to check for more logs. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getPrivateLogsByTags","params":[["0x1234..."],0,"0x1234..."],"id":1}' ``` ### node\_getPublicLogsByTagsFromContract[​](#node_getpubliclogsbytagsfromcontract "Direct link to node_getPublicLogsByTagsFromContract") Gets public logs that match any of the `tags` from the specified contract. For each tag, an array of matching logs is returned. An empty array implies no logs match that tag. **Parameters**: 1. `contractAddress` - `AztecAddress` - The contract address to search logs for. 2. `tags` - `Tag[]` - The tags to search for. 3. `page` - `number | undefined` - The page number (0-indexed) for pagination. 4. `referenceBlock` - `BlockHash | undefined` - Optional block hash used to ensure the block still exists before logs are retrieved. This block is expected to represent the latest block to which the client has synced (called anchor block in PXE). If specified and the block is not found, an error is thrown. This helps detect reorgs, which could result in undefined behavior in the client's code. **Returns**: `TxScopedL2Log[][]` - An array of log arrays, one per tag. Returns at most 10 logs per tag per page. If 10 logs are returned for a tag, the caller should fetch the next page to check for more logs. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getPublicLogsByTagsFromContract","params":["0x1234...",["0x1234..."],0,"0x1234..."],"id":1}' ``` ## Contract queries[​](#contract-queries "Direct link to Contract queries") ### node\_getContractClass[​](#node_getcontractclass "Direct link to node_getContractClass") Returns a registered contract class given its id. **Parameters**: 1. `id` - `Fr` - Id of the contract class. **Returns**: `ContractClassPublic | undefined` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getContractClass","params":["0x1234..."],"id":1}' ``` ### node\_getContract[​](#node_getcontract "Direct link to node_getContract") Returns a publicly deployed contract instance given its address. **Parameters**: 1. `address` - `AztecAddress` - Address of the deployed contract. **Returns**: `ContractInstanceWithAddress | undefined` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getContract","params":["0x1234..."],"id":1}' ``` ## Fee queries[​](#fee-queries "Direct link to Fee queries") ### node\_getCurrentMinFees[​](#node_getcurrentminfees "Direct link to node_getCurrentMinFees") Method to fetch the current min fees. **Parameters**: None **Returns**: `GasFees` - The current min fees. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getCurrentMinFees","params":[],"id":1}' ``` ### node\_getMaxPriorityFees[​](#node_getmaxpriorityfees "Direct link to node_getMaxPriorityFees") Method to fetch the current max priority fee of txs in the mempool. **Parameters**: None **Returns**: `GasFees` - The current max priority fees. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getMaxPriorityFees","params":[],"id":1}' ``` ## Node information[​](#node-information "Direct link to Node information") ### node\_isReady[​](#node_isready "Direct link to node_isReady") Method to determine if the node is ready to accept transactions. **Parameters**: None **Returns**: `boolean` - Flag indicating the readiness for tx submission. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_isReady","params":[],"id":1}' ``` ### node\_getNodeInfo[​](#node_getnodeinfo "Direct link to node_getNodeInfo") Returns the information about the server's node. Includes current Node version, compatible Noir version, L1 chain identifier, protocol version, and L1 address of the rollup contract. **Parameters**: None **Returns**: `NodeInfo` - The node information. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getNodeInfo","params":[],"id":1}' ``` ### node\_getNodeVersion[​](#node_getnodeversion "Direct link to node_getNodeVersion") Method to fetch the version of the package. **Parameters**: None **Returns**: `string` - The node package version **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getNodeVersion","params":[],"id":1}' ``` ### node\_getVersion[​](#node_getversion "Direct link to node_getVersion") Method to fetch the version of the rollup the node is connected to. **Parameters**: None **Returns**: `number` - The rollup version. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getVersion","params":[],"id":1}' ``` ### node\_getChainId[​](#node_getchainid "Direct link to node_getChainId") Method to fetch the chain id of the base-layer for the rollup. **Parameters**: None **Returns**: `number` - The chain id. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getChainId","params":[],"id":1}' ``` ### node\_getL1ContractAddresses[​](#node_getl1contractaddresses "Direct link to node_getL1ContractAddresses") Method to fetch the currently deployed l1 contract addresses. **Parameters**: None **Returns**: `L1ContractAddresses` - The deployed contract addresses. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getL1ContractAddresses","params":[],"id":1}' ``` ### node\_getProtocolContractAddresses[​](#node_getprotocolcontractaddresses "Direct link to node_getProtocolContractAddresses") Method to fetch the protocol contract addresses. **Parameters**: None **Returns**: `ProtocolContractAddresses` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getProtocolContractAddresses","params":[],"id":1}' ``` ### node\_getEncodedEnr[​](#node_getencodedenr "Direct link to node_getEncodedEnr") Returns the ENR of this node for peer discovery, if available. **Parameters**: None **Returns**: `string | undefined` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getEncodedEnr","params":[],"id":1}' ``` ## Validator queries[​](#validator-queries "Direct link to Validator queries") ### node\_getValidatorsStats[​](#node_getvalidatorsstats "Direct link to node_getValidatorsStats") Returns stats for validators if enabled. **Parameters**: None **Returns**: `ValidatorsStats` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getValidatorsStats","params":[],"id":1}' ``` ### node\_getValidatorStats[​](#node_getvalidatorstats "Direct link to node_getValidatorStats") Returns stats for a single validator if enabled. **Parameters**: 1. `validatorAddress` - `EthAddress` 2. `fromSlot` - `SlotNumber | undefined` 3. `toSlot` - `SlotNumber | undefined` **Returns**: `SingleValidatorStats | undefined` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getValidatorStats","params":["0x1234...","100","100"],"id":1}' ``` ## Debug operations[​](#debug-operations "Direct link to Debug operations") ### node\_registerContractFunctionSignatures[​](#node_registercontractfunctionsignatures "Direct link to node_registerContractFunctionSignatures") Registers contract function signatures for debugging purposes. **Parameters**: 1. `functionSignatures` - `string[]` - An array of function signatures to register by selector. **Returns**: `void` **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_registerContractFunctionSignatures","params":[["0x1234..."]],"id":1}' ``` ### node\_getAllowedPublicSetup[​](#node_getallowedpublicsetup "Direct link to node_getAllowedPublicSetup") Returns the list of allowed public setup elements configured for this node. **Parameters**: None **Returns**: `AllowedElement[]` - The list of allowed elements. **Example**: ``` curl -X POST http://localhost:8080 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getAllowedPublicSetup","params":[],"id":1}' ``` ## Admin API[​](#admin-api "Direct link to Admin API") Administrative operations are exposed on port 8880 under the `nodeAdmin_` namespace. Security: Admin API Access For security reasons, the admin port (8880) should **not be exposed** to the host machine in Docker deployments. The examples below show both CLI and Docker methods: **CLI Method** (when running with `aztec start` directly): ``` curl -X POST http://localhost:8880 ... ``` **Docker Method** (when running with Docker Compose): ``` docker exec -it curl -X POST http://localhost:8880 ... ``` Replace `` with your container name (e.g., `aztec-node`, `aztec-sequencer`, `prover-node`). ### nodeAdmin\_getConfig[​](#nodeadmin_getconfig "Direct link to nodeAdmin_getConfig") Retrieves the configuration of this node. **Parameters**: None **Returns**: `AztecNodeAdminConfig` **Example (CLI)**: ``` curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"nodeAdmin_getConfig","params":[],"id":1}' ``` **Example (Docker)**: ``` docker exec -it aztec-node curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"nodeAdmin_getConfig","params":[],"id":1}' ``` ### nodeAdmin\_setConfig[​](#nodeadmin_setconfig "Direct link to nodeAdmin_setConfig") Updates the configuration of this node. **Parameters**: 1. `config` - `object` - Updated configuration to be merged with the current one. **Returns**: `void` **Example (CLI)**: ``` curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"nodeAdmin_setConfig","params":[{}],"id":1}' ``` **Example (Docker)**: ``` docker exec -it aztec-node curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"nodeAdmin_setConfig","params":[{}],"id":1}' ``` ### nodeAdmin\_pauseSync[​](#nodeadmin_pausesync "Direct link to nodeAdmin_pauseSync") Pauses archiver and world state syncing. **Parameters**: None **Returns**: `void` **Example (CLI)**: ``` curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"nodeAdmin_pauseSync","params":[],"id":1}' ``` **Example (Docker)**: ``` docker exec -it aztec-node curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"nodeAdmin_pauseSync","params":[],"id":1}' ``` ### nodeAdmin\_resumeSync[​](#nodeadmin_resumesync "Direct link to nodeAdmin_resumeSync") Resumes archiver and world state syncing. **Parameters**: None **Returns**: `void` **Example (CLI)**: ``` curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"nodeAdmin_resumeSync","params":[],"id":1}' ``` **Example (Docker)**: ``` docker exec -it aztec-node curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"nodeAdmin_resumeSync","params":[],"id":1}' ``` ### nodeAdmin\_rollbackTo[​](#nodeadmin_rollbackto "Direct link to nodeAdmin_rollbackTo") Pauses syncing and rolls back the database to the target L2 block number. **Parameters**: 1. `targetBlockNumber` - `number` - The block number to roll back to. **Returns**: `void` **Example (CLI)**: ``` curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"nodeAdmin_rollbackTo","params":[12345],"id":1}' ``` **Example (Docker)**: ``` docker exec -it aztec-node curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"nodeAdmin_rollbackTo","params":[12345],"id":1}' ``` ### nodeAdmin\_startSnapshotUpload[​](#nodeadmin_startsnapshotupload "Direct link to nodeAdmin_startSnapshotUpload") Pauses syncing, creates a backup of archiver and world-state databases, and uploads them. Returns immediately. **Parameters**: 1. `location` - `string` - The location to upload the snapshot to. **Returns**: `void` **Example (CLI)**: ``` curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"nodeAdmin_startSnapshotUpload","params":["0x1234..."],"id":1}' ``` **Example (Docker)**: ``` docker exec -it aztec-node curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"nodeAdmin_startSnapshotUpload","params":["0x1234..."],"id":1}' ``` ### nodeAdmin\_getSlashPayloads[​](#nodeadmin_getslashpayloads "Direct link to nodeAdmin_getSlashPayloads") Returns all monitored payloads by the slasher for the current round. **Parameters**: None **Returns**: `SlashPayloadRound[]` **Example (CLI)**: ``` curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"nodeAdmin_getSlashPayloads","params":[],"id":1}' ``` **Example (Docker)**: ``` docker exec -it aztec-node curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"nodeAdmin_getSlashPayloads","params":[],"id":1}' ``` ### nodeAdmin\_getSlashOffenses[​](#nodeadmin_getslashoffenses "Direct link to nodeAdmin_getSlashOffenses") Returns all offenses applicable for the given round. **Parameters**: 1. `round` - `bigint | 'all' | 'current'` **Returns**: `Offense[]` **Example (CLI)**: ``` curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"nodeAdmin_getSlashOffenses","params":["current"],"id":1}' ``` **Example (Docker)**: ``` docker exec -it aztec-node curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"nodeAdmin_getSlashOffenses","params":["current"],"id":1}' ``` ### nodeAdmin\_reloadKeystore[​](#nodeadmin_reloadkeystore "Direct link to nodeAdmin_reloadKeystore") Reloads keystore configuration from disk. What is updated: * Validator attester keys * Coinbase address per validator * Fee recipient address per validator What is NOT updated (requires node restart): * L1 publisher signers (the funded accounts that send L1 transactions) * Prover keys * HA signer PostgreSQL connections Notes: * New validators must use a publisher key that was already configured at node startup (or omit the publisher field to fall back to the attester key). A validator with an unknown publisher key will cause the reload to be rejected. **Parameters**: None **Returns**: `void` **Example (CLI)**: ``` curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"nodeAdmin_reloadKeystore","params":[],"id":1}' ``` **Example (Docker)**: ``` docker exec -it aztec-node curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"nodeAdmin_reloadKeystore","params":[],"id":1}' ``` ## Next steps[​](#next-steps "Direct link to Next steps") * [How to Run a Sequencer Node](/operate/operators/setup/sequencer_management.md) - Set up a node * [Ethereum RPC Calls Reference](/operate/operators/reference/ethereum_rpc_reference.md) - L1 RPC usage * [CLI Reference](/operate/operators/reference/cli-reference.md) - Command-line options * [Aztec Discord](https://discord.gg/aztec) - Developer support --- # Sequencer Management ## Overview[​](#overview "Direct link to Overview") Once your sequencer is running, you need to manage its ongoing operations. This guide covers sequencer management tasks including participating in governance, running with delegated stake, and querying contract state to monitor your sequencer's health and performance. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before proceeding, you should: * Have a running sequencer node (see [Sequencer Setup Guide](/operate/operators/setup/sequencer_management.md)) * Be familiar with basic sequencer operations * Have access to Foundry's `cast` tool for contract queries * Understand your sequencer's role in the network ## Understanding Sequencer Operations[​](#understanding-sequencer-operations "Direct link to Understanding Sequencer Operations") As a sequencer operator, your responsibilities extend beyond simply running a node. You participate in network governance, manage your stake (whether self-funded or delegated), and monitor your sequencer's performance and status on the network. ### Key Management Areas[​](#key-management-areas "Direct link to Key Management Areas") **Governance Participation**: Sequencers play a crucial role in protocol governance. You signal support for protocol upgrades, vote on proposals, and help shape the network's evolution. Active participation ensures your voice is heard in decisions that affect the protocol. **Stake Management**: Whether you're using your own stake or operating with delegated stake from others, you need to understand how staking works, monitor your balances, and ensure you maintain sufficient funds for operations. **Operational Monitoring**: Regular monitoring of your sequencer's status, performance metrics, and onchain state helps you catch issues early and maintain optimal operations. ## What This Guide Covers[​](#what-this-guide-covers "Direct link to What This Guide Covers") This guide walks you through sequencer management in four parts: ### 1. Governance and Proposal Process[​](#1-governance-and-proposal-process "Direct link to 1. Governance and Proposal Process") Learn how to participate in protocol governance: * Understanding payloads and the governance lifecycle * Signaling support for protocol upgrades * Creating and voting on proposals * Executing approved changes * Upgrading your node after governance changes See [Governance Participation](/operate/operators/sequencer-management/creating_and_voting_on_proposals.md) for detailed instructions. ### 2. Running as a Staking Provider[​](#2-running-as-a-staking-provider "Direct link to 2. Running as a Staking Provider") If you're operating a sequencer with delegated stake: * Understanding the delegated stake model * Registering as a provider with the Staking Registry * Managing sequencer identities for delegation * Updating provider configuration and commission rates * Monitoring delegator relationships See [Becoming a Staking Provider](/operate/operators/setup/become_a_staking_provider.md) for setup instructions. ### 3. Claiming Rewards[​](#3-claiming-rewards "Direct link to 3. Claiming Rewards") Learn how to claim your sequencer rewards: * Understanding how rewards accumulate in the Rollup contract * Checking reward claimability status and pending rewards * Claiming rewards to your coinbase address * Troubleshooting common claiming issues See [Claiming Rewards](/operate/operators/sequencer-management/claiming-rewards.md) for detailed instructions. ### 4. Useful Commands[​](#4-useful-commands "Direct link to 4. Useful Commands") Essential contract query commands for operators: * Finding contract addresses (Registry, Rollup, Governance) * Querying the sequencer set and individual sequencer status * Checking governance signals and proposal counts * Monitoring stake balances and voting power * Troubleshooting common query issues See [Useful Commands](/operate/operators/sequencer-management/useful-commands.md) for a complete reference. ## Getting Started[​](#getting-started "Direct link to Getting Started") Start with the [Useful Commands](/operate/operators/sequencer-management/useful-commands.md) guide to learn how to query your sequencer's status and verify it's operating correctly. This helps you establish a baseline for monitoring. If you're participating in governance, review the [Governance Participation](/operate/operators/sequencer-management/creating_and_voting_on_proposals.md) guide to understand how to signal, vote, and execute proposals. For operators running with delegated stake, the [Becoming a Staking Provider](/operate/operators/setup/become_a_staking_provider.md) guide walks you through provider registration and management. ## Best Practices[​](#best-practices "Direct link to Best Practices") **Monitor Regularly**: Check your sequencer's status, balance, and attestation activity regularly. Set up alerts for critical thresholds like low balances or missed attestations. **Participate in Governance**: Stay informed about governance proposals and participate in votes that affect your operations. Join the community discussions on Discord to understand proposed changes. **Maintain Adequate Balances**: Ensure your publisher account always has sufficient ETH (at least 0.1 ETH) to avoid being slashed. Monitor balances and set up automated top-ups if possible. **Keep Your Node Updated**: When governance proposals pass that require node upgrades, prepare during the execution delay period. Have a plan for coordinated upgrades to minimize downtime. **Communicate with Delegators**: If you're running with delegated stake, maintain open communication with your delegators about performance, commission changes, and planned maintenance. ## Next Steps[​](#next-steps "Direct link to Next Steps") * Query your sequencer status using the [Useful Commands](/operate/operators/sequencer-management/useful-commands.md) * Learn about [governance participation](/operate/operators/sequencer-management/creating_and_voting_on_proposals.md) to vote on protocol changes * Set up [monitoring](/operate/operators/monitoring.md) to track your sequencer's performance * Join the [Aztec Discord](https://discord.gg/aztec) for operator support and community discussions --- # Claiming Rewards ## Overview[​](#overview "Direct link to Overview") Sequencer rewards accumulate in the Rollup contract but are not automatically distributed. You must manually claim them by calling the Rollup contract. This guide shows you how to check pending rewards and claim them using Foundry's `cast` command. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before proceeding, you should: * Have a running sequencer that earned rewards (see [Sequencer Setup Guide](/operate/operators/setup/sequencer_management.md)) * Have Foundry installed with the `cast` command available ([installation guide](https://book.getfoundry.sh/getting-started/installation)) * Know your Rollup contract address (see [Useful Commands](/operate/operators/sequencer-management/useful-commands.md#get-the-rollup-contract-address)) * Have your sequencer's coinbase address * Have an Ethereum RPC endpoint for the network you're querying ## Understanding Reward Claiming[​](#understanding-reward-claiming "Direct link to Understanding Reward Claiming") ### How Rewards Accumulate[​](#how-rewards-accumulate "Direct link to How Rewards Accumulate") When your sequencer proposes blocks and participates in consensus, rewards accumulate in the Rollup contract under your coinbase address. These rewards come from: * Block rewards distributed by the protocol * Transaction fees from processed transactions Rewards are tracked per coinbase address in the Rollup contract's storage but remain in the contract until you claim them. ### Manual vs Automatic[​](#manual-vs-automatic "Direct link to Manual vs Automatic") Rewards are not automatically sent to your coinbase address. You must explicitly claim them by calling the `claimSequencerRewards` function on the Rollup contract. ### Claim Requirements[​](#claim-requirements "Direct link to Claim Requirements") Before claiming, verify these conditions: 1. **Rewards must be claimable**: A governance vote must pass to enable the claiming of rewards (only possible after a minimum configured timestamp) and governance must have called `setRewardsClaimable(true)` on the rollup contract. 2. **Rewards have accumulated**: Query your pending rewards before attempting to claim. 3. **Sufficient gas**: Ensure you have ETH to pay transaction gas costs. ## Checking Reward Status[​](#checking-reward-status "Direct link to Checking Reward Status") ### Set Up Your Environment[​](#set-up-your-environment "Direct link to Set Up Your Environment") For convenience, set your RPC URL as an environment variable: ``` export RPC_URL="https://your-ethereum-rpc-endpoint.com" export ROLLUP_ADDRESS="[YOUR_ROLLUP_CONTRACT_ADDRESS]" ``` Replace `[YOUR_ROLLUP_CONTRACT_ADDRESS]` with your actual Rollup contract address. ### Check if Rewards Are Claimable[​](#check-if-rewards-are-claimable "Direct link to Check if Rewards Are Claimable") Verify reward claiming is enabled before attempting to claim: ``` cast call $ROLLUP_ADDRESS "isRewardsClaimable()" --rpc-url $RPC_URL ``` **Expected output:** * `0x0000000000000000000000000000000000000000000000000000000000000001` - Rewards are claimable (true) * `0x0000000000000000000000000000000000000000000000000000000000000000` - Rewards are not yet claimable (false) If rewards are not claimable, check when they will become claimable: ``` cast call $ROLLUP_ADDRESS "getEarliestRewardsClaimableTimestamp()" --rpc-url $RPC_URL ``` This returns a Unix timestamp indicating the earliest time when governance can enable reward claiming. ### Query Your Pending Rewards[​](#query-your-pending-rewards "Direct link to Query Your Pending Rewards") Check accumulated rewards: ``` cast call $ROLLUP_ADDRESS "getSequencerRewards(address)" [COINBASE_ADDRESS] --rpc-url $RPC_URL ``` Replace `[COINBASE_ADDRESS]` with your sequencer's coinbase address. **Example:** ``` # Query and convert to decimal tokens (assuming 18 decimals) cast call $ROLLUP_ADDRESS "getSequencerRewards(address)" 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb --rpc-url $RPC_URL | cast --to-dec | cast --from-wei # Output: 0.1 ``` ## Claiming Your Rewards[​](#claiming-your-rewards "Direct link to Claiming Your Rewards") The `claimSequencerRewards` function is permissionless - anyone can call it for any address. Rewards are always sent to the `coinbase` address, regardless of who submits the transaction. ### Basic Claim Command[​](#basic-claim-command "Direct link to Basic Claim Command") Use `cast send` to claim rewards: ``` cast send $ROLLUP_ADDRESS \ "claimSequencerRewards(address)" \ [COINBASE_ADDRESS] \ --rpc-url $RPC_URL \ --private-key [YOUR_PRIVATE_KEY] ``` Replace: * `[COINBASE_ADDRESS]` - The coinbase address whose rewards you want to claim * `[YOUR_PRIVATE_KEY]` - The private key of the account paying for gas **Example:** ``` cast send $ROLLUP_ADDRESS \ "claimSequencerRewards(address)" \ 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb \ --rpc-url $RPC_URL \ --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 ``` ### Using a Keystore File[​](#using-a-keystore-file "Direct link to Using a Keystore File") For better security, use a keystore file instead of exposing your private key: ``` cast send $ROLLUP_ADDRESS \ "claimSequencerRewards(address)" \ [COINBASE_ADDRESS] \ --rpc-url $RPC_URL \ --keystore [PATH_TO_KEYSTORE] \ --password [KEYSTORE_PASSWORD] ``` ### Using a Hardware Wallet[​](#using-a-hardware-wallet "Direct link to Using a Hardware Wallet") If you're using a Ledger wallet: ``` cast send $ROLLUP_ADDRESS \ "claimSequencerRewards(address)" \ [COINBASE_ADDRESS] \ --rpc-url $RPC_URL \ --ledger ``` This will prompt you to confirm the transaction on your Ledger device. ## Verifying Your Claim[​](#verifying-your-claim "Direct link to Verifying Your Claim") Check that the transaction succeeded and your pending rewards were reset to zero: ``` # Check transaction succeeded (look for status: 1) cast receipt [TRANSACTION_HASH] --rpc-url $RPC_URL # Verify pending rewards are now zero cast call $ROLLUP_ADDRESS "getSequencerRewards(address)" [COINBASE_ADDRESS] --rpc-url $RPC_URL ``` ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### "Rewards not claimable" Error[​](#rewards-not-claimable-error "Direct link to \"Rewards not claimable\" Error") **Symptom**: Transaction reverts with "Rewards not claimable" error. **Solution**: 1. Check if rewards are claimable using `isRewardsClaimable()` 2. If `false`, wait until governance enables claiming via `setRewardsClaimable(true)` 3. Check the earliest claimable timestamp using `getEarliestRewardsClaimableTimestamp()` ### No Pending Rewards[​](#no-pending-rewards "Direct link to No Pending Rewards") **Symptom**: `getSequencerRewards()` returns zero. **Possible causes**: 1. Your sequencer has not proposed any blocks yet 2. You already claimed all available rewards 3. Your coinbase address is configured incorrectly **Solutions**: 1. Verify your sequencer is active and proposing blocks (check [monitoring](/operate/operators/monitoring.md)) 2. Check your sequencer logs for block proposals 3. Verify the coinbase address in your sequencer configuration matches the address you're querying 4. Check if blocks you proposed have been proven (rewards are distributed after proof submission) ### Transaction Fails with "Out of Gas"[​](#transaction-fails-with-out-of-gas "Direct link to Transaction Fails with \"Out of Gas\"") **Symptom**: Transaction reverts due to insufficient gas. **Solution**: 1. Increase the gas limit when sending the transaction using `--gas-limit`: ``` cast send $ROLLUP_ADDRESS \ "claimSequencerRewards(address)" \ [COINBASE_ADDRESS] \ --rpc-url $RPC_URL \ --private-key [YOUR_PRIVATE_KEY] \ --gas-limit 200000 ``` 2. Ensure your account has sufficient ETH to cover gas costs ### Insufficient Funds for Gas[​](#insufficient-funds-for-gas "Direct link to Insufficient Funds for Gas") **Symptom**: Transaction fails because the sending account has insufficient ETH. **Solution**: 1. Check your account balance: ``` cast balance [YOUR_ADDRESS] --rpc-url $RPC_URL ``` 2. Send ETH to your account to cover gas costs (recommended: at least 0.005 ETH) ### Wrong Network[​](#wrong-network "Direct link to Wrong Network") **Symptom**: Transaction fails or contract calls return unexpected results. **Solution**: 1. Verify your RPC URL points to the correct network (Ethereum mainnet) 2. Verify the Rollup contract address matches your target network 3. Check your account has ETH on the correct network ## Best Practices[​](#best-practices "Direct link to Best Practices") **Claim Regularly**: Claim rewards periodically to reduce accumulated balances in the Rollup contract. This minimizes risk and simplifies accounting. **Monitor Pending Rewards**: Set up automated scripts to query pending rewards and alert you when they exceed a threshold. **Use Keystore Files**: Avoid exposing private keys in command history. Use keystore files or hardware wallets for production operations. **Verify Before Claiming**: Check pending rewards before claiming to ensure the transaction justifies the gas cost. **Track Claim History**: Keep records of claim transactions for accounting purposes using transaction hashes on blockchain explorers. **Coordinate with Delegators**: If operating with delegated stake, communicate with delegators about claiming and distribution schedules. ## Next Steps[​](#next-steps "Direct link to Next Steps") * Set up [monitoring](/operate/operators/monitoring.md) to track reward accumulation automatically * Learn about [becoming a staking provider](/operate/operators/setup/become_a_staking_provider.md) if operating with delegators * Review [useful commands](/operate/operators/sequencer-management/useful-commands.md) for other sequencer queries * Join the [Aztec Discord](https://discord.gg/aztec) for operator support and community discussions --- # Governance and Proposal Process ## Overview[​](#overview "Direct link to Overview") This guide shows you how to participate in protocol governance as a sequencer. You'll learn how to signal support for protocol upgrades, create proposals, and vote on governance decisions that shape the Aztec network. Conceptual Background Before diving into the practical steps, you may want to understand the underlying concepts: * [Governance Overview](/participate/governance.md) - How the governance system works * [Proposal Lifecycle](/participate/governance/proposal-lifecycle.md) - The stages from signaling to execution * [Voting](/participate/governance/voting.md) - How voting power and delegation work * [GSE and Stake Mobility](/participate/governance/gse.md) - How your stake moves during upgrades ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before proceeding, you should: * Have a running sequencer node (see [Sequencer Setup Guide](/operate/operators/setup/sequencer_management.md)) * Understand [how governance works](/participate/governance.md) ## Understanding Governance Components[​](#understanding-governance-components "Direct link to Understanding Governance Components") ### Payloads[​](#payloads "Direct link to Payloads") Protocol upgrades consist of a series of commands that execute on protocol contracts or replace contract references. You define these steps in a contract called a **payload** that you deploy on Ethereum. This guide assumes the payload already exists at a known address. You'll participate in the payload's journey through signaling, proposal creation, voting, and execution. Always Verify Payloads Before signaling support or voting, always: 1. Verify the payload address on Etherscan or your preferred block explorer 2. Review the `getActions()` function to understand what changes the payload will make 3. Check if the payload has been audited (if applicable) 4. Discuss the proposal with the community on [Aztec Discord](https://discord.gg/aztec) Never signal or vote for a payload you haven't personally verified. Here's an example payload structure: ``` contract UpgradePayload is IPayload { IRegistry public immutable REGISTRY; address public NEW_ROLLUP = address(new FakeRollup()); constructor(IRegistry _registry) { REGISTRY = _registry; } function getActions() external view override(IPayload) returns (IPayload.Action[] memory) { IPayload.Action[] memory res = new IPayload.Action[](1); res[0] = Action({ target: address(REGISTRY), data: abi.encodeWithSelector(REGISTRY.addRollup.selector, NEW_ROLLUP) }); return res; } function getURI() external pure override(IPayload) returns (string memory) { return "UpgradePayload"; } } ``` If this payload's proposal passes governance voting, the governance contract executes `addRollup` on the `Registry` contract. ### Contract Addresses[​](#contract-addresses "Direct link to Contract Addresses") Key contracts you'll use: * **Governance Proposer**: Handles payload signaling and proposal creation * **Governance Staking Escrow (GSE)**: Manages stake delegation and voting * **Governance**: Executes approved proposals * **Rollup**: Your sequencer stakes here and defaults to delegating voting power here **To obtain these contract addresses:** Check your sequencer logs at startup for the line beginning with `INFO: node Aztec Node started on chain...` ### Governance Lifecycle Overview[​](#governance-lifecycle-overview "Direct link to Governance Lifecycle Overview") The governance process follows these stages: 1. **Signaling**: Sequencers signal support for a payload when proposing blocks. A payload needs a quorum of support to be promoted to a proposal. Signaling can start any time from the moment a payload is deployed. 2. **Proposal Creation**: After reaching quorum, anyone can submit the payload as an official proposal. 3. **Voting Delay** (3 days): A mandatory waiting period before voting opens (allows time for community review). 4. **Voting Period** (7 days): Users who hold stake in the network vote on the proposal using their staked tokens. A proposal passes if it receives at least 20% quorum, 2/3 of votes are "yea", and a minimum of 500 validators' worth of voting power is cast. 5. **Execution Delay** (30 days): After passing the vote, another mandatory delay before execution (allows time for node upgrades). 6. **Execution**: Anyone can execute the proposal, which applies the changes. There is a 7-day grace period after the execution delay during which the proposal can still be executed. ## Signaling Support for a Payload[​](#signaling-support-for-a-payload "Direct link to Signaling Support for a Payload") As a sequencer, you initiate proposals through signaling. When you propose a block, you can automatically signal support for a specific payload. Once enough sequencers signal support within a round, the payload qualifies to become an official proposal. ### How Signaling Works[​](#how-signaling-works "Direct link to How Signaling Works") * Only you can signal during slots when you're the block proposer * Your sequencer node automatically calls `signal` on the `GovernanceProposer` contract when proposing a block (if you've configured a payload address) * Rounds consist of 1000 slots each (20 hours at 72 seconds per slot). At every 1000-slot boundary, the system checks if any payload has received 600 or more signals (the quorum threshold, which is 60% of the round size) * Payloads that reach quorum can be submitted as official proposals by anyone ### Configure Your Signaling Preference[​](#configure-your-signaling-preference "Direct link to Configure Your Signaling Preference") Use the `setConfig` method on your node's admin interface to specify which payload address you want to signal support for. ``` docker exec -it aztec-sequencer curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{ "jsonrpc":"2.0", "method":"nodeAdmin_setConfig", "params":[{"governanceProposerPayload":"0x1234567890abcdef1234567890abcdef12345678"}], "id":1 }' ``` Replace `0x1234567890abcdef1234567890abcdef12345678` with your actual payload contract address and `aztec-sequencer` with your container name. Expected response: ``` {"jsonrpc":"2.0","id":1} ``` ### Verify Your Configuration[​](#verify-your-configuration "Direct link to Verify Your Configuration") Use the `getConfig` method to verify the payload address: ``` docker exec -it aztec-sequencer curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{ "jsonrpc":"2.0", "method":"nodeAdmin_getConfig", "id":1 }' ``` Search for `governanceProposerPayload` in the response to confirm it matches your configured address. Once configured, your sequencer automatically signals support for this payload each time you propose a block. Each signal counts toward the quorum requirement. ## Creating a Proposal[​](#creating-a-proposal "Direct link to Creating a Proposal") Once a payload receives the required quorum (600 signals in a 1000-slot round), you or any user can call `submitRoundWinner` on the `GovernanceProposer` contract to officially create the proposal. ### Submit the Payload[​](#submit-the-payload "Direct link to Submit the Payload") ``` cast send [GOVERNANCE_PROPOSER_ADDRESS] \ "submitRoundWinner(uint256)" [ROUND_NUMBER] \ --rpc-url [YOUR_RPC_URL] \ --private-key [YOUR_PRIVATE_KEY] ``` To find the current round number: ``` # Get the current round from the GovernanceProposer contract cast call [GOVERNANCE_PROPOSER_ADDRESS] \ "getCurrentRound()" \ --rpc-url [YOUR_RPC_URL] ``` ### Verify the Created Proposal[​](#verify-the-created-proposal "Direct link to Verify the Created Proposal") After creation, you can query the proposal in the governance contract: ``` # Get the total proposal count cast call [GOVERNANCE_CONTRACT_ADDRESS] \ "proposalCount()" \ --rpc-url [YOUR_RPC_URL] # Query the latest proposal (count - 1, since proposals are zero-indexed) cast call [GOVERNANCE_CONTRACT_ADDRESS] \ "getProposal(uint256)" $((PROPOSAL_COUNT - 1)) \ --rpc-url [YOUR_RPC_URL] ``` This returns the `Proposal` struct data, which includes: * The payload address * Creation timestamp * Voting start and end times * Current vote tallies ## Voting on Proposals[​](#voting-on-proposals "Direct link to Voting on Proposals") Once a payload becomes a proposal, there's a mandatory waiting period before voting opens. You can vote in two ways: through default delegation to the rollup contract, or by delegating to an address you control for custom voting. ### Default Voting Through the Rollup[​](#default-voting-through-the-rollup "Direct link to Default Voting Through the Rollup") By default, when you stake as a sequencer, you delegate your voting power to the rollup contract through the GSE (Governance Staking Escrow). The rollup automatically votes "yea" on proposals created through the `GovernanceProposer` using **all** delegated stake from **all** sequencers in that rollup. **Key points:** * If you signaled for a payload, your stake votes "yea" automatically—no additional action needed * If you didn't signal but other sequencers did, your stake still votes "yea" when the rollup votes * To vote differently, you must change your delegation before voting opens (see Custom Voting below) Anyone can trigger the rollup vote: ``` cast send [ROLLUP_ADDRESS] \ "vote(uint256)" [PROPOSAL_ID] \ --rpc-url [YOUR_RPC_URL] \ --private-key [YOUR_PRIVATE_KEY] ``` ### Custom Voting: Delegating to Your Own Address[​](#custom-voting-delegating-to-your-own-address "Direct link to Custom Voting: Delegating to Your Own Address") If you want to vote differently on a proposal (for example, to vote "nay" or to split your voting power), you can delegate your stake to an address you control. This removes your stake's voting power from the rollup's control and gives it to your chosen address. Voting Power Timestamp Voting power is timestamped at the moment a proposal becomes "active" (when the voting period opens). You must complete delegation **before** the voting period begins to use your voting power for that proposal. Check the proposal's voting start time and delegate well in advance. #### Step 1: Delegate Your Stake[​](#step-1-delegate-your-stake "Direct link to Step 1: Delegate Your Stake") Use the GSE contract to delegate to an address you control: ``` cast send [GSE_ADDRESS] \ "delegate(address,address,address)" \ [ROLLUP_ADDRESS] \ [YOUR_ATTESTER_ADDRESS] \ [YOUR_DELEGATEE_ADDRESS] \ --rpc-url [YOUR_RPC_URL] \ --private-key [YOUR_WITHDRAWER_PRIVATE_KEY] ``` * `[ROLLUP_ADDRESS]`: The rollup contract where you staked * `[YOUR_ATTESTER_ADDRESS]`: Your sequencer's attester address * `[YOUR_DELEGATEE_ADDRESS]`: The address that will vote (often the same as your attester address, or another address you control) * You must sign this transaction with your **withdrawer** private key (the withdrawer that you specified when you initially deposited to the rollup) #### Step 2: Vote Through GSE[​](#step-2-vote-through-gse "Direct link to Step 2: Vote Through GSE") Once you've delegated to an address you control, that address can vote directly on proposals: ``` # Vote "yea" with your voting power cast send [GSE_ADDRESS] \ "vote(uint256,uint256,bool)" \ [PROPOSAL_ID] \ [AMOUNT] \ true \ --rpc-url [YOUR_RPC_URL] \ --private-key [YOUR_DELEGATEE_PRIVATE_KEY] ``` * `[AMOUNT]`: The amount of voting power to use (can be your full stake or a partial amount) * You can vote multiple times with different amounts to split your voting power between "yea" and "nay" if desired * To vote "nay" with your voting power, set the boolean in the code above to false #### Step 3: Verify Your Vote[​](#step-3-verify-your-vote "Direct link to Step 3: Verify Your Vote") Check that your vote was recorded: ``` # Check vote counts for a proposal # Note: This returns the proposal's vote tallies from the Governance contract, not GSE cast call [GOVERNANCE_CONTRACT_ADDRESS] \ "getProposal(uint256)" [PROPOSAL_ID] \ --rpc-url [YOUR_RPC_URL] ``` This returns the current "yea" and "nay" vote tallies. ## Executing Proposals[​](#executing-proposals "Direct link to Executing Proposals") When a proposal receives sufficient support, it passes. After passing, there's another mandatory delay before the proposal becomes executable. Once executable, anyone can trigger execution. ### Execute the Proposal[​](#execute-the-proposal "Direct link to Execute the Proposal") Once the proposal state is Executable, anyone can execute it: ``` cast send [GOVERNANCE_CONTRACT_ADDRESS] \ "execute(uint256)" [PROPOSAL_ID] \ --rpc-url [YOUR_RPC_URL] \ --private-key [YOUR_PRIVATE_KEY] ``` After execution, the governance contract performs all actions defined in the payload. The protocol changes become effective immediately. ### Upgrade Your Node[​](#upgrade-your-node "Direct link to Upgrade Your Node") **Critical**: Once a proposal executes, you must upgrade your node software to track the protocol changes. Monitor proposals closely from the signaling stage through execution. When a vote passes, prepare to upgrade your node software during the execution delay period, so you're ready when the proposal becomes effective. In practice, this often means running multiple nodes, with one node being on the version upgraded from, and one being on the version being upgraded to. Stake Mobility If you deposited with `moveWithLatestRollup = true`, your stake automatically becomes available to the new rollup after an upgrade. If you used `false`, you'll need to manually exit and re-enter. See [GSE and Stake Mobility](/participate/governance/gse.md) for details. ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### My Signal Isn't Being Recorded[​](#my-signal-isnt-being-recorded "Direct link to My Signal Isn't Being Recorded") **Symptoms**: You configured a payload address, but the signal count isn't increasing. **Solutions**: 1. Verify you're actually proposing blocks in slots assigned to you 2. Check your node logs for errors related to governance signaling 3. Verify the payload address is correct and matches the format (0x...) 4. Confirm the `GovernanceProposer` contract address is correct for your network ### I Can't Delegate My Voting Power[​](#i-cant-delegate-my-voting-power "Direct link to I Can't Delegate My Voting Power") **Symptoms**: Delegation transaction fails or reverts. **Solutions**: 1. Verify you're using your **withdrawer** private key, not your attester key 2. Confirm you have stake deposited in the rollup 3. Check that the addresses are correct (rollup, attester, delegatee) 4. Ensure the rollup address matches where you actually staked ### My Vote Transaction Fails[​](#my-vote-transaction-fails "Direct link to My Vote Transaction Fails") **Symptoms**: Vote transaction reverts or fails. **Solutions**: 1. Check the proposal is in the "Active" state (voting period is open) 2. Verify you delegated before the voting period started (voting power is timestamped) 3. Confirm you have sufficient voting power (check your stake amount) 4. Ensure you're not trying to vote with more power than you have 5. Check you're using the correct private key (delegatee key, not withdrawer) ### How Do I Check When Voting Opens?[​](#how-do-i-check-when-voting-opens "Direct link to How Do I Check When Voting Opens?") Query the proposal to see the voting timeline: ``` cast call [GOVERNANCE_CONTRACT_ADDRESS] \ "getProposal(uint256)" [PROPOSAL_ID] \ --rpc-url [YOUR_RPC_URL] ``` The returned data includes timestamps for: * Voting start time * Voting end time ## Summary[​](#summary "Direct link to Summary") As a sequencer participating in governance: 1. **Signal support**: Configure your node with a payload address. Your node automatically signals when proposing blocks. 2. **Vote**: Your delegated stake automatically votes "yea" on proposals created through sequencer signaling. You don't need to take additional action if you support the proposal. To vote differently, delegate your stake to an address you control before voting opens, then vote directly through the GSE contract. 3. **Upgrade promptly**: Monitor proposals and upgrade your node software after execution to stay in sync with protocol changes. ## Next Steps[​](#next-steps "Direct link to Next Steps") * Learn about [sequencer setup](/operate/operators/setup/sequencer_management.md) for operating your node * Join the [Aztec Discord](https://discord.gg/aztec) to participate in governance discussions and stay informed about upcoming proposals --- # Slashing and Offenses ## Overview[​](#overview "Direct link to Overview") This guide explains how the Aztec network's slashing mechanism works and how your sequencer automatically participates in detecting and voting on validator offenses. You'll learn about the Tally Model of slashing, the types of offenses that are automatically detected, and how to configure your sequencer's slashing behavior. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before proceeding, you should: * Have a running sequencer node (see [Sequencer Setup Guide](/operate/operators/setup/sequencer_management.md)) * Understand that slashing actions are executed automatically when you propose blocks * Have the Sentinel enabled if you want to detect inactivity offenses ## Understanding the Tally Model of Slashing[​](#understanding-the-tally-model-of-slashing "Direct link to Understanding the Tally Model of Slashing") The Aztec network uses a consensus-based slashing mechanism where validators vote on individual validator offenses during block proposal. ### How Slashing Works[​](#how-slashing-works "Direct link to How Slashing Works") **Automatic Detection**: Your sequencer runs watchers that continuously monitor the network and automatically detect slashable offenses committed by other validators. **Voting Through Proposals**: Time is divided into slashing rounds (typically 128 L2 slots per round). When you propose a block during round N, your sequencer automatically votes on which validators from round N-2 should be slashed. This 2-round offset gives the network time to detect offenses before voting. **Vote Encoding**: Votes are encoded as bytes where each validator's vote is represented by 2 bits indicating the slash amount (0-3 slash units). The L1 contract tallies these votes and slashes validators that reach quorum. **Execution**: After a round ends, there's an execution delay period (approximately 3 days) during which the slashing vetoer can pause execution if needed. Once the delay passes, anyone can execute the round to apply the slashing. ### Slashing Rounds and Offsets[​](#slashing-rounds-and-offsets "Direct link to Slashing Rounds and Offsets") ``` Round 1 (Grace Period): No voting happens Round 2 (Grace Period): No voting happens Round 3: Proposers vote on offenses from Round 1 (which are typically forgiven due to grace period) Round 4: Proposers vote on offenses from Round 2 Round N: Proposers vote on offenses from Round N-2 ``` **Key parameters**: * **Round Size**: 128 L2 slots (approximately 2.6 hours at 72 seconds per slot) * **Slashing Offset**: 2 rounds (proposers in round N vote on offenses from round N-2) * **Execution Delay**: 28 rounds (\~3 days) * **Grace Period**: First 8,400 slots after the rollup becomes canonical (\~7 days; configurable per node via `SLASH_GRACE_PERIOD_L2_SLOTS`) ### Slashing Amounts[​](#slashing-amounts "Direct link to Slashing Amounts") The L1 contract defines three fixed slashing tiers that can be configured for different offenses. These amounts are set on L1 deployment and can only be changed via governance. Network Configuration On mainnet, **all offenses are currently configured to slash 2,000 tokens (1% of the Activation Threshold - the minimum stake required to join the validator set)**. A validator is ejected when a slash would drop its stake below the rollup's local ejection threshold (190,000 tokens on mainnet, 95% of the Activation Threshold). A validator that joined at exactly the 200,000 token Activation Threshold can therefore absorb **5 slashes** (totaling 5% of its stake) and is ejected by the 6th. See [Ejection from the Validator Set](#ejection-from-the-validator-set) for details. ## Slashable Offenses[​](#slashable-offenses "Direct link to Slashable Offenses") Your sequencer automatically detects and votes to slash the following offenses: ### 1. Inactivity[​](#1-inactivity "Direct link to 1. Inactivity") **What it is**: A validator fails to attest to block proposals when selected for committee duty, or fails to propose a block when selected as proposer. **Detection criteria**: * Measured **per epoch** for validators on the committee during that epoch (committees are assigned per epoch and remain constant for all slots in that epoch) * The Sentinel calculates: `(missed_proposals + missed_attestations) / (total_proposals + total_attestations)` * A validator is considered inactive for an epoch if this ratio meets or exceeds `SLASH_INACTIVITY_TARGET_PERCENTAGE` (e.g., 0.8 = 80% or more duties missed) * Requires **consecutive committee participation with inactivity**: Must be inactive for N consecutive epochs where they were on the committee (configured via `SLASH_INACTIVITY_CONSECUTIVE_EPOCH_THRESHOLD=2`). Epochs where the validator was not on the committee are not counted, so a validator inactive in epochs 1, 3, and 5 meets the threshold for 3 consecutive inactive epochs even though epochs 2 and 4 are skipped. **Proposed penalty**: 1% of stake **Note**: Requires the Sentinel to be enabled (`SENTINEL_ENABLED=true`). The Sentinel tracks attestation and proposal activity for all validators. ### 2. Valid Epoch Not Proven[​](#2-valid-epoch-not-proven "Direct link to 2. Valid Epoch Not Proven") **What it is**: An epoch was not proven within the proof submission window, even though all data was available and the epoch was valid. **Detection criteria**: * An epoch gets pruned (removed from the chain) * Your node can re-execute all transactions from that epoch * The state roots match the original epoch (indicating it could have been proven) **Proposed penalty**: 0% (disabled for initial deployment) **Responsibility**: The entire committee of the pruned epoch is slashed. ### 3. Data Withholding[​](#3-data-withholding "Direct link to 3. Data Withholding") **What it is**: The committee failed to make transaction data publicly available, preventing the epoch from being proven. **Detection criteria**: * An epoch gets pruned * Your node cannot obtain all the transactions needed to re-execute the epoch * The data was not propagated to the sequencer set before the proof submission window ended **Proposed penalty**: 0% (disabled for initial deployment) **Responsibility**: The entire committee from the pruned epoch is slashed for failing to propagate data. ### 4. Proposed Insufficient Attestations[​](#4-proposed-insufficient-attestations "Direct link to 4. Proposed Insufficient Attestations") **What it is**: A proposer submitted a block to L1 without collecting enough valid committee attestations. **Detection criteria**: * Block published to L1 has fewer than 2/3 + 1 attestations from the committee * Your node detects this through L1 block validation **Proposed penalty**: 1% of stake ### 5. Proposed Incorrect Attestations[​](#5-proposed-incorrect-attestations "Direct link to 5. Proposed Incorrect Attestations") **What it is**: A proposer submitted a block with invalid signatures or signatures from non-committee members. **Detection criteria**: * Block contains attestations with invalid ECDSA signatures * Block contains signatures from addresses not in the committee **Proposed penalty**: 1% of stake ### 6. Attested to Descendant of Invalid Block[​](#6-attested-to-descendant-of-invalid-block "Direct link to 6. Attested to Descendant of Invalid Block") **What it is**: A validator attested to a block that builds on top of an invalid block. **Detection criteria**: * A validator attests to block B * Block B's parent block has invalid or insufficient attestations * Your node has previously identified the parent as invalid **Proposed penalty**: 1% of stake **Note**: Validators should only attest to blocks that build on valid chains with proper attestations. ## Configuring Your Sequencer for Slashing[​](#configuring-your-sequencer-for-slashing "Direct link to Configuring Your Sequencer for Slashing") The slashing module runs automatically when your sequencer is enabled. You can configure its behavior using environment variables or the node's admin API. Remember to enable the Sentinel if you want to detect inactivity offenses. ### Environment Variables[​](#environment-variables "Direct link to Environment Variables") Your sequencer comes pre-configured with default slashing settings. You can optionally override these defaults by setting environment variables before starting your node. **Default configuration:** ``` # Grace period - offenses during the first N slots are not slashed SLASH_GRACE_PERIOD_L2_SLOTS=128 # Default: first round is grace period # Inactivity detection (requires SENTINEL_ENABLED=true) SLASH_INACTIVITY_TARGET_PERCENTAGE=0.8 # Slash if missed proposals + attestations >= 80% SLASH_INACTIVITY_CONSECUTIVE_EPOCH_THRESHOLD=2 # Must be inactive for 2+ epochs SLASH_INACTIVITY_PENALTY=2000000000000000000000 # 2000 tokens (1%) # Sentinel configuration (required for inactivity detection) SENTINEL_ENABLED=true # Must be true to detect inactivity offenses SENTINEL_HISTORY_LENGTH_IN_EPOCHS=100 # Track 100 epochs of history # Epoch prune and data withholding penalties (disabled by default) SLASH_PRUNE_PENALTY=0 # Set to >0 to enable SLASH_DATA_WITHHOLDING_PENALTY=0 # Set to >0 to enable # Invalid attestations and blocks SLASH_PROPOSE_INVALID_ATTESTATIONS_PENALTY=2000000000000000000000 # 2000 tokens SLASH_ATTEST_DESCENDANT_OF_INVALID_PENALTY=2000000000000000000000 # 2000 tokens SLASH_INVALID_BLOCK_PENALTY=2000000000000000000000 # 2000 tokens # Offense expiration SLASH_OFFENSE_EXPIRATION_ROUNDS=4 # Offenses older than 4 rounds are dropped # Execution behavior SLASH_EXECUTE_ROUNDS_LOOK_BACK=4 # Check 4 rounds back for executable slashing rounds ``` ### Runtime Configuration via API[​](#runtime-configuration-via-api "Direct link to Runtime Configuration via API") You can update slashing configuration while your node is running using the `nodeAdmin_setConfig` method: **CLI Method**: ``` curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{ "jsonrpc":"2.0", "method":"nodeAdmin_setConfig", "params":[{ "slashInactivityPenalty":"2000000000000000000000", "slashInactivityTargetPercentage":0.9 }], "id":1 }' ``` **Docker Method**: ``` docker exec -it aztec-sequencer curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{ "jsonrpc":"2.0", "method":"nodeAdmin_setConfig", "params":[{ "slashInactivityPenalty":"2000000000000000000000", "slashInactivityTargetPercentage":0.9 }], "id":1 }' ``` ### Excluding Validators from Slashing[​](#excluding-validators-from-slashing "Direct link to Excluding Validators from Slashing") You can configure your node to always or never slash specific validators: ``` # Always slash these validators (regardless of detected offenses) SLASH_VALIDATORS_ALWAYS=0x1234...,0x5678... # Never slash these validators (even if offenses are detected) SLASH_VALIDATORS_NEVER=0xabcd...,0xef01... ``` **Note**: Validators in `SLASH_VALIDATORS_NEVER` take priority. If a validator appears in both lists, they won't be slashed. **Automatic protection**: Your own validator addresses (from your keystore) are automatically added to `SLASH_VALIDATORS_NEVER` unless you set `slashSelfAllowed=true` via the node admin API. ### Verify Your Configuration[​](#verify-your-configuration "Direct link to Verify Your Configuration") Check your current slashing configuration: **CLI Method**: ``` curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{ "jsonrpc":"2.0", "method":"nodeAdmin_getConfig", "id":1 }' ``` **Docker Method**: ``` docker exec -it aztec-sequencer curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{ "jsonrpc":"2.0", "method":"nodeAdmin_getConfig", "id":1 }' ``` Look for fields starting with `slash` in the response to verify your settings. ## How Automatic Slashing Works[​](#how-automatic-slashing-works "Direct link to How Automatic Slashing Works") Once configured, your sequencer handles slashing automatically: ### 1. Continuous Offense Detection[​](#1-continuous-offense-detection "Direct link to 1. Continuous Offense Detection") Watchers run in the background, monitoring: * Block attestations via the Sentinel (when enabled) * Invalid blocks from the P2P network * Chain prunes and epoch validation * L1 block data for attestation validation ### 2. Offense Storage[​](#2-offense-storage "Direct link to 2. Offense Storage") When a watcher detects an offense, it's automatically stored with: * Validator address * Offense type * Epoch or slot number * Penalty amount Offenses are kept until they're voted on or expire after the configured number of rounds. ### 3. Automatic Voting[​](#3-automatic-voting "Direct link to 3. Automatic Voting") When you're selected as a block proposer: 1. Your sequencer retrieves offenses from 2 rounds ago (the slashing offset) 2. It filters out validators in your `SLASH_VALIDATORS_NEVER` list 3. It adds synthetic offenses for validators in your `SLASH_VALIDATORS_ALWAYS` list 4. Votes are encoded as a byte array, with each validator's vote represented by two bits specifying the proposed slash amount (0–3 units) 5. The votes are submitted to L1 as part of your proposal transaction **You don't need to take any manual action** - this happens automatically during block proposal. ### 4. Round Execution[​](#4-round-execution "Direct link to 4. Round Execution") When slashing rounds become executable (after the execution delay): * Your sequencer checks if there are rounds ready to execute * If you're the proposer and a round is ready, your node includes the execution call in your proposal * This triggers the L1 contract to tally votes and slash validators that reached quorum ## Understanding the Slashing Vetoer[​](#understanding-the-slashing-vetoer "Direct link to Understanding the Slashing Vetoer") The slashing vetoer is an independent security group that can pause slashing to protect validators from unfair slashing due to software bugs. **Execution Delay**: All slashing proposals have a \~3 day execution delay (28 rounds on mainnet) during which the vetoer can review and potentially block execution. **Temporary Disable**: The vetoer can disable all slashing for up to 3 days if needed, with the ability to extend this period. **Purpose**: This failsafe protects sequencers from being unfairly slashed due to client software bugs or network issues that might cause false positives in offense detection. ## Ejection from the Validator Set[​](#ejection-from-the-validator-set "Direct link to Ejection from the Validator Set") If a slash would drop a validator's stake below the rollup's **local ejection threshold**, the validator's entire remaining stake is withdrawn instead of just the slashed amount: the slash is burned and the remainder is sent to their registered withdrawer address after the exit delay. **Local Ejection Threshold**: 190,000 tokens on mainnet (95% of the 200,000 token Activation Threshold). This is a per-rollup parameter, and other networks use different values (199,000 tokens on testnet). With mainnet's 2,000 token (1%) slash amounts, a validator that joined at exactly the Activation Threshold can absorb 5 slashes and is ejected by the 6th. A separate protocol-level ejection threshold (100,000 tokens, 50% of the Activation Threshold) applies to all stake withdrawals at the GSE level, but with current parameters the local ejection threshold is the one that triggers ejection from slashing. ## Monitoring Slashing Activity[​](#monitoring-slashing-activity "Direct link to Monitoring Slashing Activity") ### Check Pending Offenses[​](#check-pending-offenses "Direct link to Check Pending Offenses") Monitor offenses your node has detected but not yet voted on by checking your node logs: ``` # Look for these log messages grep "Adding pending offense" /path/to/node/logs grep "Voting to slash" /path/to/node/logs ``` ### View Executed Slashing Rounds[​](#view-executed-slashing-rounds "Direct link to View Executed Slashing Rounds") Your node logs when slashing rounds are executed: ``` grep "Slashing round.*has been executed" /path/to/node/logs ``` ### Query L1 Contract State[​](#query-l1-contract-state "Direct link to Query L1 Contract State") You can query the TallySlashingProposer contract to see voting activity: ``` # Get current round information cast call [TALLY_SLASHING_PROPOSER_ADDRESS] \ "getCurrentRound()" \ --rpc-url [YOUR_RPC_URL] # Check a specific round's vote count cast call [TALLY_SLASHING_PROPOSER_ADDRESS] \ "getRound(uint256)" [ROUND_NUMBER] \ --rpc-url [YOUR_RPC_URL] ``` ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Slashing Module Not Running[​](#slashing-module-not-running "Direct link to Slashing Module Not Running") **Symptom**: No slashing-related logs appear in your node output. **Solutions**: 1. Verify your node is running as a validator (not just an observer) 2. Check that `disableValidator` is not set to `true` in your config 3. Confirm the rollup contract has a slashing proposer configured 4. Restart your node and check for errors during slasher initialization ### Inactivity Offenses Not Detected[​](#inactivity-offenses-not-detected "Direct link to Inactivity Offenses Not Detected") **Symptom**: Your node doesn't detect inactivity offenses even when validators miss attestations. **Solutions**: 1. Enable the Sentinel: Set `SENTINEL_ENABLED=true` 2. Verify Sentinel is tracking data: Check logs for "Sentinel" messages 3. Ensure `SLASH_INACTIVITY_PENALTY` is greater than 0 4. Check that `SENTINEL_HISTORY_LENGTH_IN_EPOCHS` is configured appropriately (see configuration section) 5. Remember: Validators need to be inactive for consecutive epochs (threshold: 2 by default) ### Own Validators Being Slashed[​](#own-validators-being-slashed "Direct link to Own Validators Being Slashed") **Symptom**: Your node is voting to slash your own validators. **Solutions**: 1. Verify that `slashSelfAllowed` is not set to `true` 2. Check that your validator addresses from the keystore are being automatically added to `SLASH_VALIDATORS_NEVER` 3. Manually add your addresses to `SLASH_VALIDATORS_NEVER` as a safeguard: ``` SLASH_VALIDATORS_NEVER=0xYourAddress1,0xYourAddress2 ``` ### Penalty Amounts Not Matching L1[​](#penalty-amounts-not-matching-l1 "Direct link to Penalty Amounts Not Matching L1") **Symptom**: Your configured penalties don't result in slashing on L1. **Solutions**: 1. For the current network, all penalties should be set to `2000000000000000000000` (2000 tokens, 1%) 2. Verify your penalty configuration matches the default values shown in the Environment Variables section ## Best Practices[​](#best-practices "Direct link to Best Practices") **Enable the Sentinel**: If you want to participate in inactivity slashing, make sure `SENTINEL_ENABLED=true`. This is the only way to detect validators who go offline. **Use Grace Periods**: Set `SLASH_GRACE_PERIOD_L2_SLOTS` to avoid slashing validators during the initial network bootstrap period when issues are more likely. **Monitor Your Offenses**: Regularly check your logs to see what offenses your node is detecting and voting on. This helps you verify your slashing configuration is working as expected. **Don't Disable Default Protections**: Unless you explicitly want to slash your own validators, keep `slashSelfAllowed` at its default (`false`) to avoid accidentally voting against yourself. **Understand the Impact**: Remember that slashing is permanent and affects validators' stake. Only configure `SLASH_VALIDATORS_ALWAYS` for validators you have strong evidence of malicious behavior. **Stay Updated**: Monitor Aztec Discord and governance proposals for changes to slashing parameters or new offense types being added to the protocol. ## Summary[​](#summary "Direct link to Summary") As a sequencer operator: 1. **Slashing is automatic**: Your sequencer detects offenses and votes during block proposals without manual intervention 2. **Configuration is flexible**: Use environment variables or runtime API calls to adjust penalties and behavior 3. **Safety mechanisms exist**: Grace periods, vetoer controls, and automatic self-protection prevent unfair slashing 4. **Monitoring is important**: Check logs and L1 state to ensure your slasher is operating as expected ## Next Steps[​](#next-steps "Direct link to Next Steps") * Review [Governance and Proposal Process](/operate/operators/sequencer-management/creating_and_voting_on_proposals.md) to understand how slashing parameters can be changed * Set up [monitoring](/operate/operators/monitoring.md) to track your sequencer's slashing activity * Join the [Aztec Discord](https://discord.gg/aztec) to discuss slashing behavior and network health with other operators --- # Useful Commands ## Overview[​](#overview "Direct link to Overview") This reference provides commands for common sequencer operator tasks. You'll use Foundry's `cast` command to query onchain contract state, check sequencer status, and monitor governance processes. If you need help with something not covered here, visit the [Aztec Discord](https://discord.gg/aztec) in the `#operator-faq` channel. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before using these commands, ensure you have: * **Foundry installed** with the `cast` command available ([installation guide](https://book.getfoundry.sh/getting-started/installation)) * **Aztec CLI tool** installed (see [prerequisites guide](/operate/operators/prerequisites.md#aztec-toolchain)) * **Ethereum RPC endpoint** (execution layer) for the network you're querying * **Contract addresses** for your deployment (Registry, Rollup, Governance) ## Getting Started[​](#getting-started "Direct link to Getting Started") ### Set Up Your Environment[​](#set-up-your-environment "Direct link to Set Up Your Environment") For convenience, set your RPC URL as an environment variable: ``` export RPC_URL="https://your-ethereum-rpc-endpoint.com" ``` All examples below use `--rpc-url $RPC_URL`. In production, always include this flag with your actual RPC endpoint. ### Understanding Deployments[​](#understanding-deployments "Direct link to Understanding Deployments") Assume there are multiple deployments of Aztec, such as `mainnet` and `ignition-mainnet`. Each deployment has a unique Registry contract address that remains constant across upgrades. If a governance upgrade deploys a new rollup contract, the Registry contract address stays the same. ### Find the Registry Contract Address[​](#find-the-registry-contract-address "Direct link to Find the Registry Contract Address") The Registry contract is your entrypoint to all other contracts for a specific deployment. You'll need this address to discover other contract addresses. Contact the Aztec team or check the documentation for the Registry contract address for your target network (mainnet, ignition-mainnet, etc.). ### Get the Rollup Contract Address[​](#get-the-rollup-contract-address "Direct link to Get the Rollup Contract Address") Once you have the Registry address, retrieve the Rollup contract: ``` cast call [REGISTRY_CONTRACT_ADDRESS] "getCanonicalRollup()" --rpc-url $RPC_URL ``` Replace `[REGISTRY_CONTRACT_ADDRESS]` with your actual Registry contract address. **Example:** ``` cast call 0x1234567890abcdef1234567890abcdef12345678 "getCanonicalRollup()" --rpc-url $RPC_URL ``` This returns the Rollup contract address in hexadecimal format. ## Query the Sequencer Set[​](#query-the-sequencer-set "Direct link to Query the Sequencer Set") ### Get the GSE Contract Address[​](#get-the-gse-contract-address "Direct link to Get the GSE Contract Address") The GSE (Governance Staking Escrow) contract manages sequencer registrations and balances. Get its address from the Rollup contract: ``` cast call [ROLLUP_ADDRESS] "getGSE()" --rpc-url $RPC_URL ``` This returns the GSE contract address, which you'll need for some queries below. ### Count Active Sequencers[​](#count-active-sequencers "Direct link to Count Active Sequencers") Get the total number of active sequencers in the set: ``` cast call [ROLLUP_ADDRESS] "getActiveAttesterCount()" --rpc-url $RPC_URL ``` This returns the count of currently active sequencers as a hexadecimal number. ### List Sequencers by Index[​](#list-sequencers-by-index "Direct link to List Sequencers by Index") Retrieve individual sequencer addresses by their index (0-based): ``` cast call [ROLLUP_ADDRESS] "getAttesterAtIndex(uint256)" [INDEX] --rpc-url $RPC_URL ``` Replace: * `[ROLLUP_ADDRESS]` - Your Rollup contract address * `[INDEX]` - The index of the sequencer (starting from 0) **Example:** ``` # Get the first sequencer (index 0) cast call 0xabcdef1234567890abcdef1234567890abcdef12 "getAttesterAtIndex(uint256)" 0 --rpc-url $RPC_URL # Get the second sequencer (index 1) cast call 0xabcdef1234567890abcdef1234567890abcdef12 "getAttesterAtIndex(uint256)" 1 --rpc-url $RPC_URL ``` ### Check Sequencer Status[​](#check-sequencer-status "Direct link to Check Sequencer Status") Query the complete status and information for a specific sequencer: ``` cast call [ROLLUP_ADDRESS] "getAttesterView(address)" [ATTESTER_ADDRESS] --rpc-url $RPC_URL ``` Replace: * `[ROLLUP_ADDRESS]` - Your Rollup contract address * `[ATTESTER_ADDRESS]` - The sequencer's attester address you want to check **Example:** ``` cast call 0xabcdef1234567890abcdef1234567890abcdef12 "getAttesterView(address)" 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb --rpc-url $RPC_URL ``` ### Interpret the Response[​](#interpret-the-response "Direct link to Interpret the Response") The `getAttesterView` command returns an `AttesterView` struct containing: 1. **status** - The sequencer's current status code (see Status Codes below) 2. **effectiveBalance** - The sequencer's effective stake balance 3. **exit** - Exit information struct (if the sequencer is exiting): * `withdrawalId` - Withdrawal ID in the GSE contract * `amount` - Amount being withdrawn * `exitableAt` - Timestamp when withdrawal can be finalized * `recipientOrWithdrawer` - Address that receives funds or can initiate withdrawal * `isRecipient` - Whether the exit has a recipient set * `exists` - Whether an exit exists 4. **config** - Attester configuration struct: * `publicKey` - BLS public key (G1 point with x and y coordinates) * `withdrawer` - Address authorized to withdraw stake ### Get Individual Sequencer Information[​](#get-individual-sequencer-information "Direct link to Get Individual Sequencer Information") Query specific pieces of information using the GSE contract: ``` # Check if a sequencer is registered cast call [GSE_ADDRESS] "isRegistered(address,address)" [ROLLUP_ADDRESS] [ATTESTER_ADDRESS] --rpc-url $RPC_URL # Get sequencer's balance on this rollup instance cast call [GSE_ADDRESS] "balanceOf(address,address)" [ROLLUP_ADDRESS] [ATTESTER_ADDRESS] --rpc-url $RPC_URL # Get sequencer's effective balance (includes bonus if latest rollup) cast call [GSE_ADDRESS] "effectiveBalanceOf(address,address)" [ROLLUP_ADDRESS] [ATTESTER_ADDRESS] --rpc-url $RPC_URL # Get sequencer's configuration (withdrawer and public key) cast call [ROLLUP_ADDRESS] "getConfig(address)" [ATTESTER_ADDRESS] --rpc-url $RPC_URL # Get only the status cast call [ROLLUP_ADDRESS] "getStatus(address)" [ATTESTER_ADDRESS] --rpc-url $RPC_URL ``` ### Status Codes[​](#status-codes "Direct link to Status Codes") | Status | Name | Meaning | | ------ | ---------- | ------------------------------------------------------------------------------------------------------------------------------- | | 0 | NONE | The sequencer does not exist in the sequencer set | | 1 | VALIDATING | The sequencer is currently active and participating in consensus | | 2 | ZOMBIE | The sequencer is not active (balance fell below ejection threshold, possibly due to slashing) but still has funds in the system | | 3 | EXITING | The sequencer has initiated withdrawal and is in the exit delay period | ## Governance Operations[​](#governance-operations "Direct link to Governance Operations") ### Get Governance Contract Addresses[​](#get-governance-contract-addresses "Direct link to Get Governance Contract Addresses") First, get the Governance contract from the Registry, then query it for the GovernanceProposer contract: ``` # Get the Governance contract cast call [REGISTRY_ADDRESS] "getGovernance()" --rpc-url $RPC_URL # Get the GovernanceProposer contract cast call [GOVERNANCE_ADDRESS] "governanceProposer()" --rpc-url $RPC_URL ``` Replace `[REGISTRY_ADDRESS]` and `[GOVERNANCE_ADDRESS]` with your actual addresses. ### Check Governance Quorum Requirements[​](#check-governance-quorum-requirements "Direct link to Check Governance Quorum Requirements") Query the quorum parameters for the governance system: ``` # Get the signaling round size (in L2 slots) cast call [GOVERNANCE_PROPOSER_ADDRESS] "ROUND_SIZE()" --rpc-url $RPC_URL # Get the number of signals required for quorum in any single round cast call [GOVERNANCE_PROPOSER_ADDRESS] "QUORUM_SIZE()" --rpc-url $RPC_URL ``` **What these values mean:** * **ROUND\_SIZE()** - The size of any signaling round, measured in L2 slots (e.g., 1000 slots on mainnet) * **QUORUM\_SIZE()** - The number of signals needed within a round for a payload to reach quorum (e.g., 600 signals on mainnet, which is 60% of ROUND\_SIZE) ### Find the Current Round Number[​](#find-the-current-round-number "Direct link to Find the Current Round Number") Calculate which governance round corresponds to a specific L2 slot: ``` cast call [GOVERNANCE_PROPOSER_ADDRESS] "computeRound(uint256)" [SLOT_NUMBER] --rpc-url $RPC_URL ``` Replace: * `[GOVERNANCE_PROPOSER_ADDRESS]` - Your GovernanceProposer contract address * `[SLOT_NUMBER]` - The L2 slot number you want to check This returns the round number in hexadecimal format. Convert it to decimal for use in the next command. **Example:** ``` # Check which round slot 5000 belongs to cast call 0x9876543210abcdef9876543210abcdef98765432 "computeRound(uint256)" 5000 --rpc-url $RPC_URL # Output: 0x0000000000000000000000000000000000000000000000000000000000000005 (round 5) ``` ### Check Signal Count for a Payload[​](#check-signal-count-for-a-payload "Direct link to Check Signal Count for a Payload") Check how many sequencers have signaled support for a specific payload in a given round: ``` cast call [GOVERNANCE_PROPOSER_ADDRESS] "signalCount(address,uint256,address)" [ROLLUP_ADDRESS] [ROUND_NUMBER] [PAYLOAD_ADDRESS] --rpc-url $RPC_URL ``` Replace: * `[GOVERNANCE_PROPOSER_ADDRESS]` - Your GovernanceProposer contract address * `[ROLLUP_ADDRESS]` - Your Rollup contract address * `[ROUND_NUMBER]` - The round number as a decimal integer (not hex) * `[PAYLOAD_ADDRESS]` - The address of the payload contract you're checking **Example:** ``` cast call 0x9876543210abcdef9876543210abcdef98765432 "signalCount(address,uint256,address)" 0xabcdef1234567890abcdef1234567890abcdef12 5 0x1111111111111111111111111111111111111111 --rpc-url $RPC_URL ``` This returns the number of signals the payload has received in that round. Compare this to the quorum threshold (QUORUM\_SIZE) to determine if the payload can be promoted to a proposal. ### Get Current Proposal Count[​](#get-current-proposal-count "Direct link to Get Current Proposal Count") Check how many governance proposals exist: ``` cast call [GOVERNANCE_CONTRACT_ADDRESS] "proposalCount()" --rpc-url $RPC_URL ``` ### Query a Specific Proposal[​](#query-a-specific-proposal "Direct link to Query a Specific Proposal") Get details about a specific proposal: ``` cast call [GOVERNANCE_CONTRACT_ADDRESS] "getProposal(uint256)" [PROPOSAL_ID] --rpc-url $RPC_URL ``` Replace: * `[GOVERNANCE_CONTRACT_ADDRESS]` - Your Governance contract address * `[PROPOSAL_ID]` - The proposal ID (zero-indexed, so the first proposal is 0) This returns the proposal struct containing: * Payload address * Creation timestamp * Voting start and end times * Current vote tallies ## Tips and Best Practices[​](#tips-and-best-practices "Direct link to Tips and Best Practices") ### Using Etherscan[​](#using-etherscan "Direct link to Using Etherscan") You can also query these contracts through Etherscan's "Read Contract" interface: 1. Navigate to the contract address on Etherscan 2. Go to the "Contract" tab 3. Click "Read Contract" or "Read as Proxy" 4. Find the function you want to call and enter parameters This provides a user-friendly interface without requiring command-line tools. ### Monitoring Automation[​](#monitoring-automation "Direct link to Monitoring Automation") Consider creating scripts that regularly query sequencer status and governance signals. This helps you: * Track your sequencer's health * Monitor governance proposals you care about * Receive alerts when action is needed ### Decoding Hex Output[​](#decoding-hex-output "Direct link to Decoding Hex Output") Some commands return hexadecimal values. Use `cast` to convert them: ``` # Convert hex to decimal cast --to-dec 0x03e8 # Convert hex to address format cast --to-address 0x000000000000000000000000742d35Cc6634C0532925a3b844Bc9e7595f0bEb ``` ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### "Invalid JSON RPC response"[​](#invalid-json-rpc-response "Direct link to \"Invalid JSON RPC response\"") **Issue**: Command fails with JSON RPC error. **Solutions**: * Verify your RPC endpoint is accessible and correct * Check that you're connected to the right network (Ethereum mainnet) * Ensure your RPC provider supports the `eth_call` method * Try a different RPC endpoint ### "Reverted" or "Execution reverted"[​](#reverted-or-execution-reverted "Direct link to \"Reverted\" or \"Execution reverted\"") **Issue**: Contract call reverts. **Solutions**: * Verify the contract address is correct * Check that the function signature matches the contract's ABI * Ensure you're passing the correct parameter types * Verify the contract is deployed on the network you're querying ### "Could not find function"[​](#could-not-find-function "Direct link to \"Could not find function\"") **Issue**: Function not found in contract. **Solutions**: * Verify the function name spelling and capitalization * Check that you're querying the correct contract * Ensure the contract version matches the function you're calling * Try querying through Etherscan to verify the contract ABI ## Next Steps[​](#next-steps "Direct link to Next Steps") * [Learn about sequencer setup](/operate/operators/setup/sequencer_management.md) to operate your sequencer node * [Participate in governance](/operate/operators/sequencer-management/creating_and_voting_on_proposals.md) by signaling, voting, and creating proposals * [Monitor your node](/operate/operators/monitoring.md) with metrics and observability tools * Join the [Aztec Discord](https://discord.gg/aztec) for operator support and community discussions --- # Become a Staking Provider ## Overview[​](#overview "Direct link to Overview") This guide covers running a sequencer with delegated stake on the Aztec network. Unlike conventional setups where you must have your own stake, delegated stake lets you (the "provider") operate sequencers backed by tokens from delegators. **This is a non-custodial system**: Delegators retain full control and ownership of their tokens at all times. You never take custody of the delegated tokens—they remain in the delegator's control while providing economic backing for your sequencer operations. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before proceeding, ensure you have: * Knowledge of running a sequencer node (see [Sequencer Setup Guide](/operate/operators/setup/sequencer_management.md)) * An Ethereum wallet with sufficient ETH for gas fees * Understanding of basic Aztec staking mechanics * Foundry installed for `cast` commands * Aztec CLI v4.3.1 or later installed: ``` VERSION=4.3.1 bash -i <(curl -sL https://install.aztec.network/4.3.1) ``` ### Contract Addresses[​](#contract-addresses "Direct link to Contract Addresses") For Staking Registry and GSE (Governance Staking Escrow) addresses, see the [Networks page](/networks.md#l1-contract-addresses). ## How Delegated Stake Works[​](#how-delegated-stake-works "Direct link to How Delegated Stake Works") You register with the StakingRegistry contract and add sequencer identities (keystores) to a queue. When delegators stake to your provider, the system: 1. Dequeues one keystore from your provider queue 2. Creates a [Split contract](https://docs.splits.org/core/split) for reward distribution 3. Registers the sequencer into the staking queue using the dequeued keystore ### Reward Distribution[​](#reward-distribution "Direct link to Reward Distribution") When a delegator stakes to your provider, a Split contract is automatically created to manage reward distribution. You configure your sequencer to use the Split contract address as the coinbase (see [After Delegation: Configure Sequencer Coinbase](#after-delegation-configure-sequencer-coinbase)). Rewards are distributed according to your agreed commission rate: * **Provider commission**: Your `providerRewardsRecipient` address receives your commission rate (e.g., 5% for 500 basis points) * **Delegator rewards**: The delegator's Aztec Token Vault (ATV) receives the remaining percentage **Rewards flow:** 1. Rewards accumulate in the rollup under the coinbase address (the Split contract) 2. After governance unlocks rewards, anyone can release them from the rollup to the `coinbase` address. 3. Anyone can then disperse the rewards from the Split contract to both the ATV and your `providerRewardsRecipient` This design ensures delegators maintain control of their rewards while you earn commission for operating the sequencer infrastructure. ## Setup Process[​](#setup-process "Direct link to Setup Process") Before starting these steps, ensure your sequencer node infrastructure is set up (see [Prerequisites](#prerequisites)). Follow these steps to set up delegated stake: 1. Register your provider with the Staking Registry 2. Add sequencer identities to your provider queue 3. Set your metadata in the GitHub repo **After a delegator stakes:** Configure your sequencer's coinbase (see [After Delegation](#after-delegation-configure-sequencer-coinbase)) ### Step 1: Register Your Provider[​](#step-1-register-your-provider "Direct link to Step 1: Register Your Provider") Register with the `StakingRegistry` contract as a provider for delegated staking. Registration is permissionless and open to anyone. **Function signature:** ``` function registerProvider( address _providerAdmin, uint16 _providerTakeRate, address _providerRewardsRecipient ) external returns (uint256); ``` **Parameters:** * `_providerAdmin`: Address that can update provider configuration * `_providerTakeRate`: Commission rate in basis points (500 = 5%) * `_providerRewardsRecipient`: Address receiving commission payments **Returns:** Your unique `providerIdentifier`. Save this—you'll need it for all provider operations. **Example:** ``` # Register a provider with 5% commission rate cast send $STAKING_REGISTRY_ADDRESS \ "registerProvider(address,uint16,address)" \ $PROVIDER_ADMIN_ADDRESS \ 500 \ $REWARDS_RECIPIENT_ADDRESS \ --rpc-url $RPC_URL \ --private-key $YOUR_PRIVATE_KEY ``` ### Extracting Your Provider ID[​](#extracting-your-provider-id "Direct link to Extracting Your Provider ID") Once the transaction is confirmed, you need to extract your `providerIdentifier` from the transaction logs. The provider ID is emitted as the second topic in the registration event log. **Method 1: Using cast receipt** ``` cast receipt [TX_HASH] --rpc-url $RPC_URL | grep "return" | awk '{print $2}' | xargs cast to-dec ``` **Method 2: From transaction logs** The transaction receipt will contain one log where the second topic is your `providerId` in hex format: ``` # Example log output logs [{"address":"0xc3860c45e5f0b1ef3000dbf93149756f16928adb", "topics":["0x43fe1b4477c9a580955f586c904f4670929e184ef4bef4936221c52d0a79a75b", "0x0000000000000000000000000000000000000000000000000000000000000002", # This is your providerId "0x000000000000000000000000efdb4c5f3a2f04e0cb393725bcae2dd675cc3718", "0x00000000000000000000000000000000000000000000000000000000000001f4"], ... }] ``` Convert the hex value to decimal: ``` cast to-dec 0x0000000000000000000000000000000000000000000000000000000000000002 # Output: 2 ``` **Save your `providerIdentifier`**—you'll need it for all subsequent provider operations. ### Step 2: Add Sequencer Identities[​](#step-2-add-sequencer-identities "Direct link to Step 2: Add Sequencer Identities") Add sequencer identities (keystores) to your provider queue. Each keystore represents one sequencer that can be activated when a delegator stakes to you. **Function signature:** ``` function addKeysToProvider( uint256 _providerIdentifier, KeyStore[] calldata _keyStores ) external; ``` **Parameters:** * `_providerIdentifier`: Your provider identifier from registration * `_keyStores`: Array of keystore structures (max 100 per transaction) **KeyStore structure:** ``` struct KeyStore { address attester; // Sequencer's address BN254Lib.G1Point publicKeyG1; // BLS public key (G1) BN254Lib.G2Point publicKeyG2; // BLS public key (G2) BN254Lib.G1Point proofOfPossession; // BLS signature (prevents rogue key attacks) } ``` Critical: Key Management for Delegated Staking **⚠️ If you run out of keys, users cannot delegate tokens to you.** The Staking Registry **DOES NOT** check for duplicate keys. Please take **EXTREME** care when registering keys: * Duplicate keys will cause delegation failures when that duplicate is at the top of your queue * The only way to fix this is by calling `dripProviderQueue(_providerIdentifier, _numberOfKeysToDrip)` to remove the duplicate * Always verify keys before registration to avoid user experience issues ### Generating Keys for Registration[​](#generating-keys-for-registration "Direct link to Generating Keys for Registration") Use the `aztec validator-keys` command with the `--staker-output` flag to automatically generate properly formatted registration data: ``` aztec validator-keys new \ --fee-recipient 0x0000000000000000000000000000000000000000000000000000000000000000 \ --staker-output \ --gse-address 0xa92ecFD0E70c9cd5E5cd76c50Af0F7Da93567a4f \ --l1-rpc-urls $ETH_RPC \ --l1-chain-id 1 ``` This command automatically: 1. Generates the private keystore with ETH and BLS keys 2. Generates the public keystore with G1 and G2 public keys 3. Generates the proof of possession signature 4. Outputs the data in the correct format for the `addKeysToProvider` function The public keystore file (`keyN_staker_output.json`) contains the data you'll use for provider registration. For more details on keystore creation, see the [Sequencer Setup Guide](/operate/operators/setup/sequencer_management.md#generating-keys). ### Building the Registration Command[​](#building-the-registration-command "Direct link to Building the Registration Command") You have two options for constructing the `addKeysToProvider` command: **Option 1: Use the helper script (Recommended)** Use this helper script to automatically build the command from your `validator-keys` output: The script reads the JSON output from `validator-keys staker` and constructs the properly formatted `cast send` command. **Option 2: Manual construction** If you need to manually construct the command, the function signature is: ``` addKeysToProvider(uint256,(address,(uint256,uint256),(uint256,uint256,uint256,uint256),(uint256,uint256))[]) ``` **Parameters:** * First `uint256`: Your provider identifier (from registration in Step 1) * Tuple array: `KeyStore[]` where each element contains: * `address`: Sequencer address * `(uint256,uint256)`: publicKeyG1 (x, y coordinates) * `(uint256,uint256,uint256,uint256)`: publicKeyG2 (x0, x1, y0, y1 coordinates) * `(uint256,uint256)`: proofOfPossession (x, y coordinates) Example with placeholder values: ``` cast send $STAKING_REGISTRY_ADDRESS \ "addKeysToProvider(uint256,(address,(uint256,uint256),(uint256,uint256,uint256,uint256),(uint256,uint256))[])" \ $YOUR_PROVIDER_IDENTIFIER \ "[(0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb,(12345,67890),(11111,22222,33333,44444),(98765,43210))]" \ --rpc-url $RPC_URL \ --private-key $ADMIN_PRIVATE_KEY ``` **Important:** * Replace all values above with actual data from `aztec validator-keys new --staker-output` * Add a maximum of 100 keystores per transaction to avoid gas limit issues * Verify each keystore is unique before adding to prevent duplicate key issues ### Step 3: Set Your Metadata[​](#step-3-set-your-metadata "Direct link to Step 3: Set Your Metadata") To be featured on the staking dashboard, submit metadata about your provider. **Required metadata:** * Provider name and description * Contact email * Logo image (PNG or SVG, recommended size: 256x256px) * Website URL * Discord username * Your `providerIdentifier` **Submission process:** 1. Copy [`_example.json`](https://github.com/AztecProtocol/staking-dashboard/blob/master/providers/_example.json) from the [`providers`](https://github.com/AztecProtocol/staking-dashboard/tree/master/providers) folder in the [staking-dashboard GitHub repository](https://github.com/AztecProtocol/staking-dashboard). 2. Rename it to `{providerId}-{your-provider-name}.json` (e.g. `42-my-provider.json`), where `providerId` matches your on chain registration. 3. Fill in your metadata and open a pull request adding the file to the `providers` folder. The JSON file should follow this format: ``` { "providerId": 0, "providerName": "", "providerDescription": "", "providerEmail": "", "providerWebsite": "", "providerLogoUrl": "", "discordUsername": "", "providerSelfStake": ["0x..."] } ``` The `providerId` must match your on chain registration and be unique across all submissions. The `providerSelfStake` field is an optional array of attester addresses for sequencers receiving direct provider funding. Good metadata helps delegators understand your offering and builds trust. ## After Delegation: Configure Sequencer Coinbase[​](#after-delegation-configure-sequencer-coinbase "Direct link to After Delegation: Configure Sequencer Coinbase") Once a delegator stakes to your provider, the system creates a Split contract for that delegation and activates the corresponding sequencer. **Configure the sequencer to use the Split contract address as the coinbase.** ### Why This Matters[​](#why-this-matters "Direct link to Why This Matters") The coinbase address determines where your sequencer's block rewards are sent. Setting it to the Split contract address ensures rewards are distributed according to your agreed commission rate, which is critical for maintaining trust with your delegators. ### How to Configure the Coinbase[​](#how-to-configure-the-coinbase "Direct link to How to Configure the Coinbase") Update the `coinbase` field in your sequencer node's keystore configuration to the Split contract address created for this delegation. **Example keystore configuration:** ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "0x...", // Your Ethereum sequencer private key "bls": "0x..." // Your BLS sequencer private key }, "publisher": ["0x..."], // Address that submits blocks to L1 "coinbase": "0x[SPLIT_CONTRACT_ADDRESS]", // Split contract for this delegation "feeRecipient": "0x0000000000000000000000000000000000000000000000000000000000000000" // Not currently used, set to all zeros } ] } ``` Replace `[SPLIT_CONTRACT_ADDRESS]` with the actual Split contract address created for this delegation. You can find this address in the staking dashboard (see "Finding Your Split Contract Address" below). For detailed information about keystore configuration, including different storage methods and advanced patterns, see the [Advanced Keystore Guide](/operate/operators/keystore.md). ### Finding Your Split Contract Address[​](#finding-your-split-contract-address "Direct link to Finding Your Split Contract Address") **You have to manually monitor the delegations you receive and update the `coinbase` address to the correct Split contract!** You can retrieve the Split contract address for a specific delegation through the **Staking Dashboard**: 1. Navigate to your provider dashboard on the staking dashboard 2. Look for the dropdown called **"Sequencer Registered (x)"** where x is the number of registered sequencers 3. Click on the dropdown to expand it 4. This shows the Sequencer address → Split contract relation 5. Set the Split contract as the `coinbase` for the respective Sequencer address on your node The dropdown will display a table showing which Split contract corresponds to each of your sequencer addresses, making it easy to configure the correct coinbase for each sequencer. **Manual monitoring approach:** Since coinbase configuration must be done manually, you should: * Regularly check the staking dashboard for new delegations * Set up alerts or scheduled checks (daily or more frequently during high activity) * Update keystore configurations promptly when new delegations appear * Maintain a record of which Split contracts map to which keystores ### Important Notes[​](#important-notes "Direct link to Important Notes") * **Monitor delegations actively**: The system does not automatically notify you of new delegations * Configure the coinbase immediately after each delegation to ensure rewards flow correctly from the start * Each delegation creates a unique Split contract—configure each sequencer with its specific Split contract address * Restart your sequencer node after updating the keystore for changes to take effect * Keep a mapping of sequencer addresses to Split contracts for operational tracking ## Monitoring Keystore Availability[​](#monitoring-keystore-availability "Direct link to Monitoring Keystore Availability") As a provider, you must maintain sufficient sequencer identities (keystores) in your queue to handle incoming delegations. When a delegator stakes to your provider and your queue is empty, they cannot activate a sequencer—this results in a poor delegator experience and lost opportunity. ### Why Monitoring Matters[​](#why-monitoring-matters "Direct link to Why Monitoring Matters") Each time a delegator stakes to your provider: 1. One keystore is dequeued from your provider queue 2. A sequencer is activated using that keystore 3. Your available keystore count decreases by one If your queue runs empty, new delegations cannot activate sequencers until you add more keystores. This could cause delegators to choose other providers. ### Checking Available Keystores[​](#checking-available-keystores "Direct link to Checking Available Keystores") Check your current keystore queue with this call: ```` # Check provider queue length cast call [STAKING_REGISTRY_ADDRESS] \ "getProviderQueueLength(uint256) (uint256)" \ [YOUR_PROVIDER_IDENTIFIER] \ --rpc-url [RPC_URL] This returns your provider's queue length, which is the number of keystores currently available. ### Setting Up Automated Monitoring Implement automated monitoring to alert you when your keystore queue runs low. #### Cron Job Example The following script monitors your keystore queue and alerts when it drops below a threshold. Replace the placeholder values and uncomment your preferred alert method (webhook or email): ```bash #!/bin/bash # check-keystores.sh THRESHOLD=5 # Alert when fewer than 5 keystores remain REGISTRY_ADDRESS="[STAKING_REGISTRY_ADDRESS]" PROVIDER_ID="[YOUR_PROVIDER_IDENTIFIER]" RPC_URL="[YOUR_RPC_URL]" WEBHOOK_URL="[YOUR_WEBHOOK_URL]" # Optional: for Slack/Discord notifications # Gets current queue length QUEUE_LENGTH=$(cast call "$REGISTRY_ADDRESS" \ "getProviderQueueLength(uint256)" \ "$PROVIDER_ID" \ --rpc-url "$RPC_URL") echo "Queue length: $QUEUE_LENGTH" # Check if queue is running low if [ "$QUEUE_LENGTH" -lt "$THRESHOLD" ]; then echo "WARNING: Keystore queue running low! Only $QUEUE_LENGTH keystores remaining." # Send alert (uncomment and configure your preferred method) # Slack/Discord webhook: # curl -X POST "$WEBHOOK_URL" -H "Content-Type: application/json" \ # -d "{\"text\":\"⚠️ Keystore queue low: $QUEUE_LENGTH remaining (threshold: $THRESHOLD)\"}" # Email via mail command: # echo "Keystore queue has $QUEUE_LENGTH keys remaining" | mail -s "Low Keystore Alert" your-email@example.com fi ```` Make the script executable and schedule it with cron: ``` # Make the script executable chmod +x /path/to/check-keystores.sh # Edit crontab crontab -e # Add this line to check every 4 hours 0 */4 * * * /path/to/check-keystores.sh >> /var/log/keystore-monitor.log 2>&1 ``` ### When to Add More Keystores[​](#when-to-add-more-keystores "Direct link to When to Add More Keystores") Add keystores proactively before running out: * Monitor your delegation growth rate * Add in batches (max 100 per transaction) * Stay ahead of demand during high-activity periods See [Step 2: Add Sequencer Identities](#step-2-add-sequencer-identities) for instructions. ## Managing Your Provider[​](#managing-your-provider "Direct link to Managing Your Provider") Update your provider configuration using these functions. All must be called from your `providerAdmin` address. ### Update Admin Address[​](#update-admin-address "Direct link to Update Admin Address") Transfer provider administration to a new address: ``` cast send [STAKING_REGISTRY_ADDRESS] \ "updateProviderAdmin(uint256,address)" \ [YOUR_PROVIDER_IDENTIFIER] \ [NEW_ADMIN_ADDRESS] \ --rpc-url [RPC_URL] \ --private-key [CURRENT_ADMIN_PRIVATE_KEY] ``` ### Update Rewards Recipient[​](#update-rewards-recipient "Direct link to Update Rewards Recipient") Change the address receiving commission payments: ``` cast send [STAKING_REGISTRY_ADDRESS] \ "updateProviderRewardsRecipient(uint256,address)" \ [YOUR_PROVIDER_IDENTIFIER] \ [NEW_REWARDS_RECIPIENT_ADDRESS] \ --rpc-url [RPC_URL] \ --private-key [ADMIN_PRIVATE_KEY] ``` ### Update Commission Rate[​](#update-commission-rate "Direct link to Update Commission Rate") Modify your commission rate (applies only to new delegations): ``` cast send [STAKING_REGISTRY_ADDRESS] \ "updateProviderTakeRate(uint256,uint16)" \ [YOUR_PROVIDER_IDENTIFIER] \ [NEW_RATE_BASIS_POINTS] \ --rpc-url [RPC_URL] \ --private-key [ADMIN_PRIVATE_KEY] ``` Commission Changes Only Apply to New Delegations When you update your commission rate, only **new delegations** will use the updated rate. **Existing delegations cannot be changed**—they permanently retain the original commission rate that was agreed upon when the delegation was created. ## Verification[​](#verification "Direct link to Verification") Verify your setup is working correctly. ### Check Provider Registration[​](#check-provider-registration "Direct link to Check Provider Registration") Query the StakingRegistry to confirm your provider details: ``` cast call [STAKING_REGISTRY_ADDRESS] \ "providerConfigurations(uint256) (address,uint16,address)" \ [YOUR_PROVIDER_IDENTIFIER] \ --rpc-url [RPC_URL] ``` This returns: 1. The provider's admin address 2. The provider's commission rate in bps 3. The provider's rewards recipient ### Verify Queue Length[​](#verify-queue-length "Direct link to Verify Queue Length") Check your provider queue length: ``` cast call [STAKING_REGISTRY_ADDRESS] \ "getProviderQueueLength(uint256)" \ [YOUR_PROVIDER_IDENTIFIER] \ --rpc-url [RPC_URL] ``` ### Monitor Delegations[​](#monitor-delegations "Direct link to Monitor Delegations") View these metrics on the staking dashboard: * Total stake delegated to your provider * Number of active sequencers * Commission earned * Provider performance metrics ### Confirm Node Operation[​](#confirm-node-operation "Direct link to Confirm Node Operation") Ensure your sequencer nodes are running and synced. See [Useful Commands](/operate/operators/sequencer-management/useful-commands.md) for commands to check sequencer status. ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Registration transaction fails[​](#registration-transaction-fails "Direct link to Registration transaction fails") **Issue**: The `registerProvider` transaction reverts or fails. **Solutions**: * Ensure your wallet has sufficient ETH for gas fees * Verify the StakingRegistry contract address is correct * Check that the commission rate is within acceptable bounds (typically 0-10000 basis points) * Review transaction logs for specific error messages using a block explorer ### Cannot add sequencer identities[​](#cannot-add-sequencer-identities "Direct link to Cannot add sequencer identities") **Issue**: The `addKeysToProvider` function fails. **Solutions**: * Confirm you're calling from the `providerAdmin` address * Verify your `providerIdentifier` is correct * Ensure BLS signatures in `KeyStore` are properly formatted (use the keystore creation utility) * Check that the sequencer addresses aren't already registered elsewhere * Reduce batch size if hitting gas limits (max 100 keystores per transaction) ### No delegators appearing[​](#no-delegators-appearing "Direct link to No delegators appearing") **Issue**: No delegators are staking to your provider. **Solutions**: * Verify your provider is visible on the staking dashboard * Complete all metadata fields to build trust * Ensure your commission rate is competitive with other providers * Confirm your sequencer nodes are operational and performing well * Engage with the community on Discord to build your reputation ### Commission not being received[​](#commission-not-being-received "Direct link to Commission not being received") **Issue**: Commission payments aren't arriving at the rewards recipient address. **Solutions**: * Verify the `providerRewardsRecipient` address is correct * Check that delegations are active and generating fees * Confirm your sequencers are producing blocks and earning fees * Allow time for reward distribution (may not be immediate) * Check the contract for pending distributions that need to be claimed ## Best Practices[​](#best-practices "Direct link to Best Practices") **Maintain Sufficient Keystores**: Set up automated monitoring to ensure your keystore queue never runs empty. See [Monitoring Keystore Availability](#monitoring-keystore-availability) for guidance on implementing alerts. **Communicate Changes**: Inform delegators about commission rate changes, planned maintenance, or infrastructure updates. Good communication builds trust. **Monitor Performance**: Track your sequencers' attestation rates, block proposals, and uptime. Poor performance may cause delegators to withdraw. **Secure Your Keys**: The `providerAdmin` key controls your provider configuration. Store it securely and consider using a hardware wallet or multisig. ## Next Steps[​](#next-steps "Direct link to Next Steps") After completing this setup: 1. Monitor your provider's performance through the staking dashboard 2. Maintain high uptime for your sequencer nodes 3. Keep open communication with delegators 4. Regularly add new keystores to your provider queue (see [Monitoring Keystore Availability](#monitoring-keystore-availability)) 5. Join the [Aztec Discord](https://discord.gg/aztec) for provider support and community discussions --- # Blob retrieval ## Overview[​](#overview "Direct link to Overview") Aztec uses EIP-4844 blobs to publish transaction data to Ethereum Layer 1. Since blob data is only available on L1 for a limited period (\~18 days / 4,096 epochs), nodes need reliable ways to store and retrieve blob data for synchronization and historical access. Aztec nodes can be configured to retrieve blobs from L1 consensus (beacon nodes), file stores (S3, GCS, R2), and archive services. Automatic Configuration When using `--network [NETWORK_NAME]`, blob file stores are automatically configured for you. Most users don't need to manually configure blob storage. Override Behavior Setting the `BLOB_FILE_STORE_URLS` environment variable overrides the file store configuration from the network config. ## Understanding blob sources[​](#understanding-blob-sources "Direct link to Understanding blob sources") The blob client can retrieve blobs from multiple sources, tried in order: 1. **File Store**: Fast retrieval from configured storage (S3, GCS, R2, local files, HTTPS) 2. **L1 Consensus**: Beacon node API to a (semi-)supernode for recent blobs (within \~18 days) 3. **Archive API**: Services like Blobscan for historical blob data For near-tip synchronization, the client will retry file stores with backoff to handle eventual consistency when blobs are still being uploaded by other validators. ### L1 consensus and blob availability[​](#l1-consensus-and-blob-availability "Direct link to L1 consensus and blob availability") If your beacon node has access to [supernodes or semi-supernodes](https://ethereum.org/roadmap/fusaka/peerdas/), L1 consensus alone may be sufficient for retrieving blobs within the \~18 day retention period. With the Fusaka upgrade and [PeerDAS (Peer Data Availability Sampling)](https://eips.ethereum.org/EIPS/eip-7594), Ethereum uses erasure coding to split blobs into 128 columns, enabling robust data availability: * **Supernodes** (validators with ≥4,096 ETH staked): Custody all 128 columns and all blob data for the full \~18 day retention period. These nodes form the backbone of the network and continuously heal data gaps. * **Semi-supernodes** (validators with ≥1,824 ETH / 57 validators): Handle at least 64 columns, enabling reconstruction of complete blob data. * **Regular nodes**: Only download 1/8th of the data (8 of 128 columns) to verify availability. This is **not sufficient** to serve complete blob data. Supernodes If L1 consensus is your only blob source, your beacon node must be a supernode or semi-supernode (or connected to one) to retrieve complete blobs. A regular node cannot reconstruct full blob data from its partial columns alone. This means that for recent blobs, configuring `L1_CONSENSUS_HOST_URLS` pointing to a well-connected supernode or semi-supernode may be all you need. However, file stores and archive APIs are still recommended for: * Faster retrieval (file stores are typically faster than L1 consensus queries) * Historical access (blobs older than \~18 days are pruned from L1) * Redundancy (multiple sources improve reliability) ## Configuring blob sources[​](#configuring-blob-sources "Direct link to Configuring blob sources") ### Environment variables[​](#environment-variables "Direct link to Environment variables") Configure blob sources using environment variables: | Variable | Description | Example | | ----------------------------------- | --------------------------------------- | ---------------------------- | | `BLOB_FILE_STORE_URLS` | Comma-separated URLs to read blobs from | `gs://bucket/,s3://bucket/` | | `L1_CONSENSUS_HOST_URLS` | Beacon node URLs (comma-separated) | `https://beacon.example.com` | | `L1_CONSENSUS_HOST_API_KEYS` | API keys for beacon nodes | `key1,key2` | | `L1_CONSENSUS_HOST_API_KEY_HEADERS` | Header names for API keys | `Authorization` | | `BLOB_ARCHIVE_API_URL` | Archive API URL (e.g., Blobscan) | `https://api.blobscan.com` | | `BLOB_ALLOW_EMPTY_SOURCES` | Allow no blob sources (default: false) | `false` | tip If you want to contribute to the network by hosting a blob file store, see the [Blob upload guide](/operate/operators/setup/blob_upload.md). ### Supported storage backends[​](#supported-storage-backends "Direct link to Supported storage backends") The blob client supports the same storage backends as snapshots: * **Google Cloud Storage** - `gs://bucket-name/path/` * **Amazon S3** - `s3://bucket-name/path/` * **Cloudflare R2** - `s3://bucket-name/path/?endpoint=https://[ACCOUNT_ID].r2.cloudflarestorage.com` * **HTTP/HTTPS** (read-only) - `https://host/path` * **Local filesystem** - `file:///absolute/path` ### Storage path format[​](#storage-path-format "Direct link to Storage path format") Blobs are stored using the following path structure: ``` {base_url}/aztec-{l1ChainId}-{rollupVersion}-{rollupAddress}/blobs/{versionedBlobHash}.data ``` For example: ``` gs://my-bucket/aztec-1-1-0x1234abcd.../blobs/0x01abc123...def.data ``` ## Configuration examples[​](#configuration-examples "Direct link to Configuration examples") ### Basic file store configuration[​](#basic-file-store-configuration "Direct link to Basic file store configuration") ``` # Read blobs from GCS BLOB_FILE_STORE_URLS=gs://my-snapshots/ ``` ### Multiple read sources with L1 fallback[​](#multiple-read-sources-with-l1-fallback "Direct link to Multiple read sources with L1 fallback") ``` # Try multiple sources in order BLOB_FILE_STORE_URLS=gs://primary-bucket/,s3://backup-bucket/ # L1 consensus fallback L1_CONSENSUS_HOST_URLS=https://beacon1.example.com,https://beacon2.example.com # Archive fallback for historical blobs BLOB_ARCHIVE_API_URL=https://api.blobscan.com ``` ### Cloudflare R2 configuration[​](#cloudflare-r2-configuration "Direct link to Cloudflare R2 configuration") ``` BLOB_FILE_STORE_URLS=s3://my-bucket/?endpoint=https://[ACCOUNT_ID].r2.cloudflarestorage.com ``` Replace `[ACCOUNT_ID]` with your Cloudflare account ID. ### Local filesystem (for testing)[​](#local-filesystem-for-testing "Direct link to Local filesystem (for testing)") ``` BLOB_FILE_STORE_URLS=file:///data/blobs ``` ## Authentication[​](#authentication "Direct link to Authentication") ### Google Cloud Storage[​](#google-cloud-storage "Direct link to Google Cloud Storage") Set up [Application Default Credentials](https://cloud.google.com/docs/authentication/application-default-credentials): ``` gcloud auth application-default login ``` Or use a service account key: ``` export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account-key.json ``` ### Amazon S3 / Cloudflare R2[​](#amazon-s3--cloudflare-r2 "Direct link to Amazon S3 / Cloudflare R2") Set AWS credentials as environment variables: ``` export AWS_ACCESS_KEY_ID=your-access-key export AWS_SECRET_ACCESS_KEY=your-secret-key ``` For R2, these credentials come from your Cloudflare R2 API tokens. ## How blob retrieval works[​](#how-blob-retrieval-works "Direct link to How blob retrieval works") When a node needs blobs for a block, the blob client follows this retrieval order: ### During historical sync[​](#during-historical-sync "Direct link to During historical sync") 1. **File Store** - Quick lookup in configured file stores 2. **L1 Consensus** - Query beacon nodes using slot number 3. **Archive API** - Fall back to Blobscan or similar service ### During near-tip sync[​](#during-near-tip-sync "Direct link to During near-tip sync") 1. **File Store** - Quick lookup (no retries) 2. **L1 Consensus** - Query beacon nodes 3. **File Store with retries** - Retry with backoff for eventual consistency 4. **Archive API** - Final fallback ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### No blob sources configured[​](#no-blob-sources-configured "Direct link to No blob sources configured") **Issue**: Node starts with warning about no blob sources. **Solutions**: * Configure at least one of: `BLOB_FILE_STORE_URLS`, `L1_CONSENSUS_HOST_URLS`, or `BLOB_ARCHIVE_API_URL` * Set `BLOB_ALLOW_EMPTY_SOURCES=true` only if you understand the implications (node may fail to sync) ### Blob retrieval fails[​](#blob-retrieval-fails "Direct link to Blob retrieval fails") **Issue**: Node cannot retrieve blobs for a block. **Solutions**: * Verify your file store URLs are accessible * Check L1 consensus host connectivity * Ensure authentication credentials are configured * Try using multiple file store URLs for redundancy ### L1 consensus host errors[​](#l1-consensus-host-errors "Direct link to L1 consensus host errors") **Issue**: Cannot connect to beacon nodes. **Solutions**: * Verify beacon node URLs are correct and accessible * Check if API keys are required and correctly configured * Ensure the beacon node is synced * Try multiple beacon node URLs for redundancy ## Best practices[​](#best-practices "Direct link to Best practices") * **Configure multiple sources**: Use multiple file store URLs and L1 consensus hosts for redundancy * **Use file stores for production**: File stores provide faster, more reliable blob retrieval than L1 consensus * **Use archive API for historical access**: Configure `BLOB_ARCHIVE_API_URL` for accessing blobs older than \~18 days. Even with PeerDAS supernodes providing robust data availability, blob data is pruned from L1 after 4,096 epochs. Archive services like [Blobscan](https://blobscan.com/) store historical blob data indefinitely ## Next Steps[​](#next-steps "Direct link to Next Steps") * Learn how to [host a blob file store](/operate/operators/setup/blob_upload.md) to contribute to the network * Learn about [using snapshots](/operate/operators/setup/syncing_best_practices.md) for faster node synchronization * Set up [monitoring](/operate/operators/monitoring.md) to track your node's blob retrieval * Check the [CLI reference](/operate/operators/reference/cli-reference.md) for additional blob-related options * Join the [Aztec Discord](https://discord.gg/aztec) for support --- # Blob upload ## Overview[​](#overview "Direct link to Overview") While most nodes only need to retrieve blobs, you can contribute to the network by hosting a blob file store. When configured with an upload URL, your node will automatically upload blobs it retrieves to your file store, making them available for other nodes to download. Upload is Optional Configuring blob upload is optional. You can still download blobs from file stores without uploading them yourself — other network participants (such as sequencers and validators) upload blobs to shared storage, making them available for all nodes to retrieve. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before configuring blob upload, you should: * Have access to cloud storage (Google Cloud Storage, Amazon S3, or Cloudflare R2) with **write permissions** * Understand the [blob retrieval](/operate/operators/setup/blob_storage.md) configuration ## Configuring blob upload[​](#configuring-blob-upload "Direct link to Configuring blob upload") ### Environment variable[​](#environment-variable "Direct link to Environment variable") Configure blob upload using the following environment variable in your `.env` file: | Variable | Description | Example | | ---------------------------- | ----------------------- | ----------------------- | | `BLOB_FILE_STORE_UPLOAD_URL` | URL for uploading blobs | `s3://my-bucket/blobs/` | ### Supported storage backends[​](#supported-storage-backends "Direct link to Supported storage backends") The blob client supports the following storage backends for upload: * **Google Cloud Storage** - `gs://bucket-name/path/` * **Amazon S3** - `s3://bucket-name/path/` * **Cloudflare R2** - `s3://bucket-name/path/?endpoint=https://[ACCOUNT_ID].r2.cloudflarestorage.com` * **Local filesystem** - `file:///absolute/path` warning HTTPS URLs are read-only and cannot be used for uploads. ### Storage path format[​](#storage-path-format "Direct link to Storage path format") Blobs are stored using the following path structure: ``` {base_url}/aztec-{l1ChainId}-{rollupVersion}-{rollupAddress}/blobs/{versionedBlobHash}.data ``` For example: ``` gs://my-bucket/aztec-1-1-0x1234abcd.../blobs/0x01abc123...def.data ``` ## Healthcheck file[​](#healthcheck-file "Direct link to Healthcheck file") When blob upload is configured, your node uploads a `.healthcheck` file to the storage path on startup and periodically thereafter. Other nodes use this file to verify connectivity to your file store before attempting to download blobs. Exclude from pruning If you configure lifecycle rules or pruning policies on your storage bucket, ensure the `.healthcheck` file is excluded. Deleting this file will cause connectivity checks to fail on other nodes. ## Configuration examples[​](#configuration-examples "Direct link to Configuration examples") ### Google Cloud Storage[​](#google-cloud-storage "Direct link to Google Cloud Storage") ``` BLOB_FILE_STORE_UPLOAD_URL=gs://my-bucket/blobs/ ``` ### Amazon S3[​](#amazon-s3 "Direct link to Amazon S3") ``` BLOB_FILE_STORE_UPLOAD_URL=s3://my-bucket/blobs/ ``` ### Cloudflare R2[​](#cloudflare-r2 "Direct link to Cloudflare R2") ``` BLOB_FILE_STORE_UPLOAD_URL=s3://my-bucket/blobs/?endpoint=https://[ACCOUNT_ID].r2.cloudflarestorage.com ``` Replace `[ACCOUNT_ID]` with your Cloudflare account ID. ### Local filesystem (for testing)[​](#local-filesystem-for-testing "Direct link to Local filesystem (for testing)") ``` BLOB_FILE_STORE_UPLOAD_URL=file:///data/blobs ``` ## Authentication[​](#authentication "Direct link to Authentication") Upload requires write permissions to your storage bucket. ### Google Cloud Storage[​](#google-cloud-storage-1 "Direct link to Google Cloud Storage") Set up [Application Default Credentials](https://cloud.google.com/docs/authentication/application-default-credentials): ``` gcloud auth application-default login ``` Or use a service account key with write permissions: ``` export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account-key.json ``` ### Amazon S3 / Cloudflare R2[​](#amazon-s3--cloudflare-r2 "Direct link to Amazon S3 / Cloudflare R2") Set AWS credentials as environment variables: ``` export AWS_ACCESS_KEY_ID=your-access-key export AWS_SECRET_ACCESS_KEY=your-secret-key ``` For R2, these credentials come from your Cloudflare R2 API tokens. Ensure the token has write permissions. ## Exposing a public HTTP endpoint[​](#exposing-a-public-http-endpoint "Direct link to Exposing a public HTTP endpoint") While you upload blobs using SDK URLs (`gs://`, `s3://`), you should configure a public HTTP endpoint so other nodes can download blobs without needing cloud credentials. This allows anyone to add your file store as a read source using a simple HTTPS URL. ### Google Cloud Storage[​](#google-cloud-storage-2 "Direct link to Google Cloud Storage") GCS buckets can be accessed publicly at `https://storage.googleapis.com/BUCKET_NAME/path/to/object`. To enable public access: 1. Go to your bucket in the [Google Cloud Console](https://console.cloud.google.com/storage/browser) 2. Select the **Permissions** tab 3. Click **Grant Access** 4. Add `allUsers` as a principal with the **Storage Object Viewer** role See [Making data public](https://cloud.google.com/storage/docs/access-control/making-data-public) for detailed instructions. Once configured, other nodes can use: ``` BLOB_FILE_STORE_URLS=https://storage.googleapis.com/my-bucket/blobs/ ``` ### Amazon S3[​](#amazon-s3-1 "Direct link to Amazon S3") S3 buckets can be accessed publicly via static website hosting at `http://BUCKET_NAME.s3-website.REGION.amazonaws.com`. To enable public access: 1. Go to your bucket in the [AWS S3 Console](https://console.aws.amazon.com/s3/) 2. Disable **Block Public Access** settings 3. Add a bucket policy granting public read access 4. Enable **Static website hosting** in the bucket properties See [Hosting a static website on S3](https://docs.aws.amazon.com/AmazonS3/latest/userguide/WebsiteHosting.html) for detailed instructions. note S3 website endpoints only support HTTP. For HTTPS, use [CloudFront](https://docs.aws.amazon.com/AmazonS3/latest/userguide/website-hosting-cloudfront-walkthrough.html) as a CDN in front of your bucket. ### Cloudflare R2[​](#cloudflare-r2-1 "Direct link to Cloudflare R2") R2 buckets can expose a public HTTP endpoint via a custom domain or the managed `r2.dev` subdomain. To enable public access: 1. Go to your bucket in the [Cloudflare Dashboard](https://dash.cloudflare.com/) 2. Select **Settings** > **Public Access** 3. Either enable the `r2.dev` subdomain or connect a custom domain See [Public buckets](https://developers.cloudflare.com/r2/buckets/public-buckets/) for detailed instructions. Once configured, other nodes can use: ``` BLOB_FILE_STORE_URLS=https://pub-[ID].r2.dev/ # or with custom domain: BLOB_FILE_STORE_URLS=https://blobs.yourdomain.com/ ``` tip R2 offers free egress, making it cost-effective for public blob distribution. ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Upload fails[​](#upload-fails "Direct link to Upload fails") **Issue**: Blobs are not being uploaded to file store. **Solutions**: * Verify `BLOB_FILE_STORE_UPLOAD_URL` is set * Check write permissions on the storage bucket * Ensure credentials are configured (AWS/GCP) * Note: HTTPS URLs are read-only and cannot be used for uploads ## Next Steps[​](#next-steps "Direct link to Next Steps") * Learn about [blob retrieval](/operate/operators/setup/blob_storage.md) configuration * Learn about [using snapshots](/operate/operators/setup/syncing_best_practices.md) for faster node synchronization * Join the [Aztec Discord](https://discord.gg/aztec) for support --- # Using and running a bootnode ## Overview[​](#overview "Direct link to Overview") Bootnodes facilitate peer discovery in the Aztec network by maintaining a list of active peers that new nodes can connect to. This guide covers how to connect your node to a bootnode and how to run your own bootnode. ## What is a bootnode?[​](#what-is-a-bootnode "Direct link to What is a bootnode?") Nodes in the Aztec network must connect to peers to gossip transactions and propagate them across the network. Bootnodes help new nodes discover and connect to these peers, enabling them to join the peer-to-peer layer. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before proceeding, you should: * Have the Aztec node software installed * Understand basic command-line operations * For running a bootnode: Have the necessary network infrastructure and port access ## Connecting to a bootnode[​](#connecting-to-a-bootnode "Direct link to Connecting to a bootnode") To connect your node to a bootnode for peer discovery: 1. Obtain the bootnode's ENR (Ethereum Node Record) 2. Add the ENR to your node's `.env` file using the `BOOTSTRAP_NODES` environment variable The variable accepts a comma-separated list of bootstrap node ENRs: ``` BOOTSTRAP_NODES=[ENR] ``` For multiple bootnodes: ``` BOOTSTRAP_NODES=[ENR1],[ENR2],[ENR3] ``` Then add the environment variable to your `docker-compose.yml`: ``` environment: # ... other environment variables BOOTSTRAP_NODES: ${BOOTSTRAP_NODES} ``` ## Running a bootnode[​](#running-a-bootnode "Direct link to Running a bootnode") To run your own bootnode, create a dedicated Docker Compose configuration. Create a `docker-compose.yml` file for your bootnode: ``` services: aztec-bootnode: image: "aztecprotocol/aztec:4.3.1" container_name: "aztec-bootnode" ports: - ${P2P_PORT}:${P2P_PORT} - ${P2P_PORT}:${P2P_PORT}/udp volumes: - ${DATA_DIRECTORY}:/var/lib/data environment: P2P_PORT: ${P2P_PORT} P2P_BROADCAST_PORT: ${P2P_BROADCAST_PORT} PEER_ID_PRIVATE_KEY_PATH: ${PEER_ID_PRIVATE_KEY_PATH} entrypoint: >- node --no-warnings /usr/src/yarn-project/aztec/dest/bin/index.js start --p2p-bootstrap networks: - aztec restart: always networks: aztec: name: aztec ``` ### Configuring the bootnode port[​](#configuring-the-bootnode-port "Direct link to Configuring the bootnode port") By default, the bootnode uses the `P2P_PORT` value. To customize the port, add to your `.env` file: ``` P2P_PORT=40400 P2P_BROADCAST_PORT=[PORT] ``` ### Persisting bootnode identity[​](#persisting-bootnode-identity "Direct link to Persisting bootnode identity") To maintain a consistent bootnode identity across restarts, specify a private key location in your `.env` file: ``` DATA_DIRECTORY=./data PEER_ID_PRIVATE_KEY_PATH=/var/lib/data/bootnode-peer-id ``` **How it works:** * If a private key exists at the path, the bootnode will use it for its identity * If no private key exists, a new one will be generated and saved to that location * This ensures your bootnode maintains the same ENR across restarts ### Obtaining your bootnode's ENR[​](#obtaining-your-bootnodes-enr "Direct link to Obtaining your bootnode's ENR") After starting your bootnode, obtain its ENR from the startup logs. You can share this ENR with node operators who want to connect to your bootnode. ### Adding your bootnode to the default set[​](#adding-your-bootnode-to-the-default-set "Direct link to Adding your bootnode to the default set") info The process for adding bootnodes to Aztec's default bootnode list is currently being finalized. For now, share your bootnode ENR directly with node operators who want to connect. ## Verification[​](#verification "Direct link to Verification") To verify your bootnode setup: ### For nodes connecting to a bootnode[​](#for-nodes-connecting-to-a-bootnode "Direct link to For nodes connecting to a bootnode") 1. **Check logs**: Look for messages indicating successful peer discovery 2. **Verify peer count**: Confirm your node has connected to peers from the bootnode 3. **Monitor network activity**: Ensure transactions are being gossiped correctly ### For bootnode operators[​](#for-bootnode-operators "Direct link to For bootnode operators") 1. **Confirm bootnode is running**: Check that the process started successfully 2. **Verify port accessibility**: Ensure the configured port is open and accessible 3. **Monitor peer connections**: Check logs for incoming peer connection requests 4. **Validate ENR generation**: Confirm your bootnode's ENR is displayed in the logs ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Cannot connect to bootnode[​](#cannot-connect-to-bootnode "Direct link to Cannot connect to bootnode") **Issue**: Your node fails to connect to the specified bootnode. **Solutions**: * Verify the ENR is correct and properly formatted * Check network connectivity to the bootnode's address * Ensure the bootnode is running and accessible * Confirm firewall rules allow P2P connections ### Bootnode not discovering peers[​](#bootnode-not-discovering-peers "Direct link to Bootnode not discovering peers") **Issue**: Your bootnode isn't discovering or storing peers. **Solutions**: * Verify the bootnode container is running with the correct configuration * Check that the P2P port is properly configured and accessible * Review logs for error messages or connection issues * Ensure sufficient system resources are available ### Private key path errors[​](#private-key-path-errors "Direct link to Private key path errors") **Issue**: Errors occur when specifying the peer ID private key path. **Solutions**: * Verify the path exists and is writable within the container * Check file permissions for the directory and file * Ensure the volume mount is correctly configured in docker-compose.yml * Confirm the private key file format is correct (if reusing an existing key) ## Next Steps[​](#next-steps "Direct link to Next Steps") * Monitor your bootnode or node connections regularly * Consider running multiple bootnodes for redundancy * Join the Aztec community to share your bootnode ENR with other operators --- # Building Node Software from Source ## Overview[​](#overview "Direct link to Overview") This guide shows you how to build the Aztec node Docker image from source, including all build tools and dependencies. Building from source allows you to: * Run a specific tagged version * Verify the build process matches the official CI pipeline * Customize the software for development or testing * Audit the complete build chain ### Requirements[​](#requirements "Direct link to Requirements") **Hardware:** * 4 core / 8 vCPU * 16 GB RAM for Docker * 150 GB free disk space * Stable internet connection **Software:** * Git to clone the repository * Docker version 20.10 or later with at least 16 GB RAM allocated This guide assumes you're using a standard Linux distribution such as Debian or Ubuntu. While other operating systems may work, these instructions are tested and optimized for Linux environments. These requirements are for building the software. Running a node has different requirements—see [Running a Full Node](/operate/operators/setup/running_a_node.md). ## Build Steps[​](#build-steps "Direct link to Build Steps") ### Step 1: Clone the Repository[​](#step-1-clone-the-repository "Direct link to Step 1: Clone the Repository") Clone the Aztec packages repository: ``` git clone https://github.com/AztecProtocol/aztec-packages.git cd aztec-packages ``` ### Step 2: Check Out a Version Tag[​](#step-2-check-out-a-version-tag "Direct link to Step 2: Check Out a Version Tag") Check out the version tag you want to build. For example, to build version 4.3.1: ``` git checkout v4.3.1 ``` tip View all available release tags with: ``` git tag | grep "^v[0-9]" ``` ### Step 3: Build the Container with Build Tools[​](#step-3-build-the-container-with-build-tools "Direct link to Step 3: Build the Container with Build Tools") Build the container image with all necessary compilation tools: ``` cd build-images/src docker build --target build -t aztec-build-local:3.0 . cd ../.. ``` tip The tag `aztec-build-local:3.0` avoids conflicts with the official Docker Hub image and clearly indicates this is a locally-built version. **What this does:** * Builds the `build` stage from `build-images/src/Dockerfile` * Installs Node.js 24.12.0 from NodeSource repository * Installs Clang 16, 18, and 20 from LLVM * Installs Rust 1.85.0 using the Rust toolchain installer with wasm32 targets * Downloads and installs WASI SDK 27 from GitHub releases * Builds Foundry v1.4.1 from source * Installs CMake, Ninja, and other build essentials note This step builds all compilation tooling from scratch. The Dockerfile uses multi-stage builds—you only need the `build` target. Other targets (`devbox` and `sysbox`) are for development environments. Verifying the Build Image After the build completes, inspect the image to verify its contents: ``` # Run a shell in the container to explore docker run -it --rm aztec-build-local:3.0 /bin/bash # Check specific versions once inside: node --version # Should show v24.12.0 rustc --version # Should show Rust 1.85.0 clang-20 --version # Should show clang 20.x forge --version # Should show v1.4.1 cmake --version # Should show cmake 3.24+ ``` You can review the Dockerfile at `build-images/src/Dockerfile` to see exactly what's installed and verify each step. ### Step 4: Compile the Source Code[​](#step-4-compile-the-source-code "Direct link to Step 4: Compile the Source Code") Run the bootstrap script inside the build container to compile all source code: ``` docker run --rm \ -v $(pwd):/workspaces/aztec-packages \ -w /workspaces/aztec-packages \ aztec-build-local:3.0 \ ./bootstrap.sh full ``` **What this does:** * Mounts your local repository into the container * Compiles C++ code (Barretenberg proving system) * Compiles Rust code (Noir compiler and ACVM) * Builds TypeScript/JavaScript packages * Writes compiled artifacts to your local filesystem (persist after container exits) * Runs tests to verify the build note The bootstrap process is incremental—if interrupted, restart it to resume from where it left off. Git submodules for L1 contract dependencies are initialized automatically during the build. ### Step 5: Build the Runtime Base Image[​](#step-5-build-the-runtime-base-image "Direct link to Step 5: Build the Runtime Base Image") Build the runtime base image with Node.js dependencies. This image contains only runtime requirements—no build tools or compiled code: ``` docker build -f release-image/Dockerfile.base -t aztecprotocol/release-image-base . ``` note The tag `aztecprotocol/release-image-base` must match exactly—the Dockerfile in Step 6 references this specific tag. This image is not published to Docker Hub; it exists only locally. **What this does:** * Installs production Node.js dependencies (no dev dependencies) * Includes Node.js 24 runtime and system utilities * Copies Foundry tools (anvil, cast) from the build container * Creates a slim Ubuntu-based runtime environment without build tools ### Step 6: Build the Final Release Image[​](#step-6-build-the-final-release-image "Direct link to Step 6: Build the Final Release Image") Build the final node image, combining the runtime environment (Step 5) with your compiled code (Step 4): ``` docker build -f release-image/Dockerfile --build-arg VERSION=4.3.1 -t aztec-local:4.3.1 . ``` tip The tag `aztec-local:4.3.1` avoids conflicts with the official Docker Hub image and clearly indicates this is a locally-built version. **Build arguments:** * `VERSION` - Sets the version string that appears in `aztec --version` **What this does:** * Starts from the `aztecprotocol/release-image-base` image (Step 5) * Copies compiled source code from your local filesystem (Step 4) * Sets up environment variables for Barretenberg and ACVM binaries * Configures the entrypoint to run the Aztec node ## Verification[​](#verification "Direct link to Verification") Verify your build completed successfully: ### Check Image Exists[​](#check-image-exists "Direct link to Check Image Exists") ``` docker images aztec-local ``` You should see your image listed: ``` REPOSITORY TAG IMAGE ID CREATED SIZE aztec-local 4.3.1 abc123def456 2 minutes ago 2.5GB ``` ### Verify Version[​](#verify-version "Direct link to Verify Version") ``` docker run --rm aztec-local:4.3.1 --version ``` Should display version 4.3.1. ### Test Basic Functionality[​](#test-basic-functionality "Direct link to Test Basic Functionality") ``` docker run --rm aztec-local:4.3.1 --help ``` Should display CLI help information without errors. If all checks pass, your image is ready to use. ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Build fails with "no space left on device"[​](#build-fails-with-no-space-left-on-device "Direct link to Build fails with \"no space left on device\"") **Issue**: Insufficient disk space. **Solutions**: * Clean up unused Docker images and build cache: `docker system prune -a` * Free up at least 150 GB of disk space * Ensure adequate storage for intermediate build artifacts ### Build image fails[​](#build-image-fails "Direct link to Build image fails") **Issue**: Errors during Step 3 when building `aztec-build-local:3.0`. **Solutions**: * Verify you're in the `build-images/src` directory * Ensure the `--target build` flag is specified * Retry the build if network issues occur while downloading Rust, LLVM, or WASI SDK * Review `build-images/src/Dockerfile` to identify the failing stage ### Build fails with "failed to solve with frontend dockerfile.v0: failed to create LLB definition"[​](#build-fails-with-failed-to-solve-with-frontend-dockerfilev0-failed-to-create-llb-definition "Direct link to Build fails with \"failed to solve with frontend dockerfile.v0: failed to create LLB definition\"") **Issue**: The release image build cannot find the base image. **Solutions**: * Ensure you completed Step 5 and built the base image with the exact tag: `aztecprotocol/release-image-base` * Verify the base image exists locally: `docker images aztecprotocol/release-image-base` * If missing, return to Step 5 and rebuild the base image ### Bootstrap compilation fails[​](#bootstrap-compilation-fails "Direct link to Bootstrap compilation fails") **Issue**: Errors during `./bootstrap.sh` in Step 4. **Solutions**: * Verify you're using the correct build image: `aztec-build-local:3.0` * Confirm you checked out a valid release tag (not a branch) * Retry the build—the bootstrap script is incremental and resumes where it left off * Review error messages for specifics—missing dependencies should not occur in the build container ### Docker runs out of memory[​](#docker-runs-out-of-memory "Direct link to Docker runs out of memory") **Issue**: Build crashes due to insufficient memory. **Solutions**: * Increase Docker's memory limit to at least 16 GB (Docker Desktop: Settings → Resources → Memory) * Close other applications to free system memory * Build on a machine with more RAM if possible ### Wrong version shows in `aztec --version`[​](#wrong-version-shows-in-aztec---version "Direct link to wrong-version-shows-in-aztec---version") **Issue**: Version argument not passed correctly. **Solutions**: * Ensure you used `--build-arg VERSION=X.Y.Z` when building the release image * The version should match the git tag without the 'v' prefix (e.g., `4.3.1` not `v4.3.1`) ## Using Your Custom Build[​](#using-your-custom-build "Direct link to Using Your Custom Build") ### Running a Node[​](#running-a-node "Direct link to Running a Node") Use your locally-built image with any node setup method. For Docker Compose, update your `docker-compose.yml`: ``` services: aztec-node: image: "aztec-local:4.3.1" # ... rest of configuration ``` See [Running a Full Node](/operate/operators/setup/running_a_node.md) for complete setup instructions. ### Using the CLI[​](#using-the-cli "Direct link to Using the CLI") Run the Aztec CLI directly from your custom image: ``` docker run --rm aztec-local:4.3.1 --version ``` ## Alternative Approaches[​](#alternative-approaches "Direct link to Alternative Approaches") ### Using Pre-built Build Image[​](#using-pre-built-build-image "Direct link to Using Pre-built Build Image") To save time, skip Step 3 and pull the pre-built image from Docker Hub, then tag it locally: ``` docker pull aztecprotocol/build:3.0 docker tag aztecprotocol/build:3.0 aztec-build-local:3.0 ``` This approach is faster but requires trusting the published image. The official image is built from the same `build-images/src/Dockerfile`. ### Building Without Docker[​](#building-without-docker "Direct link to Building Without Docker") To build without Docker, install all build dependencies locally and run `./bootstrap.sh` directly: * Install all toolchains from the build image (Node.js 24, Rust 1.85.0, Clang 20, CMake, wasi-sdk) * Run `bootstrap.sh check` to verify your environment * See `build-images/README.md` for details Using the build container is strongly recommended to ensure a consistent, tested environment. ## Understanding the Build Process[​](#understanding-the-build-process "Direct link to Understanding the Build Process") The build process uses these key files in the repository: * **`build-images/src/Dockerfile`** - Defines the build container with all compilation tools * **`bootstrap.sh`** - Main build script that compiles all source code (C++, Rust, TypeScript) * **`release-image/Dockerfile.base`** - Multi-stage Dockerfile that creates a slim runtime base image * **`release-image/Dockerfile`** - Final release image with compiled Aztec software * **`release-image/bootstrap.sh`** - Build script used in CI for Docker images The official CI pipeline follows a similar process. See `.github/workflows/ci3.yml` for how production images are built and deployed. ## Next Steps[​](#next-steps "Direct link to Next Steps") * Use your custom build to [run a full node](/operate/operators/setup/running_a_node.md) * Set up [monitoring](/operate/operators/monitoring.md) for your node * Review the [CLI reference](/operate/operators/reference/cli-reference.md) for configuration options * Join the [Aztec Discord](https://discord.gg/aztec) to discuss development and customization --- # High Availability Sequencers ## Overview[​](#overview "Direct link to Overview") This guide shows you how to set up high availability (HA) for your sequencer by running the same sequencer identity across multiple physical nodes with automatic coordination via a shared database. This configuration provides redundancy and resilience, ensuring your sequencer continues operating even if individual nodes fail. **What is High Availability for sequencers?** High availability means running multiple sequencer nodes that share the same attester identity but use different publisher addresses. The nodes coordinate through a shared PostgreSQL database to prevent double-signing across all validator duties. This allows your sequencer to: * Continue performing validator duties even if one node goes offline * Maintain uptime during maintenance windows and upgrades * Protect against infrastructure failures * Ensure you don't miss any validator duties * Automatically prevent double-signing & slashable actions through distributed locking ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before setting up HA sequencers, ensure you have: * Experience running a single sequencer node (see the [Sequencer Setup guide](/operate/operators/setup/sequencer_management.md)) * Understanding of basic keystore structure and configuration * Access to multiple servers or VMs for running separate nodes * Ability to securely distribute keys across infrastructure * A PostgreSQL database accessible by all HA nodes (for coordination and slashing protection) ## How HA Signing Works[​](#how-ha-signing-works "Direct link to How HA Signing Works") The HA signer uses a shared PostgreSQL database to coordinate signing across multiple nodes, preventing double-signing through distributed locking: 1. **Distributed Locking**: When a node needs to sign a duty (block proposal, checkpoint proposal, checkpoint attestation, governance vote, etc.), it first attempts to acquire a lock in the database for that specific duty (validator + slot + duty type) (+ block index within checkpoint for block proposals). 2. **First Node Wins**: The first node to acquire the lock proceeds with signing. Other nodes receive a `DutyAlreadySignedError`, which is expected and normal in HA setups. 3. **Slashing Protection**: If a node attempts to sign different data for the same duty, the database detects this and throws a `SlashingProtectionError`, preventing slashing conditions. 4. **Automatic Retry**: If a node fails mid-signing (crashes, network issue), the lock is automatically cleaned up after a timeout, allowing other nodes to retry. 5. **Background Cleanup**: The HA signer runs background tasks to clean up stuck duties (duties that were locked but never completed), ensuring the system remains healthy. This coordination happens automatically when `VALIDATOR_HA_SIGNING_ENABLED=true` - no manual intervention is required. Limitation: Post-Signature Failures If a node successfully signs a duty but fails **after** signing (before broadcasting the signature to the network), the duty will be missed. HA signing cannot help in this scenario because the duty is already marked as "signed" in the database, preventing other nodes from retrying. This is why it's still important to have reliable infrastructure even with HA enabled - HA protects against double-signing, not against all failure modes. ## What Duties Are Protected?[​](#what-duties-are-protected "Direct link to What Duties Are Protected?") The HA signing system provides double-signing protection for all validator duties: ### Block Production Duties[​](#block-production-duties "Direct link to Block Production Duties") 1. **Block Proposals**: Individual block proposals built during your assigned slot. Each slot may contain multiple blocks, and each block proposal is tracked separately with its `blockIndexWithinCheckpoint` (0, 1, 2...). 2. **Checkpoint Proposals**: The aggregated proposal submitted at the end of a slot that bundles all blocks from that slot. This is what gets submitted to L1 along with attestations. 3. **Checkpoint Attestations**: Your validator's signature attesting to a checkpoint proposal. Validators attest to checkpoints after validating all blocks in a slot. This is the primary consensus mechanism. 4. **Attestations and Signers**: Extended attestation format that includes additional signer information for consensus coordination. ### Governance Duties[​](#governance-duties "Direct link to Governance Duties") 5. **Governance Votes**: Signatures on governance proposals for protocol upgrades and parameter changes. HA protection ensures you don't accidentally vote twice on the same proposal. 6. **Slashing Votes**: Signatures on votes to slash misbehaving validators. Critical for validator accountability without risking self-slashing from duplicate votes. ## Why High Availability?[​](#why-high-availability "Direct link to Why High Availability?") ### Benefits of HA Configuration[​](#benefits-of-ha-configuration "Direct link to Benefits of HA Configuration") **1. Redundancy and Fault Tolerance** If one node crashes, experiences network issues, or needs maintenance, the other node continues operating. You won't miss any validator duties during: * Hardware failures * Network outages * Planned maintenance * Software upgrades * Infrastructure provider issues **2. Improved Uptime** With properly configured HA, your sequencer can achieve near-perfect uptime. You can perform rolling upgrades, switching nodes in and out of service without missing duties. ### The Core Concept[​](#the-core-concept "Direct link to The Core Concept") In an HA setup: * **Attester identity is shared** across both nodes (same private key) * **Publisher identity is unique** per node (different private keys) * **Shared database coordinates signing** - prevents double-signing through distributed locking * Both nodes run simultaneously and attempt to sign duties * **First node wins** - the database ensures only one node signs each duty * **Automatic failover** - if one node fails mid-signing, the other can retry * Only one proposal is accepted per slot (enforced by L1) The validator client automatically integrates with the HA signer when enabled, providing distributed locking and slashing protection without manual coordination. ## Setting Up High Availability Sequencers[​](#setting-up-high-availability-sequencers "Direct link to Setting Up High Availability Sequencers") ### Infrastructure Requirements[​](#infrastructure-requirements "Direct link to Infrastructure Requirements") **HA Setup (2 nodes):** * 2 separate servers/VMs * Each meeting the minimum sequencer requirements (see [Sequencer Setup](/operate/operators/setup/sequencer_management.md)) * Different physical locations or availability zones (recommended) * Reliable network connectivity for both nodes * Access to the same L1 infrastructure (or separate L1 endpoints) * **PostgreSQL database** accessible by all nodes (for coordination) * Monitoring and alerting for both nodes **Database Requirements:** * PostgreSQL 12 or later * Network access from all validator nodes * Sufficient connection pool capacity (default: 10 connections per node) * Regular backups recommended for production ### Key Management[​](#key-management "Direct link to Key Management") You'll need to generate: 1. **One shared attester key** - Your sequencer's identity (used by both nodes) 2. **One unique publisher key per node** - For submitting proposals 3. **Secure distribution method** - For safely deploying the shared attester key Secure Key Distribution The shared attester key must be distributed securely to both nodes. Consider using remote signers with: * Encrypted secrets management (HashiCorp Vault, AWS Secrets Manager, etc.) * Hardware security modules (HSMs) for production deployments Never transmit private keys over unencrypted channels or store them in version control. ### Step 1: Generate Keys[​](#step-1-generate-keys "Direct link to Step 1: Generate Keys") Generate a base keystore with multiple publishers using the Aztec CLI. This will create one attester identity with multiple publisher keys that can be distributed across your nodes. ``` # Generate base keystore with one attester and 2 publishers aztec validator-keys new \ --fee-recipient 0x0000000000000000000000000000000000000000000000000000000000000000 \ --staker-output \ --gse-address 0xa92ecFD0E70c9cd5E5cd76c50Af0F7Da93567a4f \ --l1-rpc-urls $ETH_RPC \ --mnemonic "your shared mnemonic phrase for key derivation" \ --address-index 0 \ --publisher-count 2 \ --data-dir ~/ha-keys-temp ``` This command generates: * **One attester** with both ETH and BLS keys (at derivation index 0) * **Two publisher keys** (at derivation indices 1 and 2) * All keys saved to `~/ha-keys-temp/key1.json` The output will show the complete keystore JSON with all generated keys. **Save this output securely** as you'll need to extract keys from it for each node. Managing Your Mnemonic Store your mnemonic phrase securely in a password manager or hardware wallet. You'll need it to: * Regenerate keys if lost * Add more publishers later * Recover your sequencer setup Never commit mnemonics to version control or share them over insecure channels. ### Step 2: Fund Publisher Accounts[​](#step-2-fund-publisher-accounts "Direct link to Step 2: Fund Publisher Accounts") Each publisher account needs ETH to pay for L1 gas when submitting proposals. You must maintain at least **0.1 ETH** in each publisher account. **Check publisher balances:** ``` # Check balance for Publisher 1 cast balance [PUBLISHER_1_ADDRESS] --rpc-url $ETH_RPC # Check balance for Publisher 2 cast balance [PUBLISHER_2_ADDRESS] --rpc-url $ETH_RPC ``` **Example:** ``` cast balance 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb --rpc-url $ETH_RPC # Output: 100000000000000000 (0.1 ETH in wei) ``` Balance Monitoring Monitor these balances regularly to ensure they don't drop below 0.1 ETH. Falling below this threshold risks slashing. Consider setting up automated alerts when balances drop below 0.15 ETH. ### Step 3: Extract Keys from Generated Keystore[​](#step-3-extract-keys-from-generated-keystore "Direct link to Step 3: Extract Keys from Generated Keystore") Open the generated keystore file (`~/ha-keys-temp/key1.json`) and extract the keys. The file will look something like this: ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "0xABC...123", // Shared attester ETH key "bls": "0xDEF...456" // Shared attester BLS key }, "publisher": [ "0x111...AAA", // Publisher 1 (for Node 1) "0x222...BBB" // Publisher 2 (for Node 2) ], "feeRecipient": "0x0000000000000000000000000000000000000000000000000000000000000000" } ] } ``` You'll use: * The **same attester keys** (both ETH and BLS) on both nodes * A **different publisher key** for each node ### Step 4: Create Node-Specific Keystores[​](#step-4-create-node-specific-keystores "Direct link to Step 4: Create Node-Specific Keystores") Create a separate keystore file for each node, using the same attester but different publishers: **Node 1 Keystore** (`~/node1/keys/keystore.json`): Use the same attester ETH and BLS keys, but only Publisher 1: ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "0xABC...123", "bls": "0xDEF...456" }, "publisher": ["0x111...AAA"], "feeRecipient": "0x0000000000000000000000000000000000000000000000000000000000000000" } ] } ``` **Node 2 Keystore** (`~/node2/keys/keystore.json`): Use the same attester ETH and BLS keys, but only Publisher 2: ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "0xABC...123", "bls": "0xDEF...456" }, "publisher": ["0x222...BBB"], "feeRecipient": "0x0000000000000000000000000000000000000000000000000000000000000000" } ] } ``` Security Best Practice After creating node-specific keystores, **securely delete** the base keystore file (`~/ha-keys-temp/key1.json`) that contains all publishers together. Each node should only have access to its own publisher key. ### Step 5: Deploy Keystores to Nodes[​](#step-5-deploy-keystores-to-nodes "Direct link to Step 5: Deploy Keystores to Nodes") Securely transfer each keystore to its respective node: ``` # Example: Copy keystores to remote nodes via SCP scp ~/node1/keys/keystore.json user@node1-server:~/aztec/keys/ scp ~/node2/keys/keystore.json user@node2-server:~/aztec/keys/ ``` Ensure proper file permissions on each node: ``` chmod 600 ~/aztec/keys/keystore.json ``` ### Step 6: Set Up the HA Database[​](#step-6-set-up-the-ha-database "Direct link to Step 6: Set Up the HA Database") Before starting your nodes, you need a PostgreSQL database that all HA nodes can access for coordination. **1. Provision a PostgreSQL database:** For production HA setups, we recommend using a managed database service with built-in high availability: * **AWS RDS PostgreSQL** with Multi-AZ for automatic failover * **Google Cloud SQL for PostgreSQL** with high availability configuration * **Azure Database for PostgreSQL** with zone redundancy * **Self-hosted PostgreSQL** with streaming replication and automatic failover (if you manage your own infrastructure) Critical: All Nodes Must Connect to the Same Primary Database The HA signing system relies on atomic database operations for distributed locking. **All validator nodes MUST connect to the SAME PRIMARY database instance**. The configurations above are safe because they use automatic failover to a single primary. **DO NOT use:** * Read replicas (replication lag breaks consistency) * Multi-master or active-active configurations (breaks distributed locking) * Different database instances per node (defeats the purpose of HA coordination) All validator nodes must use the same database connection string that points to the current primary. The key requirements are: * PostgreSQL 12 or later * **Single primary database** that all validator nodes connect to * Network accessible from all validator nodes * Sufficient connection pool capacity (default: 10 connections per node) * A database created for the HA signer (e.g., `validator_ha`) * Automatic failover is good (keeps high availability), but only one primary at a time **Example using psql** (if manually creating the database): ``` # Connect to your PostgreSQL instance psql -h your-db-host -U postgres # Create the database CREATE DATABASE validator_ha; # Exit psql \q ``` **2. Run database migrations:** The HA signer uses database migrations to set up the required tables. Run migrations **once** before starting your nodes: ``` aztec migrate-ha-db up \ --database-url postgresql://user:password@host:port/validator_ha ``` Migration Safety Migrations are idempotent and safe to run concurrently, but for cleaner logs, run them once before starting nodes. You can also run migrations from an init container or separate migration job in Kubernetes. **3. Verify the database setup:** Check that the required tables were created: ``` # Using psql psql postgresql://user:password@host:port/validator_ha -c "\dt" # Or using your cloud provider's database console ``` You should see tables like `validator_duties`, `schema_version` and `pmigrations`. ### Step 7: Configure HA Signing[​](#step-7-configure-ha-signing "Direct link to Step 7: Configure HA Signing") Configure each node with HA signing enabled. Set these environment variables on **each node**: ``` # Enable HA signing export VALIDATOR_HA_SIGNING_ENABLED=true # PostgreSQL connection string (same database for all nodes) export VALIDATOR_HA_DATABASE_URL=postgresql://user:password@host:port/validator_ha # Unique node identifier (different for each node) export VALIDATOR_HA_NODE_ID=validator-node-1 # Use validator-node-2 for second node # Optional: Tune polling and timeout settings export VALIDATOR_HA_POLLING_INTERVAL_MS=100 # Default: 100ms export VALIDATOR_HA_SIGNING_TIMEOUT_MS=3000 # Default: 3000ms ``` **Required Environment Variables:** | Variable | Description | Example | | ------------------------------ | ------------------------------------------ | ------------------------------------- | | `VALIDATOR_HA_SIGNING_ENABLED` | Enable HA signing (required) | `true` | | `VALIDATOR_HA_DATABASE_URL` | PostgreSQL connection string (required) | `postgresql://user:pass@host:5432/db` | | `VALIDATOR_HA_NODE_ID` | Unique identifier for this node (required) | `validator-node-1` | **Optional Tuning Variables:** | Variable | Description | Default | | -------------------------------------- | -------------------------------- | ------------------ | | `VALIDATOR_HA_POLLING_INTERVAL_MS` | How often to check duty status | `100` | | `VALIDATOR_HA_SIGNING_TIMEOUT_MS` | Max wait for in-progress signing | `3000` | | `VALIDATOR_HA_MAX_STUCK_DUTIES_AGE_MS` | Max age before cleanup | `2 * slotDuration` | | `VALIDATOR_HA_POOL_MAX` | Max database connections | `10` | | `VALIDATOR_HA_POOL_MIN` | Min database connections | `0` | When `VALIDATOR_HA_SIGNING_ENABLED=true`, the validator client automatically: * Creates an HA signer using the provided configuration * Wraps the base keystore with `HAKeyStore` for HA-protected signing * Coordinates signing across nodes via PostgreSQL to prevent double-signing * Provides slashing protection to block conflicting signatures ### Step 8: Start All Nodes[​](#step-8-start-all-nodes "Direct link to Step 8: Start All Nodes") Start each node (assuming you are using Docker Compose): ``` # On each server docker compose up -d ``` Ensure both nodes are configured with: * The same network (`--network mainnet`) * Proper L1 endpoints * Correct P2P configuration * **HA signing enabled** with the same database URL * **Unique node IDs** for each node * Adequate resources ## Verification and Monitoring[​](#verification-and-monitoring "Direct link to Verification and Monitoring") ### Verify Your HA Setup[​](#verify-your-ha-setup "Direct link to Verify Your HA Setup") **1. Check that both nodes are running:** ``` # On each server curl http://localhost:8080/status # Or for Docker docker compose logs -f aztec-sequencer ``` **2. Confirm nodes recognize the shared attester:** Check logs for messages indicating the attester address is loaded correctly. Both nodes should show the same attester address. **3. Verify HA signer is active:** Look for log messages indicating HA signer initialization: ``` HAKeyStore initialized { nodeId: 'validator-node-1' } ``` **4. Verify different publishers:** Each node's logs should show a different publisher address being used for submitting transactions. **5. Monitor attestations:** Watch L1 for attestations from your sequencer's attester address. You should see attestations being submitted even if one node goes offline. **6. Check database coordination:** Query the database to see which node signed recent duties: ``` SELECT validator_address, slot, duty_type, node_id, status, started_at FROM validator_duties ORDER BY started_at DESC LIMIT 10; ``` You should see duties distributed across both nodes, with only one node signing each duty. **7. Check duty type distribution:** View the distribution of different duty types across your nodes: ``` SELECT duty_type, node_id, COUNT(*) as duty_count, COUNT(CASE WHEN status = 'signed' THEN 1 END) as signed_count FROM validator_duties WHERE started_at > NOW() - INTERVAL '1 hour' GROUP BY duty_type, node_id ORDER BY duty_type, node_id; ``` This helps verify that both nodes are handling all types of validator duties (block proposals, checkpoint proposals, attestations, votes, etc.). ### Testing Failover[​](#testing-failover "Direct link to Testing Failover") To verify HA is working correctly: 1. **Monitor baseline**: Note the duty completion rate with both nodes running 2. **Check database**: Verify both nodes are signing duties (query `validator_duties` table) 3. **Stop one node**: `docker compose down` on one server 4. **Verify continuity**: Check that the remaining node continues handling all validator duties 5. **Check logs**: The remaining node should show normal operation without errors 6. **Monitor database**: The remaining node should continue signing all duty types 7. **Restart the stopped node**: Verify it rejoins seamlessly and resumes signing If validator duties stop when you stop one node, check: * Database connectivity from the remaining node * HA signing is enabled (`VALIDATOR_HA_SIGNING_ENABLED=true`) * Node ID is correctly configured * Database migrations were run successfully ## Operational Best Practices[​](#operational-best-practices "Direct link to Operational Best Practices") ### Load Balancing L1 Access[​](#load-balancing-l1-access "Direct link to Load Balancing L1 Access") If possible, configure each node with its own L1 infrastructure: * **Node 1**: L1 endpoints in Region A * **Node 2**: L1 endpoints in Region B This protects against L1 provider outages affecting both nodes simultaneously. ### Geographic Distribution[​](#geographic-distribution "Direct link to Geographic Distribution") For maximum resilience, distribute nodes across: * Multiple data centers * Different cloud providers * Different geographic regions * Different network availability zones This protects against regional failures, provider outages, and network issues. ### Regular Testing[​](#regular-testing "Direct link to Regular Testing") Periodically test your HA setup: * Simulate node failures (stop nodes intentionally) * Test network partitions (firewall rules) * Test database connectivity issues (temporarily block database access) * Verify monitoring and alerting * Practice recovery procedures * Test rolling upgrades * Verify database cleanup of stuck duties ### Production Deployment Considerations[​](#production-deployment-considerations "Direct link to Production Deployment Considerations") **Database High Availability:** For production, your coordination database should also be highly available: * Use a managed PostgreSQL service (AWS RDS, Google Cloud SQL, Azure Database) with automatic failover * Enable automatic failover to standby replicas (single primary with hot standby) * **Do not use read replicas** for HA signing connections (all nodes must connect to primary) * Configure connection pooling appropriately (`VALIDATOR_HA_POOL_MAX`) * Monitor database performance and connection counts * Set up database backups and point-in-time recovery * Ensure all validator nodes use the same connection string pointing to the primary **Migration Strategy:** Run database migrations before deploying new validator nodes: ``` # Option 1: Run migrations in CI/CD pipeline aztec migrate-ha-db up --database-url $VALIDATOR_HA_DATABASE_URL # Option 2: Use Kubernetes init container (see validator-ha-signer README) # Option 3: Use separate migration job ``` **Monitoring:** Monitor these key metrics: * Database connection pool usage * Signing success/failure rates per node and per duty type * `DutyAlreadySignedError` frequency (expected in HA) * Database query latency * Stuck duty cleanup frequency * Distribution of duty types across nodes (should be relatively even over time) ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Both Nodes Stopped Performing Duties[​](#both-nodes-stopped-performing-duties "Direct link to Both Nodes Stopped Performing Duties") **Issue**: No attestations, proposals, or other validator duties from either node. **Solutions**: * Verify both nodes aren't simultaneously offline * Check L1 connectivity from each node * Verify the shared attester key is correct in both keystores * Check that the sequencer is still registered and active on L1 * Review logs for errors on both nodes * **Verify database connectivity** - check that both nodes can connect to PostgreSQL * **Check HA signing is enabled** - verify `VALIDATOR_HA_SIGNING_ENABLED=true` on both nodes * **Review database logs** - check for connection errors or timeouts * **Query validator\_duties table** - check if duties are being attempted but failing ### Database Connection Issues[​](#database-connection-issues "Direct link to Database Connection Issues") **Issue**: Nodes can't connect to the database or signing fails with database errors. **Solutions**: * Verify database is running and accessible from both nodes * Check network connectivity: `psql $VALIDATOR_HA_DATABASE_URL -c "SELECT 1;"` * Verify connection string format: `postgresql://user:password@host:port/database` * Check firewall rules allow connections from validator nodes * Verify database credentials are correct * Check connection pool limits (increase `VALIDATOR_HA_POOL_MAX` if needed) * Review database logs for connection errors ### Duplicate Signatures Appearing[​](#duplicate-signatures-appearing "Direct link to Duplicate Signatures Appearing") **Issue**: Seeing duplicate signatures for the same duty (proposals, attestations, votes) from your sequencer. **Solutions**: * Verify each node has a unique publisher key * Check that publisher keys aren't duplicated across keystores * Ensure nodes aren't sharing the same keystore file * Review keystore configuration on each node * **Verify HA signing is enabled** - duplicate signatures shouldn't occur with HA enabled * **Check database configuration** - see "Incorrect Database Configuration" below * **Check database** - query `validator_duties` to see if both nodes attempted to sign the same duty * **Review logs** for `DutyAlreadySignedError` (expected) or `SlashingProtectionError` (indicates issue) * **Check duty type** - different duty types (block proposals vs checkpoint proposals vs attestations) should be tracked separately ### Incorrect Database Configuration[​](#incorrect-database-configuration "Direct link to Incorrect Database Configuration") **Issue**: Duplicate signatures despite HA being enabled, or inconsistent behavior across nodes. **Root Cause**: Nodes may be connecting to different database instances or read replicas instead of the same primary database. **Solutions**: * **Verify all nodes use the same connection string** - check `VALIDATOR_HA_DATABASE_URL` on all nodes * **Confirm connecting to primary** - ensure connection string points to the primary database, not a read replica * **Check for multi-master setup** - multi-master or active-active database configurations will break distributed locking * **Test database connectivity** - from each node, run: ``` psql $VALIDATOR_HA_DATABASE_URL -c "SELECT pg_is_in_recovery();" ``` Should return `f` (false) for all nodes, indicating connection to the primary * **Review database failover events** - if using managed services, check if recent failover caused connection issues * **Verify no load balancing to replicas** - ensure database connection pooling or load balancers don't route to read replicas Critical If nodes connect to different database instances or read replicas, the distributed locking will fail and you **will** double-sign, leading to slashing. All nodes must connect to the same primary database. ### One Node Not Contributing[​](#one-node-not-contributing "Direct link to One Node Not Contributing") **Issue**: One node running but not performing validator duties. **Solutions**: * Check that node's sync status * Verify keystore is loaded correctly * Check network connectivity to L1 * Review logs for specific errors * Confirm publisher account has sufficient ETH * **Verify HA configuration** - check `VALIDATOR_HA_SIGNING_ENABLED`, `VALIDATOR_HA_DATABASE_URL`, and `VALIDATOR_HA_NODE_ID` * **Check database** - query to see if the node is attempting to sign duties * **Review logs for HA errors** - look for `DutyAlreadySignedError` (normal) or database connection errors * **Verify node ID is unique** - both nodes must have different `VALIDATOR_HA_NODE_ID` values * **Check duty distribution** - use the duty type distribution query from the verification section ### Keystore Loading Failures[​](#keystore-loading-failures "Direct link to Keystore Loading Failures") **Issue**: Node fails to load the keystore. **Solutions**: * Verify keystore.json syntax is valid * Check file permissions (readable by the node process) * Ensure the keystore path is correct * Validate all private keys are properly formatted * Review the [Keystore Troubleshooting guide](/operate/operators/keystore/troubleshooting.md) ### Database Migration Issues[​](#database-migration-issues "Direct link to Database Migration Issues") **Issue**: Migrations fail or nodes can't start due to missing tables. **Solutions**: * Verify migrations were run: `aztec migrate-ha-db up --database-url $VALIDATOR_HA_DATABASE_URL` * Check database permissions - the user needs CREATE TABLE privileges * Review migration logs for specific errors * Verify database version is PostgreSQL 12 or later * Check that the `validator_duties`, `schema_version` and `pmigrations` (created by node-pg-migrate) tables exist ## Related Guides[​](#related-guides "Direct link to Related Guides") Running Multiple Sequencers Per Node Want to run multiple sequencer identities on a **single node** instead? See the [Advanced Keystore Patterns guide](/operate/operators/keystore/advanced-patterns.md#multiple-sequencers)—that's a different use case from HA. ## Next Steps[​](#next-steps "Direct link to Next Steps") * Review the [Advanced Keystore Patterns guide](/operate/operators/keystore/advanced-patterns.md) for multiple sequencers per node * Set up [monitoring and observability](/operate/operators/monitoring.md) for your HA infrastructure * Learn about [governance participation](/operate/operators/sequencer-management/creating_and_voting_on_proposals.md) as a sequencer * Join the [Aztec Discord](https://discord.gg/aztec) for operator support and best practices --- # Registering a Sequencer ## Overview[​](#overview "Direct link to Overview") This guide covers registering your sequencer on the Aztec network through the staking dashboard for **self-staking**. This is one of two ways to participate as a sequencer: 1. **Self-staking** (this guide): You provide your own stake via the staking dashboard 2. **Delegated staking**: You receive stake from delegators (see [Running as a Staking Provider](/operate/operators/setup/become_a_staking_provider.md)) Before proceeding, ensure you have completed the [Sequencer Setup Guide](/operate/operators/setup/sequencer_management.md) and your node is running. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Completed sequencer node setup with keystore generated * Access to your **public keystore** file (`keyN_staker_output.json`) * Sufficient **Aztec Token Position (ATP)** or **Aztec Token Vault (ATV)** balance for staking * Wallet with ETH for gas fees * Web browser for accessing the staking dashboard ## Understanding Your Keystore[​](#understanding-your-keystore "Direct link to Understanding Your Keystore") When you generated your sequencer keys, two files were automatically created: 1. **Private keystore** (`~/.aztec/keystore/keyN.json`) - Contains private keys, used by your sequencer node. Keep this secure and never share it. 2. **Public keystore** (`~/.aztec/keystore/keyN_staker_output.json`) - Contains only public information, used for registration via the staking dashboard. ### Public Keystore Structure[​](#public-keystore-structure "Direct link to Public Keystore Structure") The public keystore contains the following information needed for registration: ``` [ { "attester": "0xYOUR_ATTESTER_ADDRESS", "publicKeyG1": { "x": "FIELD_ELEMENT_AS_DECIMAL_STRING", "y": "FIELD_ELEMENT_AS_DECIMAL_STRING" }, "publicKeyG2": { "x0": "FIELD_ELEMENT_AS_DECIMAL_STRING", "x1": "FIELD_ELEMENT_AS_DECIMAL_STRING", "y0": "FIELD_ELEMENT_AS_DECIMAL_STRING", "y1": "FIELD_ELEMENT_AS_DECIMAL_STRING" }, "proofOfPossession": { "x": "FIELD_ELEMENT_AS_DECIMAL_STRING", "y": "FIELD_ELEMENT_AS_DECIMAL_STRING" } } ] ``` **Fields explained:** * **`attester`**: Your Ethereum attester address (sequencer identifier) * **`publicKeyG1`**: BLS public key on the G1 curve (x, y coordinates) * **`publicKeyG2`**: BLS public key on the G2 curve (x0, x1, y0, y1 coordinates) * **`proofOfPossession`**: Cryptographic proof to prevent rogue key attacks tip The public keystore contains no private keys and is safe to share with the staking dashboard or other parties. ## Preparing Your Keystore File[​](#preparing-your-keystore-file "Direct link to Preparing Your Keystore File") ### Single Sequencer[​](#single-sequencer "Direct link to Single Sequencer") If you're registering one sequencer, simply use the `keyN_staker_output.json` file that was generated when you created your keys. ### Multiple Sequencers[​](#multiple-sequencers "Direct link to Multiple Sequencers") If you're registering multiple sequencers in a single transaction, combine the individual keystore files into a single JSON array. Each object in the array represents one sequencer. **Example for two sequencers:** ``` [ { "attester": "0xATTESTER_ADDRESS_1", "publicKeyG1": { "x": "0x...", "y": "0x..." }, "publicKeyG2": { "x0": "0x...", "x1": "0x...", "y0": "0x...", "y1": "0x..." }, "proofOfPossession": { "x": "0x...", "y": "0x..." } }, { "attester": "0xATTESTER_ADDRESS_2", "publicKeyG1": { "x": "0x...", "y": "0x..." }, "publicKeyG2": { "x0": "0x...", "x1": "0x...", "y0": "0x...", "y1": "0x..." }, "proofOfPossession": { "x": "0x...", "y": "0x..." } } ] ``` Simply copy the contents of each `keyN_staker_output.json` file and combine them into a single array. ## Registration Steps[​](#registration-steps "Direct link to Registration Steps") Follow these steps to register your sequencer(s) through the staking dashboard: 1. **Navigate to the staking dashboard** at 2. **Connect your wallet** with the account that holds your Aztec Token Position (ATP) or Aztec Token Vault (ATV) balance 3. **Click "Stake"** ![Staking dashboard home](/assets/images/staking_dashboard_1-f9dd165c0b8b06914f0baa0befa2281a.png) 4. **Select "Run your own Sequencer"** ![Select sequencer option](/assets/images/staking_dashboard_2-ef4c50308b741b29cfec7f995b46baf1.png) 5. **Click through "Start Registration"** after reviewing the requirements 6. **Select the ATP or ATV balance you want to stake** 7. **Upload your keystore JSON file** (either single or combined multi-sequencer file) ![Upload keystore file](/assets/images/staking_dashboard_3-8dcef3510e7073a4b559b11ddb78a254.png) 8. **Confirm your attester/sequencer addresses** ![Confirm addresses](/assets/images/staking_dashboard_4-f7c95ff94cdad0fa714e05a8765c49ac.png) 9. **Approve token spend** in your wallet ![Approve tokens](/assets/images/staking_dashboard_5-9a9d86173c781ba0d45525487dd4fbd5.png) 10. **Add staking for all sequencers to the queue** ![Add to queue](/assets/images/staking_dashboard_6-e791f311fccc92f91dd1901022d73f73.png) 11. **Execute transactions** in the dashboard ![Execute transactions](/assets/images/staking_dashboard_7-411d05fecee6a3f62ff3d580aa932410.png) 12. **Confirm each transaction** in your wallet 13. **Click "Complete"** when all transactions are confirmed 14. **Verification**: Your sequencers have entered the queue. You can verify this at ## Verification[​](#verification "Direct link to Verification") After registration, verify your sequencer is properly registered: ### Via Staking Dashboard[​](#via-staking-dashboard "Direct link to Via Staking Dashboard") Use the staking dashboard to: * View your sequencer's registration status * Monitor your stake amount * Track sequencer performance metrics ### Via Blockchain Explorer[​](#via-blockchain-explorer "Direct link to Via Blockchain Explorer") You can verify your sequencers are in the queue at ### Via Smart Contract[​](#via-smart-contract "Direct link to Via Smart Contract") You can also query the status directly using the Rollup contract. See [Useful Commands](/operate/operators/sequencer-management/useful-commands.md) for detailed instructions. ## Next Steps[​](#next-steps "Direct link to Next Steps") After registering your sequencer: 1. **Monitor performance**: Track your sequencer's attestation rate and block proposals via the staking dashboard 2. **Maintain uptime**: Keep your sequencer node running with high availability 3. **Monitor your stake**: Ensure your stake remains above the ejection threshold 4. **Stay informed**: Join the [Aztec Discord](https://discord.gg/aztec) for operator support and network updates ## Alternative: Running with Delegated Stake[​](#alternative-running-with-delegated-stake "Direct link to Alternative: Running with Delegated Stake") If you prefer to run a sequencer backed by delegated stake instead of self-staking, see the [Becoming a Staking Provider](/operate/operators/setup/become_a_staking_provider.md) guide. --- # Running a Full Node ## Overview[​](#overview "Direct link to Overview") This guide covers the steps required to run a full node on Aztec using Docker Compose. A full node allows you to connect and interact with the network, providing an interface to send and receive transactions and state updates without relying on third parties. You should run your own full node if you want to interact with the network in the most privacy-preserving way. It's also a great way to support the Aztec network and get involved with the community. ### Minimum Hardware Requirements[​](#minimum-hardware-requirements "Direct link to Minimum Hardware Requirements") * 8 core / 16 vCPU (released in 2015 or later) * 16 GB RAM * 1 TB NVMe SSD * 25 Mbps network connection These requirements are subject to change as the network throughput increases. **Before proceeding:** Ensure you've reviewed and completed the [prerequisites](/operate/operators/prerequisites.md). This setup includes only essential settings. The `--network mainnet` flag applies network-specific defaults—see the [CLI reference](/operate/operators/reference/cli-reference.md) for all available configuration options. ## Setup[​](#setup "Direct link to Setup") ### Step 1: Set Up Directory Structure[​](#step-1-set-up-directory-structure "Direct link to Step 1: Set Up Directory Structure") Create the directory structure for node data: ``` mkdir -p aztec-node/data cd aztec-node touch .env ``` ### Step 2: Configure Environment Variables[​](#step-2-configure-environment-variables "Direct link to Step 2: Configure Environment Variables") Add the following to your `.env` file: ``` DATA_DIRECTORY=./data LOG_LEVEL=info ETHEREUM_HOSTS=[your L1 execution endpoint] L1_CONSENSUS_HOST_URLS=[your L1 consensus endpoint] ETHEREUM_DEBUG_HOSTS=[your trace capable L1 execution endpoint] P2P_IP=[your external IP address] P2P_PORT=40400 AZTEC_PORT=8080 AZTEC_ADMIN_PORT=8880 ``` tip Find your public IP address with: `curl ipv4.icanhazip.com` warning In order to retrieve blocks posted to L1 via non-standard contract interactions, it is necessary to have access to an L1 rpc endpoint with 'trace' capability (either `trace_transaction` or `debug_traceTransaction`). The variable `ETHEREUM_DEBUG_HOSTS` is used to provide these url/s to the node. If not provided, the value of this will default to that set in `ETHEREUM_HOSTS`. The node will validate whether it is able to execute a trace call on the provided url/s, if not, it looks to the value set in `ETHEREUM_ALLOW_NO_DEBUG_HOSTS` to determine whether this should prevent the node from starting. By default `ETHEREUM_ALLOW_NO_DEBUG_HOSTS` is `true`, allowing the node to start. Any url provided in `ETHEREUM_DEBUG_HOSTS` will only be used in the case of having to execute a trace, it won't be used in regular L1 interactions. Note - if the node does not have access to an rpc url that is capable of trace calls and it encounters a block posted via a transaction using non-standard contract interactions, it may become stuck and unable to progress the chain. ### Step 3: Create Docker Compose File[​](#step-3-create-docker-compose-file "Direct link to Step 3: Create Docker Compose File") Create a `docker-compose.yml` file in your `aztec-node` directory: ``` services: aztec-node: image: "aztecprotocol/aztec:4.3.1" container_name: "aztec-node" ports: - ${AZTEC_PORT}:${AZTEC_PORT} - ${P2P_PORT}:${P2P_PORT} - ${P2P_PORT}:${P2P_PORT}/udp volumes: - ${DATA_DIRECTORY}:/var/lib/data environment: DATA_DIRECTORY: /var/lib/data LOG_LEVEL: ${LOG_LEVEL} ETHEREUM_HOSTS: ${ETHEREUM_HOSTS} L1_CONSENSUS_HOST_URLS: ${L1_CONSENSUS_HOST_URLS} ETHEREUM_DEBUG_HOSTS: ${ETHEREUM_DEBUG_HOSTS} P2P_IP: ${P2P_IP} P2P_PORT: ${P2P_PORT} AZTEC_PORT: ${AZTEC_PORT} AZTEC_ADMIN_PORT: ${AZTEC_ADMIN_PORT} entrypoint: >- node --no-warnings /usr/src/yarn-project/aztec/dest/bin/index.js start --node --archiver --network mainnet networks: - aztec restart: always networks: aztec: name: aztec ``` Security: Admin Port Not Exposed The admin port (8880) is intentionally **not exposed** to the host machine for security reasons. The admin API provides sensitive operations like configuration changes and database rollbacks that should never be accessible from outside the container. If you need to access admin endpoints, use `docker exec`: ``` docker exec -it aztec-node curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"nodeAdmin_getConfig","params":[],"id":1}' ``` ### Step 4: Start the Node[​](#step-4-start-the-node "Direct link to Step 4: Start the Node") Start the node: ``` docker compose up -d ``` ## Verification[​](#verification "Direct link to Verification") Once your node is running, verify it's working correctly: ### Check Node Sync Status[​](#check-node-sync-status "Direct link to Check Node Sync Status") Check the current sync status: ``` curl -s -X POST -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getL2Tips","params":[],"id":67}' \ http://localhost:8080 | jq -r ".result.proven.number" ``` Compare the output with block explorers (see [Networks page](/networks.md) for explorer links). ### Check Node Status[​](#check-node-status "Direct link to Check Node Status") ``` curl http://localhost:8080/status ``` ### Verify Port Connectivity[​](#verify-port-connectivity "Direct link to Verify Port Connectivity") ``` # Check TCP connectivity on port 40400 nc -vz [YOUR_EXTERNAL_IP] 40400 # Should return: "Connection to [YOUR_EXTERNAL_IP] 40400 port [tcp/*] succeeded!" # Check UDP connectivity on port 40400 nc -vu [YOUR_EXTERNAL_IP] 40400 # Should return: "Connection to [YOUR_EXTERNAL_IP] 40400 port [udp/*] succeeded!" ``` ### View Logs[​](#view-logs "Direct link to View Logs") ``` docker compose logs -f aztec-node ``` If all checks pass, your node should be up, running, and connected to the network. ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Port forwarding not working[​](#port-forwarding-not-working "Direct link to Port forwarding not working") **Issue**: Your node cannot connect to peers. **Solutions**: * Verify your external IP address matches the `P2P_IP` setting * Check firewall rules on your router and local machine * Test connectivity using: `nc -zv [your-ip] 40400` ### Node not syncing[​](#node-not-syncing "Direct link to Node not syncing") **Issue**: Your node is not synchronizing with the network. **Solutions**: * Check L1 endpoint connectivity * Verify both execution and consensus clients are fully synced * Review logs for specific error messages * Ensure L1 endpoints support high throughput ### Docker issues[​](#docker-issues "Direct link to Docker issues") **Issue**: Container won't start or crashes. **Solutions**: * Ensure Docker and Docker Compose are up to date * Check disk space availability * Verify the `.env` file is properly formatted * Review container logs: `docker compose logs aztec-node` ## Next Steps[​](#next-steps "Direct link to Next Steps") * Review [syncing best practices](/operate/operators/setup/syncing_best_practices.md) for faster synchronization * Learn about [bootnode operation](/operate/operators/setup/bootnode_operation.md) for peer discovery * Check the [CLI reference](/operate/operators/reference/cli-reference.md) for advanced configuration options * Join the [Aztec Discord](https://discord.gg/aztec) for support and community discussions --- # Running a Prover ## Overview[​](#overview "Direct link to Overview") This guide covers the steps required to run a prover on the Aztec network. Operating a prover is a resource-intensive role typically undertaken by experienced engineers due to its technical complexity and hardware requirements. Aztec provers are critical infrastructure components. They generate cryptographic proofs attesting to transaction correctness, ultimately producing a single rollup proof submitted to Ethereum. Prerequisites Before proceeding, ensure you've reviewed and completed the [prerequisites](/operate/operators/prerequisites.md). ## Prover Architecture[​](#prover-architecture "Direct link to Prover Architecture") The prover consists of three main components: 1. **Prover node**: Polls L1 for unproven epochs, creates prover jobs, distributes them to the broker, and submits the final rollup proof to the rollup contract. 2. **Prover broker**: Manages the job queue, distributing work to agents and collecting results. 3. **Prover agent(s)**: Executes proof generation jobs in a stateless manner. ## Minimum Requirements[​](#minimum-requirements "Direct link to Minimum Requirements") ### Prover Node[​](#prover-node "Direct link to Prover Node") * 16 core / 32 vCPU (released in 2015 or later) * 16 GB RAM * 1 TB NVMe SSD * 25 Mbps network connection ### Prover Broker[​](#prover-broker "Direct link to Prover Broker") * 8 core / 16 vCPU (released in 2015 or later) * 16 GB RAM * 10 GB SSD ### Prover Agents[​](#prover-agents "Direct link to Prover Agents") **For each agent:** * 32 core / 64 vCPU (released in 2015 or later) * 128 GB RAM * 10 GB SSD These requirements are subject to change as the network throughput increases. Prover agents require high-performance hardware, typically data center-grade infrastructure. Running Multiple Agents You can run multiple prover agents on a single machine by adjusting `PROVER_AGENT_COUNT`. Hardware requirements scale approximately linearly: * **2 agents**: 64 cores, 256 GB RAM * **3 agents**: 96 cores, 384 GB RAM * **4 agents**: 128 cores, 512 GB RAM ## Generating Keys[​](#generating-keys "Direct link to Generating Keys") Before setting up your prover, you need to generate the required Ethereum private key for the prover publisher. ### Prover Publisher Private Key[​](#prover-publisher-private-key "Direct link to Prover Publisher Private Key") The prover publisher key is used to submit proofs to L1. This account needs ETH funding to pay for L1 gas. Generate an Ethereum private key using Foundry's `cast` tool: ``` # Generate a new wallet with a 24-word mnemonic cast wallet new-mnemonic --words 24 # This outputs a mnemonic phrase, a derived address, and private key # Save these securely - you'll need the private key for PROVER_PUBLISHER_PRIVATE_KEY # and the address for PROVER_ID ``` **Important notes:** * Save both the private key and the derived address securely * The private key will be used for `PROVER_PUBLISHER_PRIVATE_KEY` * The derived Ethereum address will be used for `PROVER_ID` Account Funding Required The publisher account needs to be funded with ETH to post proofs to L1. Ensure the account holds sufficient ETH for gas costs during operation. tip If you don't have Foundry installed, follow the installation guide at [getfoundry.sh](https://getfoundry.sh/). ## Setup[​](#setup "Direct link to Setup") The prover components are distributed across multiple machines for better performance and resource utilization. This setup runs multiple prover agents on separate high-performance machines, isolates the broker for better job queue management, and separates network-facing components (prover node) from compute-intensive components (agents). ### Architecture[​](#architecture "Direct link to Architecture") * **Prover Node**: Runs on a machine with network access and L1 connectivity * **Prover Broker**: Can run on the same machine as the prover node or separately (must be accessible from prover agents) * **Prover Agents**: Run on separate high-performance machines (32+ cores each, scalable with `PROVER_AGENT_COUNT`) Network Requirements Prover agents must communicate with the prover broker over the network. Ensure that: * The broker machine's port 8080 is accessible from all agent machines * Firewall rules allow traffic between agents and broker * Network connectivity is stable and low-latency between components ### Prover Node and Broker Setup[​](#prover-node-and-broker-setup "Direct link to Prover Node and Broker Setup") On the machine that will run the prover node and broker: #### Step 1: Set Up Directory Structure[​](#step-1-set-up-directory-structure "Direct link to Step 1: Set Up Directory Structure") ``` mkdir -p aztec-prover-node/prover-node-data aztec-prover-node/prover-broker-data cd aztec-prover-node touch .env ``` #### Step 2: Configure Environment Variables[​](#step-2-configure-environment-variables "Direct link to Step 2: Configure Environment Variables") Add to your `.env` file: ``` # Prover Node Configuration DATA_DIRECTORY=./prover-node-data P2P_IP=[your external IP address] P2P_PORT=40400 ETHEREUM_HOSTS=[your L1 execution endpoint] L1_CONSENSUS_HOST_URLS=[your L1 consensus endpoint] LOG_LEVEL=info PROVER_BROKER_HOST=http://prover-broker:8080 PROVER_PUBLISHER_PRIVATE_KEY=[your prover publisher private key, see prerequisites] AZTEC_PORT=8080 AZTEC_ADMIN_PORT=8880 # Prover Broker Configuration PROVER_BROKER_DATA_DIRECTORY=./prover-broker-data PROVER_BROKER_PORT=8080 ``` #### Step 3: Create Docker Compose File[​](#step-3-create-docker-compose-file "Direct link to Step 3: Create Docker Compose File") Create `docker-compose.yml`: ``` name: aztec-prover-node services: prover-node: image: aztecprotocol/aztec:4.3.1 entrypoint: >- node --no-warnings /usr/src/yarn-project/aztec/dest/bin/index.js start --prover-node --archiver --network mainnet depends_on: prover-broker: condition: service_started required: true environment: DATA_DIRECTORY: /var/lib/data ETHEREUM_HOSTS: ${ETHEREUM_HOSTS} L1_CONSENSUS_HOST_URLS: ${L1_CONSENSUS_HOST_URLS} LOG_LEVEL: ${LOG_LEVEL} PROVER_BROKER_HOST: ${PROVER_BROKER_HOST} PROVER_PUBLISHER_PRIVATE_KEY: ${PROVER_PUBLISHER_PRIVATE_KEY} P2P_IP: ${P2P_IP} P2P_PORT: ${P2P_PORT} AZTEC_PORT: ${AZTEC_PORT} AZTEC_ADMIN_PORT: ${AZTEC_ADMIN_PORT} ports: - ${AZTEC_PORT}:${AZTEC_PORT} - ${P2P_PORT}:${P2P_PORT} - ${P2P_PORT}:${P2P_PORT}/udp volumes: - ${DATA_DIRECTORY}:/var/lib/data restart: unless-stopped prover-broker: image: aztecprotocol/aztec:4.3.1 entrypoint: >- node --no-warnings /usr/src/yarn-project/aztec/dest/bin/index.js start --prover-broker --network mainnet environment: DATA_DIRECTORY: /var/lib/data ETHEREUM_HOSTS: ${ETHEREUM_HOSTS} P2P_IP: ${P2P_IP} LOG_LEVEL: ${LOG_LEVEL} ports: - ${PROVER_BROKER_PORT}:8080 volumes: - ${PROVER_BROKER_DATA_DIRECTORY}:/var/lib/data restart: unless-stopped ``` Security: Admin Port Not Exposed The admin port (8880) is intentionally **not exposed** to the host machine for security reasons. The admin API provides sensitive operations like configuration changes and database rollbacks that should never be accessible from outside the container. If you need to access admin endpoints, use `docker exec`: ``` docker exec -it prover-node curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"nodeAdmin_getConfig","params":[],"id":1}' ``` **Important:** The broker exposes port 8080 via `ports: - ${PROVER_BROKER_PORT}:8080`, making it accessible to external prover agents. Ensure this port is reachable from your agent machines. This configuration includes only essential settings. The `--network mainnet` flag applies network-specific defaults—see the [CLI reference](/operate/operators/reference/cli-reference.md) for all available configuration options. #### Step 4: Start Node and Broker[​](#step-4-start-node-and-broker "Direct link to Step 4: Start Node and Broker") ``` docker compose up -d ``` ### Prover Agent Setup[​](#prover-agent-setup "Direct link to Prover Agent Setup") On each machine that will run prover agents: #### Step 1: Set Up Directory[​](#step-1-set-up-directory "Direct link to Step 1: Set Up Directory") ``` mkdir aztec-prover-agent cd aztec-prover-agent touch .env ``` #### Step 2: Configure Environment Variables[​](#step-2-configure-environment-variables-1 "Direct link to Step 2: Configure Environment Variables") Add to your `.env` file: ``` PROVER_AGENT_COUNT=1 PROVER_AGENT_POLL_INTERVAL_MS=10000 PROVER_BROKER_HOST=http://[BROKER_MACHINE_IP]:8080 PROVER_ID=[address corresponding to PROVER_PUBLISHER_PRIVATE_KEY] ``` Replace `[BROKER_MACHINE_IP]` with the IP address of the machine running the prover broker. **Agent configuration tips:** * Set `PROVER_AGENT_COUNT` based on your machine's hardware (e.g., 64 cores/256 GB RAM = 2 agents, 96 cores/384 GB RAM = 3 agents, 128 cores/512 GB RAM = 4 agents) * Test connectivity before starting: `curl http://[BROKER_MACHINE_IP]:8080` * If the curl test fails, check your network configuration, firewall rules, and ensure the broker is running #### Step 3: Create Docker Compose File[​](#step-3-create-docker-compose-file-1 "Direct link to Step 3: Create Docker Compose File") Create `docker-compose.yml`: ``` name: aztec-prover-agent services: prover-agent: image: aztecprotocol/aztec:4.3.1 entrypoint: >- node --no-warnings /usr/src/yarn-project/aztec/dest/bin/index.js start --prover-agent --network mainnet environment: PROVER_AGENT_COUNT: ${PROVER_AGENT_COUNT} PROVER_AGENT_POLL_INTERVAL_MS: ${PROVER_AGENT_POLL_INTERVAL_MS} PROVER_BROKER_HOST: ${PROVER_BROKER_HOST} PROVER_ID: ${PROVER_ID} restart: unless-stopped ``` #### Step 4: Start Agent[​](#step-4-start-agent "Direct link to Step 4: Start Agent") ``` docker compose up -d ``` **Scaling your prover capacity:** * **Horizontal scaling**: Add more agent machines by repeating the agent setup on additional high-performance machines * **Vertical scaling**: Increase `PROVER_AGENT_COUNT` on existing machines (ensure adequate hardware) All agents, regardless of which machine they're on, must be able to communicate with the broker at the configured `PROVER_BROKER_HOST`. ## Verification[​](#verification "Direct link to Verification") Once your prover is running, verify all components are working correctly: ### Check Services[​](#check-services "Direct link to Check Services") On the prover node machine: ``` docker compose ps ``` On each agent machine: ``` docker compose ps ``` ### View Logs[​](#view-logs "Direct link to View Logs") On prover node machine: ``` # Prover node logs docker compose logs -f prover-node # Broker logs docker compose logs -f prover-broker ``` On agent machines: ``` # Agent logs docker compose logs -f prover-agent ``` ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Components not communicating[​](#components-not-communicating "Direct link to Components not communicating") **Issue**: Prover agent cannot connect to broker. **Solutions**: * Verify the broker IP address in `PROVER_BROKER_HOST` is correct * Ensure port 8080 on the broker machine is accessible from agent machines * Check firewall rules between machines allow traffic on port 8080 * Test connectivity from agent machine: `curl http://[BROKER_IP]:8080` * Verify the broker container is running: `docker compose ps` * Check if the broker port is exposed in docker-compose.yml * Review broker logs for connection attempts: `docker compose logs prover-broker` ### Insufficient resources[​](#insufficient-resources "Direct link to Insufficient resources") **Issue**: Prover agent crashes or performs poorly. **Solutions**: * Verify your hardware meets the minimum requirements (32 cores per agent, 128 GB RAM per agent) * Check system resource usage: `docker stats` * Reduce `PROVER_AGENT_COUNT` if running multiple agents per machine * Ensure no other resource-intensive processes are running * Monitor CPU and memory usage to verify resources match your configured agent count ### Agent not picking up jobs[​](#agent-not-picking-up-jobs "Direct link to Agent not picking up jobs") **Issue**: Agent logs show no job activity. **Solutions**: * Verify the broker is receiving jobs from the prover node * Check broker logs for errors * Confirm `PROVER_ID` matches your publisher address * Ensure agent can reach the broker endpoint * Test broker connectivity: `curl http://[BROKER_IP]:8080` ### Docker issues[​](#docker-issues "Direct link to Docker issues") **Issue**: Containers won't start or crash repeatedly. **Solutions**: * Ensure Docker and Docker Compose are up to date * Check disk space availability on all machines * Verify `.env` files are properly formatted * Review logs for specific error messages ### Common Issues[​](#common-issues "Direct link to Common Issues") See the [Operator FAQ](/operate/operators/operator-faq.md) for additional common issues and resolutions. ## Next Steps[​](#next-steps "Direct link to Next Steps") * Monitor your prover's performance and proof submission rate * Consider adding more prover agents for increased capacity (either by increasing `PROVER_AGENT_COUNT` or adding more machines) * Join the [Aztec Discord](https://discord.gg/aztec) for operator support * Review [governance participation](/operate/operators/sequencer-management/creating_and_voting_on_proposals.md) for participating in governance --- # Running a Sequencer ## Overview[​](#overview "Direct link to Overview") This guide covers sequencer lifecycle management on the Aztec network: keystore configuration, node setup, registration, ongoing operations, and eventual exit. Minimum Stake Requirement To participate as a sequencer on the Aztec network, you must stake a minimum of **200,000 AZTEC tokens**. Ensure you have sufficient tokens before proceeding with sequencer setup and registration. Sequencer nodes are critical infrastructure responsible for ordering transactions and producing blocks. They perform three key actions: 1. Assemble unprocessed transactions and propose the next block 2. Attest to correct execution of transactions in proposed blocks (when part of the sequencer committee) 3. Submit successfully attested blocks to L1 Before publication, blocks must be validated by a committee of sequencer nodes who re-execute public transactions and verify private function proofs. Committee members attest to validity by signing the block header. Once sufficient attestations are collected (two-thirds of the committee plus one), the block can be submitted to L1. ### Minimum Hardware Requirements[​](#minimum-hardware-requirements "Direct link to Minimum Hardware Requirements") * 8 core / 16 vCPU (released in 2015 or later) * 16 GB RAM * 1 TB NVMe SSD * 25 Mbps network connection These requirements are subject to change as the network throughput increases. **Before proceeding:** Ensure you've reviewed and completed the [prerequisites](/operate/operators/prerequisites.md). ## Keystore Explanation[​](#keystore-explanation "Direct link to Keystore Explanation") Sequencers require private keys to identify themselves as valid proposers and attesters. These keys are configured through a private keystore file. ### Private Keystore Structure[​](#private-keystore-structure "Direct link to Private Keystore Structure") The private keystore file (`keystore.json`) uses the following structure: ``` { "schemaVersion": 1, "validators": [ { "attester": { "eth": "ETH_PRIVATE_KEY", "bls": "BLS_PRIVATE_KEY" }, "publisher": ["PUBLISHER_PRIVATE_KEY"], // Optional: defaults to attester key "feeRecipient": "0x0000000000000000000000000000000000000000000000000000000000000000", // Not currently used, set to all zeros "coinbase": "ETH_ADDRESS" } ] } ``` info The attester field contains both Ethereum and BLS keys: * **ETH key**: Derives the address that serves as your sequencer's unique identifier in the protocol * **BLS key**: Used to sign proposals and attestations, as well as for staking operations ### Field Descriptions[​](#field-descriptions "Direct link to Field Descriptions") #### attester (required)[​](#attester-required "Direct link to attester (required)") **Your sequencer's identity.** Contains both Ethereum and BLS keys: * **Format**: Object with `eth` and `bls` fields * **eth**: Ethereum private key - the derived address serves as your sequencer's unique identifier in the protocol * **bls**: BLS private key - actually signs proposals and attestations, and is used for staking operations (validator registration and proof of possession) * **Purpose**: The ETH address identifies your sequencer, while the BLS key performs the cryptographic signing of consensus messages #### publisher (optional)[​](#publisher-optional "Direct link to publisher (optional)") Separate private key(s) for submitting BLS-signed messages to L1. The publisher just pays gas to post already-signed proposals and attestations. * **Format**: Array of Ethereum private keys * **Default**: Uses attester key if not specified * **Purpose**: Posts signed messages to L1 and pays for gas (doesn't participate in signing) * **Rule of thumb**: Ensure every publisher account maintains at least 0.1 ETH per attester account it serves. This balance allows the selected publisher to successfully post transactions when chosen. tip If you're using the attester ETH key for publishing (no separate publisher keys), you can omit the `publisher` field entirely from your keystore, but you will still need to fund the attester account according to the rule of thumb above. #### feeRecipient[​](#feerecipient "Direct link to feeRecipient") Aztec address that would receive L2 transaction fees. * **Format**: 32-byte Aztec address (64 hex characters) * **Current status**: Not currently used by the protocol - set to `0x0000000000000000000000000000000000000000000000000000000000000000` * **Purpose**: Reserved for future fee distribution mechanisms #### coinbase (optional)[​](#coinbase-optional "Direct link to coinbase (optional)") Ethereum address that receives all L1 block rewards and tx fees. * **Format**: Ethereum address * **Default**: Uses attester address if not specified ### Generating Keys[​](#generating-keys "Direct link to Generating Keys") Use the Aztec CLI's keystore utility to generate both your private and public keystores: ``` aztec validator-keys new \ --fee-recipient 0x0000000000000000000000000000000000000000000000000000000000000000 \ --staker-output \ --gse-address 0xa92ecFD0E70c9cd5E5cd76c50Af0F7Da93567a4f \ --l1-rpc-urls $ETH_RPC ``` **Relevant parameters:** * `--fee-recipient`: Set to all zeros (not currently used by the protocol) * `--staker-output`: Generate the public keystore for the staking dashboard * `--gse-address`: The GSE (Governance Staking Escrow) contract address (`0xa92ecFD0E70c9cd5E5cd76c50Af0F7Da93567a4f` for mainnet) * `--l1-rpc-urls`: Your Ethereum mainnet RPC endpoint * Set `ETH_RPC` environment variable, or replace `$ETH_RPC` with your Ethereum mainnet RPC URL (e.g., `https://mainnet.infura.io/v3/YOUR_API_KEY`) * `--count`: Number of validator identities to generate (default: 1) * Use this to generate multiple attester identities in a single keystore * Example: `--count 5` generates 5 validator identities with sequential addresses * All identities are derived from the same mnemonic using different derivation paths * Useful for operators running multiple sequencer identities or delegated staking providers * `--publisher-count` Number of publisher accounts per validator (default 0) **This command creates two JSON files:** 1. **Private keystore** (`~/.aztec/keystore/keyN.json`) - Contains your ETH and BLS private keys for running the node 2. **Public keystore** (`~/.aztec/keystore/keyN_staker_output.json`) - Contains only public information (public keys and proof of possession) for the staking dashboard Where `N` is an auto-incrementing number (e.g., `key1.json`, `key2.json`, etc.) **What gets generated:** * Automatically generates a mnemonic for key derivation (or provide your own with `--mnemonic`) * Creates an ETH key (for your sequencer identifier) and BLS key (for signing) * Computes BLS public keys (G1 and G2) and proof of possession * Outputs your attester address, publisher address and BLS public keys to the console **Example output (single validator):** ``` No mnemonic provided, generating new one... Using new mnemonic: word1 word2 word3 word4 word5 word6 word7 word8 word9 word10 word11 word12 Wrote validator keystore to /Users/aztec/.aztec/keystore/key1.json Wrote staker output for 1 validator(s) to /Users/aztec/.aztec/keystore/key1_staker_output.json acc1: attester: eth: 0xA55aB561877E479361BA033c4ff7B516006CF547 bls: 0xa931139040533679ff3990bfc4f40b63f50807815d77346e3c02919d71891dc1 ``` **Example output (multiple validators with `--count 3`):** ``` No mnemonic provided, generating new one... Using new mnemonic: word1 word2 word3 word4 word5 word6 word7 word8 word9 word10 word11 word12 Wrote validator keystore to /Users/aztec/.aztec/keystore/key1.json Wrote staker output for 3 validator(s) to /Users/aztec/.aztec/keystore/key1_staker_output.json acc1: attester: eth: 0xA55aB561877E479361BA033c4ff7B516006CF547 bls: 0xa931139040533679ff3990bfc4f40b63f50807815d77346e3c02919d71891dc1 acc2: attester: eth: 0xB66bC672988F590472CA144e5D8d9F82307DA658 bls: 0xb842240151644780ff4991cfd5f51c74f61918926e88457f4d13020e82902ed2 acc3: attester: eth: 0xC77cD783999F601583DB255f6E9e0F93418EB769 bls: 0xc953351262755891ff5aa2dfe6f62d85f72a29a37f99568f5e24131f93a13fe3 ``` **Critical: Save your mnemonic phrase!** * The mnemonic is the **only thing you must save** - it can regenerate all your keys, addresses, and keystores * Store it securely offline (not on the server running the node) **For convenience, note:** * **Attester address** (eth): Your sequencer's identifier (e.g., `0xA55aB...F547`) - useful for registration and monitoring * **File paths**: Where the keystores were saved All other information (BLS keys, public keys, addresses) can be re-derived from the mnemonic if needed. Provide Your Own Mnemonic For deterministic key generation or to recreate keys later, provide your own mnemonic: ``` aztec validator-keys new \ --fee-recipient 0x0000000000000000000000000000000000000000000000000000000000000000 \ --staker-output \ --gse-address 0xa92ecFD0E70c9cd5E5cd76c50Af0F7Da93567a4f \ --l1-rpc-urls $ETH_RPC \ --mnemonic "your twelve word mnemonic phrase here" ``` Generate Multiple Validator Identities To generate multiple validator identities (useful for delegated staking providers or operators running multiple sequencers): ``` # Generate 5 validator identities from the same mnemonic aztec validator-keys new \ --fee-recipient 0x0000000000000000000000000000000000000000000000000000000000000000 \ --staker-output \ --gse-address 0xa92ecFD0E70c9cd5E5cd76c50Af0F7Da93567a4f \ --l1-rpc-urls $ETH_RPC \ --count 5 ``` Each identity gets a unique attester address derived from sequential derivation paths. All identities are included in: * The same private keystore file (`keyN.json`) * The same public keystore file (`keyN_staker_output.json`) For detailed instructions, advanced options, and complete examples, see the [Creating Sequencer Keystores guide](/operate/operators/keystore/creating_keystores.md). ## Setup with Docker Compose[​](#setup-with-docker-compose "Direct link to Setup with Docker Compose") ### Step 1: Set Up Directory Structure[​](#step-1-set-up-directory-structure "Direct link to Step 1: Set Up Directory Structure") Create the directory structure for sequencer data storage: ``` mkdir -p aztec-sequencer/keys aztec-sequencer/data cd aztec-sequencer touch .env ``` ### Step 2: Generate and Move Private Keystore to Docker Directory[​](#step-2-generate-and-move-private-keystore-to-docker-directory "Direct link to Step 2: Generate and Move Private Keystore to Docker Directory") If you haven't already generated your private and public keystores, do so now (see [Generating Keys](#generating-keys) above). Move the private keystore (not the public keystore) into the Docker directory: ``` # Move the private keystore to Docker directory (replace N with your key number) cp ~/.aztec/keystore/keyN.json aztec-sequencer/keys/keystore.json # Keep the public keystore for later use with the staking dashboard # It will be at ~/.aztec/keystore/keyN_staker_output.json ``` ### Step 3: Fund Your Publisher Account[​](#step-3-fund-your-publisher-account "Direct link to Step 3: Fund Your Publisher Account") Your sequencer needs ETH to pay for gas when submitting blocks to L1. Fund the account that will act as the publisher. **Determine which address to fund:** ``` # Get your attester address (this will be your publisher if no separate publisher is configured) jq -r '.[0].attester' ~/.aztec/keystore/keyN_staker_output.json # If you have a separate publisher configured: (Note this returns the publisher private key) jq -r '.validators[0].publisher[0]' aztec-sequencer/keys/keystore.json ``` **Funding requirements:** * **Rule of thumb**: Maintain at least **0.1 ETH per attester account** in each publisher account * Publisher accounts submit blocks to L1 and pay for gas fees * The system does not retry with another publisher if a transaction fails due to insufficient funds **Examples:** * 1 attester with 1 publisher (or using attester as publisher) → Maintain ≥ 0.1 ETH * 3 attesters with 1 publisher → Maintain ≥ 0.3 ETH in that publisher account * 3 attesters with 2 publishers → Maintain ≥ 0.15 ETH in each publisher account (0.3 ETH total) tip Set up monitoring or alerts to notify you when the publisher balance falls below the recommended threshold to prevent failed block publications. ### Step 4: Configure Environment Variables[​](#step-4-configure-environment-variables "Direct link to Step 4: Configure Environment Variables") Add the following to your `.env` file: ``` DATA_DIRECTORY=./data KEY_STORE_DIRECTORY=./keys LOG_LEVEL=info ETHEREUM_HOSTS=[your Ethereum mainnet execution endpoint, or a comma separated list if you have multiple] L1_CONSENSUS_HOST_URLS=[your Ethereum mainnet consensus endpoint, or a comma separated list if you have multiple] ETHEREUM_DEBUG_HOSTS=[your trace capable L1 execution endpoint] P2P_IP=[your external IP address] P2P_PORT=40400 AZTEC_PORT=8080 AZTEC_ADMIN_PORT=8880 ``` tip Find your public IP address with: `curl ipv4.icanhazip.com` Nethermind Users (versions before v1.36.0) If you are using Nethermind as your L1 execution client with a version before v1.36.0, you must add the following environment variable: ``` # Required for Nethermind versions before v1.36.0 L1_FIXED_PRIORITY_FEE_PER_GAS=1 ``` This issue was fixed in Nethermind v1.36.0, so users on that version or later do not need this setting. warning In order to retrieve blocks posted to L1 via non-standard contract interactions, it is necessary to have access to an L1 rpc endpoint with 'trace' capability (either `trace_transaction` or `debug_traceTransaction`). The variable `ETHEREUM_DEBUG_HOSTS` is used to provide these url/s to the node. If not provided, the value of this will default to that set in `ETHEREUM_HOSTS`. The node will validate whether it is able to execute a trace call on the provided url/s, if not, it looks to the value set in `ETHEREUM_ALLOW_NO_DEBUG_HOSTS` to determine whether this should prevent the node from starting. By default `ETHEREUM_ALLOW_NO_DEBUG_HOSTS` is `true`, allowing the node to start. Any url provided in `ETHEREUM_DEBUG_HOSTS` will only be used in the case of having to execute a trace, it won't be used in regular L1 interactions. Note - if the node does not have access to an rpc url that is capable of trace calls and it encounters a block posted via a transaction using non-standard contract interactions, it may become stuck and unable to progress the chain. ### Step 5: Create Docker Compose File[​](#step-5-create-docker-compose-file "Direct link to Step 5: Create Docker Compose File") Create a `docker-compose.yml` file in your `aztec-sequencer` directory: ``` services: aztec-sequencer: image: "aztecprotocol/aztec:4.3.1" container_name: "aztec-sequencer" ports: - ${AZTEC_PORT}:${AZTEC_PORT} - ${P2P_PORT}:${P2P_PORT} - ${P2P_PORT}:${P2P_PORT}/udp volumes: - ${DATA_DIRECTORY}:/var/lib/data - ${KEY_STORE_DIRECTORY}:/var/lib/keystore environment: KEY_STORE_DIRECTORY: /var/lib/keystore DATA_DIRECTORY: /var/lib/data LOG_LEVEL: ${LOG_LEVEL} ETHEREUM_HOSTS: ${ETHEREUM_HOSTS} L1_CONSENSUS_HOST_URLS: ${L1_CONSENSUS_HOST_URLS} ETHEREUM_DEBUG_HOSTS: ${ETHEREUM_DEBUG_HOSTS} P2P_IP: ${P2P_IP} P2P_PORT: ${P2P_PORT} AZTEC_PORT: ${AZTEC_PORT} AZTEC_ADMIN_PORT: ${AZTEC_ADMIN_PORT} entrypoint: >- node --no-warnings /usr/src/yarn-project/aztec/dest/bin/index.js start --node --archiver --sequencer --network mainnet networks: - aztec restart: always networks: aztec: name: aztec ``` Security: Admin Port Not Exposed The admin port (8880) is intentionally **not exposed** to the host machine for security reasons. The admin API provides sensitive operations like configuration changes and database rollbacks that should never be accessible from outside the container. If you need to access admin endpoints, use `docker exec`: ``` docker exec -it aztec-sequencer curl -X POST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"nodeAdmin_getConfig","params":[],"id":1}' ``` This configuration includes only essential settings. The `--network mainnet` flag applies network-specific defaults—see the [CLI reference](/operate/operators/reference/cli-reference.md) for all available configuration options. ### Step 6: Start the Sequencer[​](#step-6-start-the-sequencer "Direct link to Step 6: Start the Sequencer") Start the sequencer: ``` docker compose up -d ``` ## Verification[​](#verification "Direct link to Verification") Once your sequencer is running, verify it's working correctly: ### Check Sync Status[​](#check-sync-status "Direct link to Check Sync Status") Check the current sync status (this may take a few minutes): ``` curl -s -X POST -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"node_getL2Tips","params":[],"id":67}' \ http://localhost:8080 | jq -r ".result.proven.number" ``` Compare the output with block explorers (see [Networks page](/networks.md) for explorer links). ### Check Node Status[​](#check-node-status "Direct link to Check Node Status") ``` curl http://localhost:8080/status ``` ### View Logs[​](#view-logs "Direct link to View Logs") ``` docker compose logs -f --tail 100 aztec-sequencer ``` ## Next Steps: Registering Your Sequencer[​](#next-steps-registering-your-sequencer "Direct link to Next Steps: Registering Your Sequencer") Now that your sequencer node is set up and running, you need to register it with the network. There are two ways to participate as a sequencer: ### Option 1: Self-Staking via Staking Dashboard[​](#option-1-self-staking-via-staking-dashboard "Direct link to Option 1: Self-Staking via Staking Dashboard") Register your sequencer and provide your own stake through the staking dashboard. This is the most common approach for individual operators. **→ [Register Your Sequencer (Self-Staking)](/operate/operators/setup/registering_sequencer.md)** You'll use the **public keystore** file (`keyN_staker_output.json`) that was generated when you created your keys. ### Option 2: Running with Delegated Stake[​](#option-2-running-with-delegated-stake "Direct link to Option 2: Running with Delegated Stake") Operate sequencers backed by tokens from delegators. This non-custodial system allows you to run sequencer infrastructure while delegators provide the economic backing. **→ [Run as a Staking Provider](/operate/operators/setup/become_a_staking_provider.md)** As a provider, you'll register with the Staking Registry and manage a queue of sequencer identities that activate when delegators stake to you. Which Option Should I Choose? * **Self-staking**: You have tokens and want to run your own sequencer * **Delegated staking**: You want to operate sequencer infrastructure and earn commission from delegators' stake Both options use the same node setup from this guide. ## Monitoring Sequencer Status[​](#monitoring-sequencer-status "Direct link to Monitoring Sequencer Status") You can query the status of any sequencer (attester) using the Rollup and GSE (Governance Staking Escrow) contracts on L1. ### Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Foundry installed (`cast` command) * Ethereum RPC endpoint * Registry contract address for your network ### Get Contract Addresses[​](#get-contract-addresses "Direct link to Get Contract Addresses") First, get the canonical Rollup contract address from the Registry: ``` # Get the canonical rollup address cast call [REGISTRY_CONTRACT_ADDRESS] "getCanonicalRollup()" --rpc-url [YOUR_RPC_URL] ``` Then get the GSE contract address from the Rollup: ``` # Get the GSE contract address cast call [ROLLUP_ADDRESS] "getGSE()" --rpc-url [YOUR_RPC_URL] ``` ### Query Sequencer Status[​](#query-sequencer-status "Direct link to Query Sequencer Status") Check the complete status and information for a specific sequencer: ``` # Get full attester view (status, balance, exit info, config) cast call [ROLLUP_ADDRESS] "getAttesterView(address)" [ATTESTER_ADDRESS] --rpc-url [YOUR_RPC_URL] ``` This returns an `AttesterView` struct containing: 1. **status** - The sequencer's current status (see Status Codes below) 2. **effectiveBalance** - The sequencer's effective stake balance 3. **exit** - Exit information (if the sequencer is exiting) 4. **config** - Attester configuration (withdrawer address and public key) #### Status Codes[​](#status-codes "Direct link to Status Codes") | Status | Name | Meaning | | ------ | ---------- | ------------------------------------------------------------------------------------------------------------------------------- | | 0 | NONE | The sequencer does not exist in the sequencer set | | 1 | VALIDATING | The sequencer is currently active and participating in consensus | | 2 | ZOMBIE | The sequencer is not active (balance fell below ejection threshold, possibly due to slashing) but still has funds in the system | | 3 | EXITING | The sequencer has initiated withdrawal and is in the exit delay period | ### Performance Monitoring[​](#performance-monitoring "Direct link to Performance Monitoring") Track your sequencer's performance by monitoring: * **Effective balance** - Should remain above the ejection threshold * **Status** - Should be VALIDATING for active participation * **Attestation rate** - How many attestations you've successfully submitted * **Proposal success rate** - How many of your proposed blocks were accepted * **Network participation metrics** - Overall participation in network consensus ## Exiting a Sequencer[​](#exiting-a-sequencer "Direct link to Exiting a Sequencer") warning Information about the exit process will be added when the mechanism is finalized. Check the [Aztec Discord](https://discord.gg/aztec) for the latest information on exiting the sequencer set. ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Port forwarding not working[​](#port-forwarding-not-working "Direct link to Port forwarding not working") **Issue**: Your node cannot connect to peers. **Solutions**: * Verify your external IP address matches the `P2P_IP` setting * Check firewall rules on your router and local machine * Test connectivity using: `nc -zv [your-ip] 40400` ### Sequencer not syncing[​](#sequencer-not-syncing "Direct link to Sequencer not syncing") **Issue**: Your node is not synchronizing with the network. **Solutions**: * Check L1 endpoint connectivity * Verify both execution and consensus clients are fully synced * Review logs for specific error messages * Ensure L1 endpoints support high throughput ### Private keystore issues[​](#private-keystore-issues "Direct link to Private keystore issues") **Issue**: Private keystore not loading or errors about invalid keys. **Solutions**: * Ensure `keystore.json` is properly formatted * Verify private keys are valid Ethereum private keys * Check file permissions on the keys directory ### Docker issues[​](#docker-issues "Direct link to Docker issues") **Issue**: Container won't start or crashes. **Solutions**: * Ensure Docker and Docker Compose are up to date * Check disk space availability * Verify the `.env` file is properly formatted * Review container logs: `docker compose logs aztec-sequencer` ### Common Issues[​](#common-issues "Direct link to Common Issues") See the [Operator FAQ](/operate/operators/operator-faq.md) for additional common issues and resolutions. ## Additional Resources[​](#additional-resources "Direct link to Additional Resources") After setting up and registering your sequencer: * **[Register Your Sequencer](/operate/operators/setup/registering_sequencer.md)** - Complete registration via staking dashboard * **[Monitor Sequencer Status](#monitoring-sequencer-status)** - Track performance and attestation rate * **[Operator FAQ](/operate/operators/operator-faq.md)** - Common issues and resolutions * **[Governance Participation](/operate/operators/sequencer-management/creating_and_voting_on_proposals.md)** - Participate in governance * **[High Availability Setup](/operate/operators/setup/high_availability_sequencers.md)** - Run your sequencer across multiple nodes for redundancy * **[Advanced Keystore Patterns](/operate/operators/keystore/advanced-patterns.md)** - Manage multiple sequencer identities **Community support:** * Join the [Aztec Discord](https://discord.gg/aztec) for operator support and network updates --- # Using and uploading snapshots ## Overview[​](#overview "Direct link to Overview") All nodes on the Aztec network must download and synchronize the blockchain state before they can operate. This guide covers different sync modes, including how to use snapshots for faster synchronization and how to create your own snapshots. Automatic Configuration When using `--network [NETWORK_NAME]`, snapshot URLs are automatically configured for you. Most users don't need to manually set snapshot sources. ## Understanding sync modes[​](#understanding-sync-modes "Direct link to Understanding sync modes") Nodes can synchronize state in two ways: 1. **L1 sync**: Queries the rollup and data availability layer for historical state directly from Layer 1 2. **Snapshot sync**: Downloads pre-built state snapshots from a storage location for faster synchronization Since Aztec uses blobs, syncing from L1 requires an archive node that stores complete blob history from Aztec's deployment. Snapshot sync is significantly faster, doesn't require archive nodes, and reduces load on L1 infrastructure, making it the recommended approach for most deployments. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before proceeding, you should: * Have the Aztec node software installed * Understand basic node operation * For uploading snapshots: Have access to cloud storage (Google Cloud Storage, Amazon S3, or Cloudflare R2) with appropriate permissions ## Using snapshots to sync your node[​](#using-snapshots-to-sync-your-node "Direct link to Using snapshots to sync your node") ### Configuring sync mode[​](#configuring-sync-mode "Direct link to Configuring sync mode") Control how your node synchronizes using the `SYNC_MODE` environment variable in your `.env` file: ``` aztec start --node --sync-mode [MODE] SYNC_MODE=[MODE] ``` Available sync modes: * **`snapshot`**: Downloads and uses a snapshot only if no local data exists (default behavior) * **`force-snapshot`**: Downloads and uses a snapshot even if local data exists, overwriting it * **`l1`**: Syncs directly from Layer 1 without using snapshots ### Setting the snapshot source[​](#setting-the-snapshot-source "Direct link to Setting the snapshot source") By default, nodes use Aztec's official snapshot storage. To specify a custom snapshot location, add the `SNAPSHOTS_URL` environment variable to your `.env` file: ``` SYNC_MODE=snapshot SNAPSHOTS_URL=[BASE_URL] ``` The node searches for the snapshot index at: ``` [BASE_URL]/aztec-[L1_CHAIN_ID]-[VERSION]-[ROLLUP_ADDRESS]/index.json ``` **Supported storage backends**: * **Google Cloud Storage** - `gs://bucket-name/path/` * **Amazon S3** - `s3://bucket-name/path/` * **Cloudflare R2** - `s3://bucket-name/path/?endpoint=https://[ACCOUNT_ID].r2.cloudflarestorage.com` * **HTTP/HTTPS** - `https://host/path` * **Local filesystem** - `file:///absolute/path` **Default snapshot locations by network**: * **Mainnet**: `https://aztec-labs-snapshots.com/mainnet/` * **Testnet**: `https://aztec-labs-snapshots.com/testnet/` * **Staging networks**: Configured via network metadata ### Using custom snapshot sources[​](#using-custom-snapshot-sources "Direct link to Using custom snapshot sources") You can configure your node to use custom snapshot sources for various use cases. Add the following to your `.env` file: **Google Cloud Storage:** ``` SYNC_MODE=force-snapshot SNAPSHOTS_URL=gs://my-snapshots/ ``` **Cloudflare R2:** ``` SYNC_MODE=snapshot SNAPSHOTS_URL=s3://my-bucket/snapshots/?endpoint=https://[ACCOUNT_ID].r2.cloudflarestorage.com ``` Replace `[ACCOUNT_ID]` with your Cloudflare account ID. **HTTP/HTTPS mirror:** ``` SYNC_MODE=snapshot SNAPSHOTS_URL=https://my-mirror.example.com/snapshots/ ``` Then add the environment variables to your `docker-compose.yml`: ``` environment: # ... other environment variables SYNC_MODE: ${SYNC_MODE} SNAPSHOTS_URL: ${SNAPSHOTS_URL} ``` ## Creating and uploading snapshots[​](#creating-and-uploading-snapshots "Direct link to Creating and uploading snapshots") You can create snapshots of your node's state for backup purposes or to share with other nodes. This is done by calling the `nodeAdmin_startSnapshotUpload` method on the node admin API. ### How snapshot upload works[​](#how-snapshot-upload-works "Direct link to How snapshot upload works") When triggered, the upload process: 1. Pauses node syncing temporarily 2. Creates a backup of the archiver and world-state databases 3. Uploads the backup to the specified storage location 4. Resumes normal operation ### Uploading a snapshot[​](#uploading-a-snapshot "Direct link to Uploading a snapshot") Use the node admin API to trigger a snapshot upload. You can upload to Google Cloud Storage, Amazon S3, or Cloudflare R2 by specifying the appropriate storage URI. **Example command**: **Upload to Google Cloud Storage:** ``` docker exec -it aztec-node curl -XPOST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{ "method": "nodeAdmin_startSnapshotUpload", "params": ["gs://your-bucket/snapshots/"], "id": 1, "jsonrpc": "2.0" }' ``` **Upload to Amazon S3:** ``` docker exec -it aztec-node curl -XPOST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{ "method": "nodeAdmin_startSnapshotUpload", "params": ["s3://your-bucket/snapshots/"], "id": 1, "jsonrpc": "2.0" }' ``` **Upload to Cloudflare R2:** ``` docker exec -it aztec-node curl -XPOST http://localhost:8880 \ -H 'Content-Type: application/json' \ -d '{ "method": "nodeAdmin_startSnapshotUpload", "params": ["s3://your-bucket/snapshots/?endpoint=https://[ACCOUNT_ID].r2.cloudflarestorage.com"], "id": 1, "jsonrpc": "2.0" }' ``` Replace `aztec-node` with your container name and `[ACCOUNT_ID]` with your Cloudflare account ID. **Note**: Ensure your storage credentials are configured before uploading: * **Google Cloud Storage**: Set up [Application Default Credentials](https://cloud.google.com/docs/authentication/application-default-credentials) * **Amazon S3 / Cloudflare R2**: Set environment variables `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` ### Scheduling regular snapshots[​](#scheduling-regular-snapshots "Direct link to Scheduling regular snapshots") For continuous backup, schedule the upload command to run at regular intervals using cron or a similar scheduler. The frequency depends on how current you need your snapshots to be. Once uploaded, other nodes can download these snapshots by configuring their `--snapshots-url` to point to your storage location. ## Verification[​](#verification "Direct link to Verification") To verify your sync configuration is working: ### For snapshot downloads[​](#for-snapshot-downloads "Direct link to For snapshot downloads") 1. **Check startup logs**: Look for messages indicating snapshot download progress 2. **Monitor sync time**: Snapshot sync should be significantly faster than L1 sync 3. **Verify state completeness**: Confirm your node has the expected block height after sync 4. **Check data directories**: Ensure the archiver and world-state databases are populated ### For snapshot uploads[​](#for-snapshot-uploads "Direct link to For snapshot uploads") 1. **Check API response**: The upload command should return a success response 2. **Monitor logs**: Watch for upload progress messages in the node logs 3. **Verify storage**: Check your storage bucket to confirm the snapshot files exist 4. **Validate index file**: Ensure the `index.json` file is created at the expected path 5. **Test download**: Try downloading the snapshot with another node to confirm it works ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Snapshot download fails[​](#snapshot-download-fails "Direct link to Snapshot download fails") **Issue**: Node cannot download snapshot from the specified URL. **Solutions**: * Verify the `--snapshots-url` is correct and accessible * Check network connectivity to the storage location * Confirm the snapshot index file exists at the expected path * Review node logs for specific error messages * Try using Aztec's default snapshot URL to isolate custom URL issues ### Snapshot upload fails[​](#snapshot-upload-fails "Direct link to Snapshot upload fails") **Issue**: The `nodeAdmin_startSnapshotUpload` command returns an error. **Solutions**: * Verify storage credentials are properly configured (Google Cloud, AWS, or Cloudflare R2) * Check that the specified bucket exists and you have write permissions * Confirm sufficient disk space is available for creating the backup * Review node logs for detailed error messages * For S3/R2: Ensure environment variables `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` are set ### Storage space issues[​](#storage-space-issues "Direct link to Storage space issues") **Issue**: Running out of disk space during sync or snapshot creation. **Solutions**: * Ensure sufficient disk space (at least 2x the expected database size for snapshots) * Clean up old snapshots or data if running recurring uploads * Monitor disk usage and set up alerts * Consider using a larger volume or adding storage ## Best practices[​](#best-practices "Direct link to Best practices") * **Use snapshot sync for production**: Snapshot sync is significantly faster and more efficient than L1 sync * **Choose the right storage backend**: * Google Cloud Storage for simplicity and GCP integration * Amazon S3 for AWS infrastructure integration * Cloudflare R2 for cost-effective public distribution (free egress) * **Schedule regular snapshots**: Create snapshots at regular intervals if running critical infrastructure * **Test snapshot restoration**: Periodically verify that your snapshots download and restore correctly * **Monitor storage costs**: Implement retention policies to manage cloud storage costs * **Keep snapshots current**: Older snapshots require more time to sync to the current state * **Use `force-snapshot` sparingly**: Only use when you need to reset to a known state, as it overwrites local data ## Next Steps[​](#next-steps "Direct link to Next Steps") * Learn about [running bootnodes](/operate/operators/setup/bootnode_operation.md) for improved peer discovery * Set up [monitoring](/operate/operators/monitoring.md) to track your node's sync progress * Check the [CLI reference](/operate/operators/reference/cli-reference.md) for additional sync-related options * Join the [Aztec Discord](https://discord.gg/aztec) for sync optimization tips --- # v4.x (Upgrade from Ignition) ## Overview[​](#overview "Direct link to Overview") **Migration difficulty**: High ## Breaking changes[​](#breaking-changes "Direct link to Breaking changes") ### Node.js upgraded to v24[​](#nodejs-upgraded-to-v24 "Direct link to Node.js upgraded to v24") Node.js minimum version changed from v22 to v24.12.0. ### Bot fee padding configuration renamed[​](#bot-fee-padding-configuration-renamed "Direct link to Bot fee padding configuration renamed") The bot configuration for fee padding has been renamed from "base fee" to "min fee". **v3.x:** ``` --bot.baseFeePadding ($BOT_BASE_FEE_PADDING) ``` **v4.0.0:** ``` --bot.minFeePadding ($BOT_MIN_FEE_PADDING) ``` **Migration**: Update your configuration to use the new flag name and environment variable. ### L2Tips API restructured with checkpoint information[​](#l2tips-api-restructured-with-checkpoint-information "Direct link to L2Tips API restructured with checkpoint information") The `getL2Tips()` RPC endpoint now returns a restructured response with additional checkpoint tracking. **v3.x response:** ``` { "latest": { "number": 100, "hash": "0x..." }, "proven": { "number": 98, "hash": "0x..." }, "finalized": { "number": 95, "hash": "0x..." } } ``` **v4.0.0 response:** ``` { "proposed": { "number": 100, "hash": "0x..." }, "checkpointed": { "block": { "number": 99, "hash": "0x..." }, "checkpoint": { "number": 10, "hash": "0x..." } }, "proven": { "block": { "number": 98, "hash": "0x..." }, "checkpoint": { "number": 9, "hash": "0x..." } }, "finalized": { "block": { "number": 95, "hash": "0x..." }, "checkpoint": { "number": 8, "hash": "0x..." } } } ``` **Migration**: * Replace `tips.latest` with `tips.proposed` * For `checkpointed`, `proven`, and `finalized` tips, access block info via `.block` (e.g., `tips.proven.block.number`) ### Block gas limits reworked[​](#block-gas-limits-reworked "Direct link to Block gas limits reworked") The byte-based block size limit has been removed and replaced with field-based blob limits and automatic gas budget computation from L1 rollup limits. **Removed:** ``` --maxBlockSizeInBytes ($SEQ_MAX_BLOCK_SIZE_IN_BYTES) ``` **Changed to optional (now auto-computed from L1 if not set):** ``` --maxL2BlockGas ($SEQ_MAX_L2_BLOCK_GAS) --maxDABlockGas ($SEQ_MAX_DA_BLOCK_GAS) ``` **New (proposer):** ``` --perBlockAllocationMultiplier ($SEQ_PER_BLOCK_ALLOCATION_MULTIPLIER) --maxTxsPerCheckpoint ($SEQ_MAX_TX_PER_CHECKPOINT) ``` **New (validator):** ``` --validateMaxL2BlockGas ($VALIDATOR_MAX_L2_BLOCK_GAS) --validateMaxDABlockGas ($VALIDATOR_MAX_DA_BLOCK_GAS) --validateMaxTxsPerBlock ($VALIDATOR_MAX_TX_PER_BLOCK) --validateMaxTxsPerCheckpoint ($VALIDATOR_MAX_TX_PER_CHECKPOINT) ``` **Migration**: Remove `SEQ_MAX_BLOCK_SIZE_IN_BYTES` from your configuration. Per-block L2 and DA gas budgets are now derived automatically as `(checkpointLimit / maxBlocks) * multiplier`, where the multiplier defaults to 2. You can still override `SEQ_MAX_L2_BLOCK_GAS` and `SEQ_MAX_DA_BLOCK_GAS` explicitly, but they will be capped at the checkpoint-level limits. Validators can now set independent per-block and per-checkpoint limits via the `VALIDATOR_` env vars; when not set, only checkpoint-level protocol limits are enforced. ### Setup phase allow list requires function selectors[​](#setup-phase-allow-list-requires-function-selectors "Direct link to Setup phase allow list requires function selectors") The transaction setup phase allow list now enforces function selectors, restricting which specific functions can run during setup on whitelisted contracts. Previously, any public function on a whitelisted contract or class was permitted. The semantics of the environment variable `TX_PUBLIC_SETUP_ALLOWLIST` have changed: **v3.x:** ``` --txPublicSetupAllowList ($TX_PUBLIC_SETUP_ALLOWLIST) ``` The variable fully **replaced** the hardcoded defaults. Format allowed entries without selectors: `I:address`, `C:classId`. **v4.0.0:** ``` --txPublicSetupAllowListExtend ($TX_PUBLIC_SETUP_ALLOWLIST) ``` The variable now **extends** the hardcoded defaults (which are always present). Selectors are now mandatory. An optional flags segment can be appended for additional validation: ``` I:address:selector[:flags] C:classId:selector[:flags] ``` Where `flags` is a `+`-separated list of: * `os` — `onlySelf`: only allow calls where msg\_sender == contract address * `rn` — `rejectNullMsgSender`: reject calls with a null msg\_sender * `cl=N` — `calldataLength`: enforce exact calldata length of N fields Example: `C:0xabc:0x1234:os+cl=4` **Migration**: If you were using `TX_PUBLIC_SETUP_ALLOWLIST`, ensure all entries include function selectors. Note the variable now adds to defaults rather than replacing them. If you were not setting this variable, no action is needed — the hardcoded defaults now include the correct selectors automatically. ### Token removed from default setup allowlist[​](#token-removed-from-default-setup-allowlist "Direct link to Token removed from default setup allowlist") Token class-based entries (`_increase_public_balance` and `transfer_in_public`) have been removed from the default public setup allowlist. FPC-based fee payments using custom tokens no longer work out of the box. This change was made because Token class IDs change with aztec-nr releases, making the allowlist impossible to keep up to date with new library releases. In addition, `transfer_in_public` requires complex additional logic to be built into the node to prevent mass transaction invalidation attacks. **FPC-based fee payment with custom tokens won't work on mainnet alpha**. **Migration**: Node operators who need FPC support must manually add Token entries via `TX_PUBLIC_SETUP_ALLOWLIST`. Example: ``` TX_PUBLIC_SETUP_ALLOWLIST="C:::os+cl=3,C:::cl=5" ``` Replace `` with the deployed Token contract class ID and ``/`` with the respective function selectors. Keep in mind that this will only work on local network setups, since even if you as an operator add these entries, other nodes will not have them and will not pick up these transactions. ### Sequencer environment variable renames[​](#sequencer-environment-variable-renames "Direct link to Sequencer environment variable renames") Several sequencer environment variables have been renamed: | Old variable | New variable | | ---------------------------------------- | --------------------------------------------------------------------- | | `SEQ_TX_POLLING_INTERVAL_MS` | `SEQ_POLLING_INTERVAL_MS` | | `SEQ_MAX_L1_TX_INCLUSION_TIME_INTO_SLOT` | `SEQ_L1_PUBLISHING_TIME_ALLOWANCE_IN_SLOT` | | `SEQ_MAX_TX_PER_BLOCK` | `SEQ_MAX_TX_PER_CHECKPOINT` | | `SEQ_MAX_BLOCK_SIZE_IN_BYTES` | Removed (see [Block gas limits reworked](#block-gas-limits-reworked)) | **Migration**: Search your configuration for the old variable names and replace them. The node will not recognize the old names. ### Double signing slashing[​](#double-signing-slashing "Direct link to Double signing slashing") New slashable offenses have been introduced for duplicate proposals and duplicate attestations. Penalty amounts are currently set to 0, but the detection infrastructure is active. If you run redundant sequencer nodes, you **must** enable high-availability signing with PostgreSQL to prevent accidental double signing: ``` VALIDATOR_HA_SIGNING_ENABLED=true VALIDATOR_HA_DATABASE_URL=postgresql://:@:/ VALIDATOR_HA_NODE_ID= ``` Run the database migration before starting your nodes: ``` aztec migrate-ha-db up --database-url ``` **Migration**: If you run a single node, no action is required. If you run redundant nodes for high availability, configure HA signing immediately. See the [High Availability Sequencers](/operate/operators/setup/high_availability_sequencers.md) guide for details. ### Blob-only data publication[​](#blob-only-data-publication "Direct link to Blob-only data publication") Transaction data is now published entirely via EIP-4844 blobs. The calldata fallback has been removed. Your consensus client (e.g., Lighthouse, Prysm) must run as a **supernode** or **semi-supernode** to make blobs available for retrieval. Standard pruning configurations will not retain blobs long enough. You should also configure blob file stores for redundancy: ``` BLOB_FILE_STORE_URLS= BLOB_FILE_STORE_UPLOAD_URL= BLOB_ARCHIVE_API_URL= ``` **Migration**: Ensure your consensus client is configured as a supernode. If you previously relied on calldata for data availability, switch to blob-based retrieval. See the [Blob Storage](/operate/operators/setup/blob_storage.md) guide for configuration details. ### Withdrawal delay increase[​](#withdrawal-delay-increase "Direct link to Withdrawal delay increase") The governance execution delay has increased from 7 days to 30 days. This extends the time required for staker withdrawals from approximately 15 days to approximately 38 days. **Migration**: No configuration changes needed. Be aware that withdrawal processing will take longer after the upgrade. ### Prover architecture change[​](#prover-architecture-change "Direct link to Prover architecture change") The prover now runs as a node subsystem rather than a separate standalone process. Start it alongside your node using the `--prover-node` flag: ``` aztec start --node --prover-node ``` **Migration**: If you were running the prover as a separate process, update your deployment to run it as part of the node with `--prover-node`. ## Removed features[​](#removed-features "Direct link to Removed features") ## New features[​](#new-features "Direct link to New features") ### Initial ETH per fee asset configuration[​](#initial-eth-per-fee-asset-configuration "Direct link to Initial ETH per fee asset configuration") A new environment variable `AZTEC_INITIAL_ETH_PER_FEE_ASSET` has been added to configure the initial exchange rate between ETH and the fee asset (AZTEC) at contract deployment. This value uses 1e12 precision. **Default**: `10000000` (0.00001 ETH per AZTEC) **Configuration:** ``` --initialEthPerFeeAsset ($AZTEC_INITIAL_ETH_PER_FEE_ASSET) ``` This replaces the previous hardcoded default and allows network operators to set the starting price point for the fee asset. ### `reloadKeystore` admin RPC endpoint[​](#reloadkeystore-admin-rpc-endpoint "Direct link to reloadkeystore-admin-rpc-endpoint") Node operators can now update validator attester keys, coinbase, and fee recipient without restarting the node by calling the new `reloadKeystore` admin RPC endpoint. What is updated on reload: * Validator attester keys (add, remove, or replace) * Coinbase and fee recipient per validator * Publisher-to-validator mapping What is NOT updated (requires restart): * L1 publisher signers * Prover keys * HA signer connections New validators must use a publisher key already initialized at startup. Reload is rejected with a clear error if validation fails. ### Admin API key authentication[​](#admin-api-key-authentication "Direct link to Admin API key authentication") The admin JSON-RPC endpoint now supports auto-generated API key authentication. **Behavior:** * A cryptographically secure API key is auto-generated at first startup and displayed once via stdout * Only the SHA-256 hash is persisted to `/admin/api_key_hash` * The key is reused across restarts when `--data-directory` is set * Supports both `x-api-key` and `Authorization: Bearer ` headers * Health check endpoint (`GET /status`) is excluded from auth (for k8s probes) **Configuration:** ``` --admin-api-key-hash ($AZTEC_ADMIN_API_KEY_HASH) # Use a pre-generated SHA-256 key hash --disable-admin-api-key ($AZTEC_DISABLE_ADMIN_API_KEY) # Disable auth entirely --reset-admin-api-key ($AZTEC_RESET_ADMIN_API_KEY) # Force key regeneration ``` **Helm charts**: Admin API key auth is disabled by default (`disableAdminApiKey: true`). Set to `false` in production values to enable. **Migration**: No action required — auth is opt-out. To enable, ensure `--disable-admin-api-key` is not set and note the key printed at startup. ### Transaction pool error codes for RPC callers[​](#transaction-pool-error-codes-for-rpc-callers "Direct link to Transaction pool error codes for RPC callers") Transaction submission via RPC now returns structured rejection codes when a transaction is rejected by the mempool: * `LOW_PRIORITY_FEE` — tx priority fee is too low * `INSUFFICIENT_FEE_PAYER_BALANCE` — fee payer doesn't have enough balance * `NULLIFIER_CONFLICT` — conflicting nullifier already in pool **Impact**: Improved developer experience — callers can now programmatically handle specific rejection reasons. ### RPC transaction replacement price bump[​](#rpc-transaction-replacement-price-bump "Direct link to RPC transaction replacement price bump") Transactions submitted via RPC that clash on nullifiers with existing pool transactions must now pay at least X% more in priority fee to replace them. The same bump applies when the pool is full and the incoming tx needs to evict the lowest-priority tx. P2P gossip behavior is unchanged. **Configuration:** ``` P2P_RPC_PRICE_BUMP_PERCENTAGE=10 # default: 10 (percent) ``` Set to `0` to disable the percentage-based bump (still requires strictly higher fee). ### Validator-specific block limits[​](#validator-specific-block-limits "Direct link to Validator-specific block limits") Validators can now enforce per-block and per-checkpoint limits independently from the sequencer (proposer) limits. This allows operators to accept proposals that exceed their own proposer settings, or to reject proposals that are too large even if the proposer's limits allow them. **Configuration:** ``` VALIDATOR_MAX_L2_BLOCK_GAS= # Max L2 gas per block for validation VALIDATOR_MAX_DA_BLOCK_GAS= # Max DA gas per block for validation VALIDATOR_MAX_TX_PER_BLOCK= # Max txs per block for validation VALIDATOR_MAX_TX_PER_CHECKPOINT= # Max txs per checkpoint for validation ``` When not set, no per-block limit is enforced for that dimension — only checkpoint-level protocol limits apply. These do not fall back to the `SEQ_` values. ### Setup allow list extendable via network config[​](#setup-allow-list-extendable-via-network-config "Direct link to Setup allow list extendable via network config") The setup phase allow list can now be extended via the network configuration JSON (`txPublicSetupAllowListExtend` field). This allows network operators to distribute additional allowed setup functions to all nodes without requiring code changes. The local environment variable takes precedence over the network-json value. ## Changed defaults[​](#changed-defaults "Direct link to Changed defaults") ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ## Next steps[​](#next-steps "Direct link to Next steps") * [How to Run a Sequencer Node](/operate/operators/setup/sequencer_management.md) - Updated setup instructions * [Advanced Keystore Usage](/operate/operators/keystore/creating_keystores.md) - Keystore configuration * [Ethereum RPC Calls Reference](/operate/operators/reference/ethereum_rpc_reference.md) - Infrastructure requirements * [Aztec Discord](https://discord.gg/aztec) - Upgrade support --- # Participate in the Aztec Network Welcome to the Participate section. Here you'll find educational content about how the Aztec network operates, the $AZTEC token, and how governance works. Alpha Network Aztec is currently in its **Alpha** phase, a live mainnet where bugs, including critical ones, are expected. Before using the network, read the [Alpha Network](/participate/alpha.md) page to understand current limitations, security expectations, and what to expect from rollup upgrades. ## What is Aztec?[​](#what-is-aztec "Direct link to What is Aztec?") This explainer covers what Aztec is in under 90 seconds (find more on the [video lessons](/developers/docs/resources/video_lessons.md) page): [What is Aztec: Explained in Under 90 Seconds](https://www.youtube-nocookie.com/embed/urcBvo2QJp0) ## Basics of Aztec[​](#basics-of-aztec "Direct link to Basics of Aztec") New to Aztec? Start here to understand the fundamentals: * [**Addresses**](/participate/basics/addresses.md) - How addresses work on Aztec * [**Wallets**](/participate/basics/wallets.md) - Available wallets and hardware wallet support * [**Fees**](/participate/basics/fees.md) - How fees and mana work * [**Transactions**](/participate/basics/transactions.md) - The transaction lifecycle and client-side proving * [**Blocks**](/participate/basics/blocks.md) - Blocks, epochs, and proving * [**Bridging**](/participate/basics/bridging.md) - How to move assets between Ethereum and Aztec ## $AZTEC Token[​](#aztec-token "Direct link to $AZTEC Token") Learn about the native token that powers the network: * [**Token Overview**](/participate/token.md) - Token utility and economics * [**Staking**](/participate/token/staking.md) - Stake tokens to help secure the network * [**Delegation**](/participate/token/delegation.md) - Delegate to professional operators * [**Voting**](/participate/token/voting.md) - Participate in governance decisions * [**Economics**](/participate/token/economics.md) - Reward distribution and incentives ## Governance[​](#governance "Direct link to Governance") Understand how the protocol evolves through decentralized governance: * [**Governance Overview**](/participate/governance.md) - How decisions are made * [**Proposal Lifecycle**](/participate/governance/proposal-lifecycle.md) - From idea to execution * [**Voting**](/participate/governance/voting.md) - How voting power works * [**GSE**](/participate/governance/gse.md) - Governance Staking Escrow explained * [**Upgrades**](/participate/governance/upgrades.md) - How network upgrades happen * [**L1 Contracts**](/participate/governance/contracts.md) - Smart contracts powering governance *** Ready to build? If you're a developer looking to build on Aztec, head over to the [Developer Guides](/developers/overview.md). Ready to operate? If you want to run network infrastructure, see the [Operator Guides](/operate/operators.md). --- # Alpha Network Alpha is the Aztec mainnet in its initial operational phase. It is live on Ethereum mainnet with real staking, governance, and user transactions. It is also early, unaudited software where bugs, including critical ones, are expected. This page is the central reference for understanding what Alpha is, what risks come with using it, and how the network will evolve. ## What Alpha is[​](#what-alpha-is "Direct link to What Alpha is") Alpha is the first production deployment of the Aztec rollup. Governance, staking, and block production are fully operational with real economic stakes. Sequencers must stake real tokens to participate, and governance proposals have real consequences. However, "production" does not mean "finished." Alpha exists to: * Battle-test the protocol under real conditions * Establish decentralized governance and validator sets * Identify bugs that only surface at scale or under adversarial conditions * Build toward a mature, stable mainnet Think of Alpha as a live stress test with real stakes. The protocol is functional, but it is not yet hardened. ## What to build on Alpha[​](#what-to-build-on-alpha "Direct link to What to build on Alpha") Alpha is an experimentation phase for developers as much as it is for the protocol. This is the moment to try things that were not possible before: private smart contracts, hybrid public/private applications, novel privacy-preserving primitives, and patterns that no other chain can support. Much like the early days of Ethereum, the applications built now will shape what Aztec becomes. Expect rough edges, breaking changes, and the need to redeploy after upgrades. In exchange, you get a first-mover opportunity to explore a new design space alongside the protocol itself. Build on [Testnet](/networks.md#testnet) first to iterate quickly, then deploy to Alpha when you are ready to validate against real network conditions. ## Known limitations and expected issues[​](#known-limitations-and-expected-issues "Direct link to Known limitations and expected issues") See the [Limitations](/developers/docs/resources/considerations/limitations.md) page for the full list of current developer-facing limitations. The sections below summarize the highest-impact issues for Alpha users. ### Proving system bugs[​](#proving-system-bugs "Direct link to Proving system bugs") The Aztec proving system is novel and complex. Bugs in proof generation, verification, and circuit constraints are expected during Alpha. Some circuits are still under-constrained, meaning that soundness is not fully guaranteed. In practice, this means: * Provers may occasionally produce invalid proofs * Proof verification on L1 may encounter edge cases * Block production may halt temporarily while issues are diagnosed and patched These are known risks of an early-stage ZK rollup, not unexpected failures. ### Unaudited software[​](#unaudited-software "Direct link to Unaudited software") No part of the Aztec stack has been fully audited. The protocol, smart contracts, client software, and cryptographic primitives are all under active development. Code is being iterated on daily and audits are ongoing. Published audit reports are available in the [Aztec audit reports repository](https://github.com/AztecProtocol/audit-reports). ### Privacy is not guaranteed[​](#privacy-is-not-guaranteed "Direct link to Privacy is not guaranteed") Some privacy features are still in development. Known leakage includes: * The number of side effects in a transaction is visible (private transactions can be fingerprinted based on their side effect count) * No privacy-preserving queries to third-party nodes exist yet * New privacy standards for smart contract design have not been established ### Circuit and transaction limits[​](#circuit-and-transaction-limits "Direct link to Circuit and transaction limits") ZK-SNARK circuits impose hard upper bounds on what a single transaction can do, including the number of state reads and writes, notes, nullifiers, logs, and messages. Deeply nested function calls can exceed per-transaction limits. See [Limitations](/developers/docs/resources/considerations/limitations.md#circuit-limitations) for current constants. ## State migration and rollup upgrades[​](#state-migration-and-rollup-upgrades "Direct link to State migration and rollup upgrades") As the protocol matures, it will undergo rollup upgrades through the [governance process](/participate/governance/upgrades.md). When a new rollup version is deployed: * A new rollup contract is added to the onchain Registry * The new rollup becomes canonical (receives block rewards) * The old rollup remains accessible for bridging assets in and out ### State migration[​](#state-migration "Direct link to State migration") Migrating application state from one rollup version to another is a hard problem for any ZK rollup, and Aztec is no exception. You should expect that: * **State does not carry over automatically.** Application data, deployed contracts, and notes on the current rollup will not be directly accessible on a new rollup version without explicit migration steps. * **Migration tooling is still being developed.** There are no mature, battle-tested tools for migrating L2 state across rollup upgrades yet. * **Applications will need to redeploy.** Developers should plan that contracts will need to be redeployed and state reconstructed (or omitted) after a rollup upgrade. * **User funds are not permanently locked, but they are not immune to protocol bugs.** The Registry model ensures that users can always bridge assets out of any historical rollup version. However, a proving system bug or other protocol flaw could still allow funds to be stolen before guardrails are in place, so "not locked" is not the same as "risk-free." If you are building on Alpha, design your application with rollup upgrades in mind. Avoid assumptions about state permanence at this stage. For details on how rollup upgrades work, see [Network Upgrades](/participate/governance/upgrades.md). ## Security disclosures[​](#security-disclosures "Direct link to Security disclosures") We expect bugs, including critical ones, to be discovered during Alpha. Aztec is preparing a bug bounty program and actively conducts internal and external reviews. ### Reporting vulnerabilities[​](#reporting-vulnerabilities "Direct link to Reporting vulnerabilities") If you discover a security vulnerability: 1. **Do not** open a public GitHub issue or pull request 2. Use [GitHub Private Vulnerability Reporting](https://github.com/AztecProtocol/aztec-packages/security/advisories/new) to submit details 3. You can also email (but submit full details through GitHub, not email) Mark reports as **CRITICAL** if you believe a vulnerability is actively being exploited or could result in loss of funds, key compromise, or broad user impact. For the full security policy, see [SECURITY.md](https://github.com/AztecProtocol/aztec-packages/blob/master/SECURITY.md). ### Non-security bugs[​](#non-security-bugs "Direct link to Non-security bugs") For bugs that are not security-sensitive (performance issues, feature requests, unexpected behavior), open a [GitHub Issue](https://github.com/AztecProtocol/aztec-packages/issues). Keeping non-security bugs public helps the community track progress and collaborate on fixes. ## Path to beta[​](#path-to-beta "Direct link to Path to beta") The transition from Alpha to Beta is defined by performance milestones, not a specific calendar date. The network will be considered Beta when it consistently meets the following targets: | Metric | Target | | -------------------------- | ---------- | | **User-perceived latency** | 12s median | | **Sustained throughput** | 10 TPS | | **Uptime** | 99.9% | These thresholds reflect the minimum bar for a network that application developers and users can rely on for production workloads. Until they are met, expect the instability and limitations described on this page. ## What this means for you[​](#what-this-means-for-you "Direct link to What this means for you") | If you are a... | Expect... | | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Validator / Sequencer** | Occasional downtime, software updates, and proving failures. Stay current with node releases. | | **Developer** | Breaking changes, API instability, and the need to redeploy after upgrades. Build on [Testnet](/networks.md#testnet) first, and design for impermanence. | | **Governance Participant** | An active role in shaping the protocol. Upgrades are real and consequential, so participate in proposals and voting. | | **User** | A functional but rough experience. Do not store meaningful secrets or rely on state permanence. | ## Next steps[​](#next-steps "Direct link to Next steps") * [Networks Overview](/networks.md): Technical details, RPC endpoints, and contract addresses * [Limitations](/developers/docs/resources/considerations/limitations.md): Full list of current developer-facing limitations * [Privacy Considerations](/developers/docs/resources/considerations/privacy_considerations.md): What leaks and what doesn't * [Network Upgrades](/participate/governance/upgrades.md): How rollup upgrades work through governance * [Security Policy](https://github.com/AztecProtocol/aztec-packages/blob/master/SECURITY.md): How to report vulnerabilities * [Operator Guides](/operate/operators.md): Running network infrastructure --- # Basics of Aztec This section covers the fundamental concepts you need to understand how Aztec works. Whether you're a user, token holder, or just curious about the technology, these pages explain the core ideas without requiring technical expertise. ## What You'll Learn[​](#what-youll-learn "Direct link to What You'll Learn") ### [Addresses](/participate/basics/addresses.md)[​](#addresses "Direct link to addresses") Every account on Aztec has an address, but they work differently from Ethereum addresses. Learn how Aztec's privacy-first design creates addresses that can receive funds even before the account exists. ### [Wallets](/participate/basics/wallets.md)[​](#wallets "Direct link to wallets") Discover the wallets available for Aztec, how they differ from traditional crypto wallets, and what hardware wallet support looks like. ### [Fees](/participate/basics/fees.md)[​](#fees "Direct link to fees") Understand how transaction fees work on Aztec. We use "mana" (similar to Ethereum's gas) and fees are paid in the $AZTEC token. ### [Transactions](/participate/basics/transactions.md)[​](#transactions "Direct link to transactions") Learn about the unique transaction lifecycle on Aztec, including why transactions are proven on your device before being sent to the network. ### [Blocks](/participate/basics/blocks.md)[​](#blocks "Direct link to blocks") Explore how blocks are produced on Aztec, the role of sequencers and provers, and how epochs organize proving work. ### [Bridging](/participate/basics/bridging.md)[​](#bridging "Direct link to bridging") Understand how assets move between Ethereum (L1) and Aztec (L2), including the role of portal contracts and message passing. *** Want the technical details? These pages focus on concepts for a general audience. For technical implementation details and code examples, see the [Developer Guides](/developers/overview.md). --- # Addresses on Aztec Addresses on Aztec work differently from traditional blockchains. This page explains how they're created and why they enable powerful privacy features. ## Every Account is a Smart Contract[​](#every-account-is-a-smart-contract "Direct link to Every Account is a Smart Contract") On Ethereum, you have two types of accounts: externally owned accounts (EOAs) controlled by private keys, and smart contract accounts. On Aztec, **every account is a smart contract**. This is called "native account abstraction." This design means your account can have custom rules for: * **Authentication** - How you prove ownership (single key, multisig, biometrics) * **Recovery** - How you regain access if you lose your keys * **Permissions** - What actions are allowed and when ## Deterministic Addresses[​](#deterministic-addresses "Direct link to Deterministic Addresses") One of Aztec's unique features is that addresses can be calculated before the account contract is deployed. Your address is derived from: 1. **Your public keys** - The cryptographic keys associated with your account 2. **Contract information** - The code and parameters of your account contract This has practical benefits: * **Receive funds first** - Someone can send you tokens before your account even exists * **Predictable addresses** - You know your address as soon as you generate your keys * **Flexible deployment** - Deploy your account only when you need to send transactions ## The Complete Address[​](#the-complete-address "Direct link to The Complete Address") An Aztec address is derived from a "complete address," which bundles together all the information needed to interact with an account: * All your public keys (for encryption and privacy features) * The partial address (contract deployment information) The address itself is a hash of this complete address. To send someone a private transaction, you need their complete address - not just the address hash - because you need their public keys to encrypt data for them. ## Privacy Considerations[​](#privacy-considerations "Direct link to Privacy Considerations") Aztec addresses are designed with privacy in mind: * **Addresses don't reveal activity** - Unlike Ethereum where all transactions associated with an address are visible on chain, Aztec's privacy features keep your transaction history hidden * **Multiple accounts are easy** - Create as many accounts as you need for different purposes ## How This Differs from Ethereum[​](#how-this-differs-from-ethereum "Direct link to How This Differs from Ethereum") | Aspect | Ethereum | Aztec | | ------------------ | --------------------------------------------------- | ----------------------------------------------------------------- | | Account types | EOAs and smart contracts | Only smart contracts (native account abstraction) | | Address derivation | From public key (EOA) or CREATE/CREATE2 (contracts) | Deterministically from complete address (keys + contract code) | | Custom auth logic | Requires smart contract wallet (e.g. ERC-4337) | Built in - every account defines its own auth | | Sending to someone | Just need their address | Need their complete address (includes public keys for encryption) | *** For developers Learn how to work with accounts programmatically in the [Accounts documentation](/developers/docs/foundational-topics/accounts.md). --- # Blocks and Epochs Aztec uses a decentralized block production system with two key roles: sequencers who produce blocks and provers who generate validity proofs. ## Block Production Overview[​](#block-production-overview "Direct link to Block Production Overview") Block production on Aztec involves several steps: 1. **Selection** - A sequencer is randomly chosen 2. **Block proposal** - The sequencer creates and proposes a block 3. **Attestation** - Committee members validate and sign 4. **Proving** - Provers generate validity proofs 5. **Settlement** - Proofs are verified on Ethereum ## Sequencers[​](#sequencers "Direct link to Sequencers") Sequencers are responsible for ordering transactions and producing blocks. They: * Collect transactions from the network * Order them into blocks * Propose blocks to the committee * Earn rewards for successful blocks ### How Sequencers Are Selected[​](#how-sequencers-are-selected "Direct link to How Sequencers Are Selected") Each time slot, a sequencer is randomly selected to propose a block. The selection uses randomness from Ethereum (RANDAO), making it unpredictable but verifiable. This ensures: * **Fairness** - All staked sequencers have a chance to propose * **Unpredictability** - Nobody knows who will propose until the slot arrives * **Decentralization** - No single party controls block production ## Provers[​](#provers "Direct link to Provers") Provers generate the cryptographic proofs that make Aztec a valid rollup. They: * Watch for completed epochs * Generate validity proofs for all blocks in the epoch * Submit proofs to Ethereum * Earn rewards for successful proving ### Why Proving Matters[​](#why-proving-matters "Direct link to Why Proving Matters") The proofs guarantee that all transactions in a block were valid. Without a proof, Ethereum has no way to verify that Aztec's state transitions are correct. ## Epochs[​](#epochs "Direct link to Epochs") Aztec organizes time into **epochs**, which are groups of consecutive slots. Epochs serve as the unit for proving: * Multiple blocks are produced during an epoch * After the epoch ends, provers generate a single proof covering all blocks * This aggregated proof is submitted to Ethereum ### Why Use Epochs?[​](#why-use-epochs "Direct link to Why Use Epochs?") Generating a proof for every block would be expensive and slow. By batching blocks into epochs: * **Efficiency** - One proof covers many blocks * **Cost savings** - Fewer proofs mean lower L1 costs * **Parallelization** - Different provers can work on different parts ## The Attestation Committee[​](#the-attestation-committee "Direct link to The Attestation Committee") Not all sequencers propose blocks, but many participate in **attestation**. Committee members: 1. Receive proposed blocks 2. Verify the transactions 3. Sign attestations if valid 4. Return attestations to the proposer A block needs attestations from at least 2/3 + 1 of the committee to be considered valid. This provides Byzantine fault tolerance - the network can handle some malicious or offline validators. ## Timeline of a Block[​](#timeline-of-a-block "Direct link to Timeline of a Block") Here's what happens during a typical slot: | Phase | What Happens | | ------------ | ------------------------------------------- | | Selection | Randomness determines the proposer | | Collection | Proposer gathers pending transactions | | Ordering | Proposer arranges transactions into a block | | Proposal | Block is sent to committee members | | Validation | Committee members verify the block | | Attestation | Committee members sign if valid | | Finalization | Proposer collects attestations | ## Rewards[​](#rewards "Direct link to Rewards") Both sequencers and provers earn rewards: * **Sequencers** receive 70% of checkpoint rewards plus transaction fees * **Provers** receive 30% of checkpoint rewards See [Economics](/participate/token/economics.md) for details on how rewards work. ## What Happens If Things Go Wrong[​](#what-happens-if-things-go-wrong "Direct link to What Happens If Things Go Wrong") The system has safeguards for various failure scenarios: * **Proposer offline** - The slot is skipped; next slot's proposer takes over * **Insufficient attestations** - Block isn't finalized; transactions return to mempool * **Proof not submitted** - Unproven blocks are pruned and must be re-proposed *** For operators Want to run a sequencer or prover? See the [Operator Guides](/operate/operators.md). --- # Bridging Between Ethereum and Aztec Aztec is a Layer 2 rollup on Ethereum. This means assets like tokens need to be "bridged" between the two networks. This page explains how bridging works at a high level. ## How Bridging Works[​](#how-bridging-works "Direct link to How Bridging Works") Moving assets between Ethereum (L1) and Aztec (L2) involves a few key concepts: ### Portal Contracts[​](#portal-contracts "Direct link to Portal Contracts") A **portal** is a smart contract on Ethereum that represents the L1 side of a bridge. Each bridged asset has a portal contract that: * Holds locked assets on L1 * Sends messages to L2 when deposits are made * Releases assets when withdrawals are processed ### Message Passing[​](#message-passing "Direct link to Message Passing") Unlike direct function calls, L1 and L2 communicate through **messages**. This asynchronous design is necessary because: * L2 transactions are private and can't be triggered directly from L1 * Privacy requires that L2 "pulls" messages rather than having L1 "push" them * The rollup batches operations for efficiency ### The Bridging Flow[​](#the-bridging-flow "Direct link to The Bridging Flow") **Depositing to Aztec (L1 → L2):** 1. You send tokens to the portal contract on Ethereum 2. The portal creates a message for Aztec 3. The message is included in the next rollup 4. You (or anyone) can claim the tokens on Aztec **Withdrawing from Aztec (L2 → L1):** 1. You initiate a withdrawal on Aztec 2. A message is created for the portal 3. After the epoch is proven and verified on L1 4. You can claim your tokens from the portal contract ## Why "Pull" Instead of "Push"?[​](#why-pull-instead-of-push "Direct link to Why \"Pull\" Instead of \"Push\"?") Other rollups often have L1 contracts directly call L2 functions (push model). Aztec uses a pull model where L2 retrieves messages from L1. Here's why: **Privacy**: If L1 pushed data to L2, all the call data would be visible on Ethereum. With the pull model: * The deposit amount and original sender are visible on L1 * The recipient on L2 can remain private * L2 reveals only what's necessary ## Timing Considerations[​](#timing-considerations "Direct link to Timing Considerations") ### Deposits (L1 → L2)[​](#deposits-l1--l2 "Direct link to Deposits (L1 → L2)") * The L1 transaction sends tokens to the portal and creates a message * The message becomes available on L2 once the L1 block is processed by the rollup * The recipient can then claim the tokens on L2 ### Withdrawals (L2 → L1)[​](#withdrawals-l2--l1 "Direct link to Withdrawals (L2 → L1)") Withdrawals take longer: * Must wait for the epoch to end * Must wait for the proof to be generated * Must wait for proof verification on Ethereum * Only then can tokens be claimed on L1 This delay is inherent to ZK rollups since the proof must be generated and verified before L1 state can be updated. ## What Can Be Bridged[​](#what-can-be-bridged "Direct link to What Can Be Bridged") Aztec uses a portal contract pattern where each L2 token contract is paired with a portal contract on L1. Any token can be bridged if a corresponding portal contract is deployed. There is no fixed list of "supported" tokens - developers can create portals for any L1 asset. The fee token used on Aztec has its own portal for bridging in and out of the network. ## Security Considerations[​](#security-considerations "Direct link to Security Considerations") Bridging involves trust assumptions: * **Smart contract security** - Portal contracts must be correct and secure * **Rollup validity** - The Aztec proof system must be sound * **Liveness** - The network must continue operating for withdrawals Aztec's ZK proofs provide strong guarantees - if a proof verifies on L1, the state transition was valid. There is no challenge period; finality on L1 is immediate once the proof is verified. ## Using Bridges[​](#using-bridges "Direct link to Using Bridges") Bridging typically involves interacting with the portal contract on L1 (for deposits) or initiating a withdrawal on L2 (for withdrawals). The exact interface depends on the specific bridge implementation and any frontend tooling built around it. *** For developers Learn how to implement bridging in your applications in the [Portal documentation](/developers/docs/aztec-nr/framework-description/ethereum_aztec_messaging.md). --- # Fees on Aztec Every transaction on Aztec requires paying a fee. This page explains how fees work and how to get the tokens needed to pay them. ## Mana: Aztec's Unit of Work[​](#mana-aztecs-unit-of-work "Direct link to Mana: Aztec's Unit of Work") On Ethereum, you pay for computation using "gas." On Aztec, we use "mana." Mana measures the computational effort required to process your transaction. | Ethereum | Aztec | Description | | ---------------- | --------------- | -------------------------- | | Gas | Mana | Unit of computational work | | ETH per gas | $AZTEC per mana | Price per unit | | Gas fee (in ETH) | Fee (in $AZTEC) | Total cost | ## What Fees Cover[​](#what-fees-cover "Direct link to What Fees Cover") Aztec is a Layer 2 rollup on Ethereum, so fees account for costs on both layers: 1. **L1 costs** - Publishing blocks and data to Ethereum 2. **L2 costs** - Operating the Aztec network, including proving ## Paying Fees[​](#paying-fees "Direct link to Paying Fees") ### The Fee Token[​](#the-fee-token "Direct link to The Fee Token") Fees on Aztec are paid in $AZTEC, the native token of the network. To pay fees, you need: 1. $AZTEC tokens bridged from Ethereum 2. An Aztec account to hold them ### Getting Fee Tokens[​](#getting-fee-tokens "Direct link to Getting Fee Tokens") **On Testnet:** * Visit the [Aztec testnet faucet](https://testnet.aztec.network/) to get free testnet tokens * You'll also need Sepolia ETH for bridging - get it from [Sepolia faucets](https://sepoliafaucet.com/) **On Mainnet:** * Bridge $AZTEC from Ethereum to Aztec * The bridging process is similar to other L2 token bridges ### How Bridging Works[​](#how-bridging-works "Direct link to How Bridging Works") The fee token is bridged from Ethereum: 1. Lock $AZTEC on Ethereum (L1) 2. Claim your tokens on Aztec (L2) 3. Use them to pay transaction fees You can even claim bridged tokens and use them to pay for the claim transaction itself. ## Fee Payment Options[​](#fee-payment-options "Direct link to Fee Payment Options") Aztec offers flexible fee payment: ### Pay Directly[​](#pay-directly "Direct link to Pay Directly") If you have $AZTEC, pay for your own transactions directly from your account. ### Fee-Paying Contracts[​](#fee-paying-contracts "Direct link to Fee-Paying Contracts") A fee-paying contract (FPC) pays AZTEC(referredtoas"FeeJuice"inthedeveloperdocs)onyourbehalf,typicallyinexchangeforanothertoken.Thisletsapplicationsacceptfeesintokenstheirusersalreadyholdandletsbrand−newaccountstransactwithoutfirstacquiringAZTEC. * **Sponsored FPC** — available on testnet, devnet, and local network, covers transaction costs for free. Useful for development and onboarding. * **Third-party FPCs** — deployed by ecosystem teams for use on testnet and mainnet. These accept various tokens and handle $AZTEC fee payment behind the scenes. As one example, Nethermind offers a [Private Multi Asset FPC](https://github.com/NethermindEth/aztec-fpc) that supports multiple tokens with private fee transfers. ### Private Fee Payment[​](#private-fee-payment "Direct link to Private Fee Payment") Some apps pay fees through a fully private fee-paying contract, so the fee payment itself leaks no information about who you are. The more apps that route private fee payments through the *same* contract address, the stronger your privacy — every payment shares one large anonymity set instead of many small ones. If you care about fee privacy, look for apps that use a shared private FPC. For example, [DeFi Wonderland](https://github.com/defi-wonderland/aztec-fee-payment) has built a community implementation where every app can derive the same contract address from a common deployment salt. Note that the derived address depends on the compiled contract bytecode, which changes between Aztec versions — always verify the address matches the network you are using. ## Understanding Your Fee[​](#understanding-your-fee "Direct link to Understanding Your Fee") Transaction fees have several components: * **Base fee** - Minimum cost that adjusts based on network demand * **Priority fee** - Optional tip to prioritize your transaction * **Congestion pricing** - Fees increase when the network is busy (similar to Ethereum's EIP-1559) ## Tips for Lower Fees[​](#tips-for-lower-fees "Direct link to Tips for Lower Fees") * **Time your transactions** - Fees may be lower during off-peak times * **Batch operations** - Combine multiple actions in one transaction when possible * **Check fee estimates** - Wallets show estimated fees before you confirm *** For developers Learn how to implement fee payment in your applications in the [Fees documentation](/developers/docs/foundational-topics/fees.md). --- # Transactions on Aztec Transactions on Aztec work differently from traditional blockchains. The most significant difference is that your device proves the transaction before it's sent to the network. ## The Key Difference: Client-Side Proving[​](#the-key-difference-client-side-proving "Direct link to The Key Difference: Client-Side Proving") On Ethereum, you sign a transaction and send it to the network. Miners or validators then execute and validate it. On Aztec, your wallet does more work: 1. **Execute locally** - Your wallet runs the transaction on your device 2. **Generate proof** - Your wallet creates a zero-knowledge proof that the execution was correct 3. **Send proof** - Only the proof (not your private data) goes to the network This is called "client-side proving" and it's what enables Aztec's privacy. ## Why Client-Side Proving Matters[​](#why-client-side-proving-matters "Direct link to Why Client-Side Proving Matters") ### Privacy[​](#privacy "Direct link to Privacy") Because private execution happens on your device, the network never sees your private inputs. A private transfer doesn't reveal who sent it, who received it, or how much - just a proof that the rules were followed. ### Correctness[​](#correctness "Direct link to Correctness") The proof guarantees that private execution was performed correctly. The sequencer cannot alter the outcome of your private function calls - it can only verify the proof and include the transaction. ### Account Abstraction[​](#account-abstraction "Direct link to Account Abstraction") Because your account contract runs on your device, you can define custom authentication logic (like multisig or social recovery) without adding complexity for the network. ## Transaction Lifecycle[​](#transaction-lifecycle "Direct link to Transaction Lifecycle") Here's what happens when you send a transaction: ### 1. Initiation[​](#1-initiation "Direct link to 1. Initiation") You decide to make a transfer, interact with a contract, or perform some action. Your wallet prepares the transaction. ### 2. Private Execution[​](#2-private-execution "Direct link to 2. Private Execution") Your wallet (specifically the PXE - Private eXecution Environment) executes the private portion of the transaction locally. This determines what private state changes will happen. ### 3. Proof Generation[​](#3-proof-generation "Direct link to 3. Proof Generation") Your wallet generates a zero-knowledge proof of the private execution. This proves correctness without revealing private information. ### 4. Submission[​](#4-submission "Direct link to 4. Submission") The transaction request - including the private proof and any public function calls - is sent to the network. ### 5. Sequencer Processing[​](#5-sequencer-processing "Direct link to 5. Sequencer Processing") The sequencer verifies the private proof and executes any public function calls. Public execution happens on the sequencer, not on your device, since it reads from and writes to public state that is shared across the network. ### 6. Block Production[​](#6-block-production "Direct link to 6. Block Production") The sequencer assembles transactions into a block and proposes it. Validators in the committee validate and attest to the block. ### 7. Epoch Proving[​](#7-epoch-proving "Direct link to 7. Epoch Proving") Provers generate a rollup proof covering all transactions in the epoch, aggregating the work into a single proof. ### 8. L1 Settlement[​](#8-l1-settlement "Direct link to 8. L1 Settlement") The epoch proof is submitted to Ethereum. Once verified on L1, the state transition is finalized. ## Private vs Public Transactions[​](#private-vs-public-transactions "Direct link to Private vs Public Transactions") Aztec supports both private and public execution: | Aspect | Private | Public | | ------------------ | ------------------ | ----------------------- | | Execution location | Your device | Sequencer | | Data visibility | Hidden | Visible | | State model | Notes (like UTXOs) | Storage (like Ethereum) | | Proof generation | You | Network | Many transactions use both - private functions first, then public functions. ## What the Network Sees[​](#what-the-network-sees "Direct link to What the Network Sees") Even with privacy, some information is visible: * **That a transaction occurred** - The fact of the transaction is public * **Number of private state updates** - How many notes were created/spent * **Public function calls** - If any public functions are called * **Fees paid** - The transaction fee What stays private: * Who sent the transaction (for private functions) * Transaction amounts (for private transfers) * Which accounts are involved (for private interactions) ## Transaction Speed[​](#transaction-speed "Direct link to Transaction Speed") Transaction finality depends on several factors: 1. **Inclusion** - When a sequencer includes your transaction in a block 2. **Block finalization** - When the block is attested by the committee 3. **Epoch proving** - When the epoch proof is generated 4. **L1 settlement** - When the proof is verified on Ethereum For most practical purposes, transactions are "confirmed" after block finalization, even before L1 settlement. *** For developers Learn how to construct and send transactions in the [Transactions documentation](/developers/docs/foundational-topics/transactions.md). --- # Wallets on Aztec Wallets on Aztec do more than just store your keys. This page covers what wallets are available, what makes them different, and the future of hardware wallet support. ## Available Wallets[​](#available-wallets "Direct link to Available Wallets") ### Aztec CLI Wallet[​](#aztec-cli-wallet "Direct link to Aztec CLI Wallet") The Aztec CLI wallet is the reference wallet packaged and maintained by the Aztec team. It is a command-line tool that handles account creation, transaction signing, and private state management. note This is the wallet shipped as part of the Aztec tooling suite. Third-party wallets may become available as the ecosystem grows. ### Developer Wallets[​](#developer-wallets "Direct link to Developer Wallets") For testing and development, the Aztec sandbox includes built-in wallet functionality that developers can use to test their applications. ## What Makes Aztec Wallets Different[​](#what-makes-aztec-wallets-different "Direct link to What Makes Aztec Wallets Different") Aztec wallets have additional responsibilities compared to traditional crypto wallets: ### Private State Management[​](#private-state-management "Direct link to Private State Management") Unlike public blockchains where anyone can see your balance, Aztec keeps your private data encrypted. Your wallet must: * Track your private notes (like UTXOs) * Decrypt incoming transfers * Maintain a local database of your private state ### Client-Side Proving[​](#client-side-proving "Direct link to Client-Side Proving") On Aztec, transactions are proven on your device before being sent to the network. This means: * Your wallet generates zero-knowledge proofs * Proof generation happens locally * Only the proof (not your private data) is shared with the network ### Key Management[​](#key-management "Direct link to Key Management") Aztec accounts use multiple key types for different purposes: * **Nullifier keys** - Spend your private notes by creating nullifiers that mark notes as consumed * **Incoming viewing keys** - Decrypt notes that others send to you * **Outgoing viewing keys** - Decrypt records of notes you've sent to others * **Tagging keys** - Help your wallet efficiently find and index your private notes Your wallet derives all of these from your master seed and manages them securely. For transaction authorization, Aztec uses its native account abstraction—your account contract defines its own authentication logic, which may use one of these keys or a separate signing mechanism. ## Hardware Wallet Support[​](#hardware-wallet-support "Direct link to Hardware Wallet Support") ### Current Status[​](#current-status "Direct link to Current Status") Hardware wallet support for Aztec is **planned but not yet available**. This is a high priority for the ecosystem. ### How It Would Work[​](#how-it-would-work "Direct link to How It Would Work") When hardware wallets are supported, they would: 1. **Store authentication keys securely** - Keys used for transaction authorization never leave the hardware device 2. **Authorize transactions** - Approve transactions with physical confirmation 3. **Work with viewing keys** - Some viewing functionality may need to remain on software wallets for practicality ### Challenges[​](#challenges "Direct link to Challenges") Supporting hardware wallets with Aztec's privacy model presents unique challenges: * **Proof generation** - Zero-knowledge proofs require significant computation, which hardware wallets can't perform * **Key types** - Aztec uses multiple key types that need different handling * **Privacy trade-offs** - Balancing hardware security with the need to view private state ### Expected Support[​](#expected-support "Direct link to Expected Support") The Aztec team is working on hardware wallet integration. Ledger and Trezor support is planned for future releases. Check the [Aztec forum](https://forum.aztec.network/) for updates. ## Choosing a Wallet[​](#choosing-a-wallet "Direct link to Choosing a Wallet") When selecting an Aztec wallet, consider: | Factor | What to Look For | | ------------------ | ------------------------------------- | | **Security** | Open source, audited code | | **Backup** | Seed phrase or key export options | | **Compatibility** | Support for the applications you use | | **Device support** | Browser extension, mobile, or desktop | ## Security Best Practices[​](#security-best-practices "Direct link to Security Best Practices") * **Back up your keys** - Write down your recovery phrase and store it securely * **Use separate accounts** - Keep large holdings in accounts you don't use for daily transactions * **Verify addresses** - Always double-check addresses before sending * **Keep software updated** - Use the latest wallet versions for security fixes *** For developers Learn about building wallet integrations in the [Wallets documentation](/developers/docs/foundational-topics/wallets.md). --- # Governance The Aztec network is governed by its community through an onchain governance system. This system allows the network to upgrade rollup contracts, adjust parameters, and evolve over time while maintaining security and decentralization. ## Quick Summary for Token Holders[​](#quick-summary-for-token-holders "Direct link to Quick Summary for Token Holders") If you're a token holder looking to participate in governance, here's what you need to know: | Your Situation | How to Participate | What Happens | | ---------------------------------------------------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | **I have staked tokens** (as sequencer or delegator) | Nothing required - you're already participating | Your voting power is automatically delegated to the rollup, which votes "yea" on proposals that passed sequencer signaling | | **I want to vote differently than the default** | Delegate your voting power to yourself | You can cast your own votes on proposals | | **I have tokens but haven't staked** | Lock tokens in the Governance contract | You gain voting power without staking rewards or slashing risk | For step-by-step instructions, see [Voting on Proposals](/participate/token/voting.md). ## Design Goals[​](#design-goals "Direct link to Design Goals") The governance system is designed around two core requirements: 1. **Backwards Compatibility**: Users must always be able to bridge assets in and out of any rollup version that has ever existed 2. **Canonical Rewards**: Only the most recent (canonical) rollup should receive block rewards These goals shape the entire governance architecture, from how rollups are tracked to how upgrades are proposed and executed. ## How Governance Works[​](#how-governance-works "Direct link to How Governance Works") ![](/assets/ideal-img/governance.188029b.640.png) Governance follows a multi-stage process: 1. **Signaling**: Block producers on the canonical rollup signal support for a payload by calling `signal()` on the Governance Proposer during their assigned slots 2. **Quorum**: When enough signals are received within a round (e.g., 151 out of 300 slots), the payload qualifies for proposal 3. **Proposal Creation**: Anyone can call `submitRoundWinner()` to formally submit the payload as a proposal to Governance 4. **Voting**: Token holders vote on the proposal using their voting power (determined at the moment voting opens) 5. **Execution**: After the voting period and execution delay, anyone can trigger execution of approved proposals All signaling and voting happen on L1 (Ethereum). ## Core Contracts[​](#core-contracts "Direct link to Core Contracts") The governance system consists of several interconnected smart contracts: ### Registry[​](#registry "Direct link to Registry") The [Registry](https://github.com/AztecProtocol/aztec-packages/blob/master/l1-contracts/src/governance/Registry.sol) maintains a list of all rollup contract instances. The most recent entry is considered "canonical" and is eligible to receive block rewards. The Registry's `addRollup()` function can only be called by the Governance contract. ### Governance[​](#governance-1 "Direct link to Governance") The [Governance](https://github.com/AztecProtocol/aztec-packages/blob/master/l1-contracts/src/governance/Governance.sol) contract is the core of the system. It: * Receives proposals from the Governance Proposer * Tracks proposal state through their lifecycle * Manages voting power through deposits and withdrawals * Executes approved proposals ### Governance Proposer[​](#governance-proposer "Direct link to Governance Proposer") The [Governance Proposer](https://github.com/AztecProtocol/aztec-packages/blob/master/l1-contracts/src/governance/proposer/GovernanceProposer.sol) is the gateway for submitting proposals. Only this contract can propose to Governance, ensuring that proposals have community support before entering the voting phase. The Governance Proposer extends the [EmpireBase](https://github.com/AztecProtocol/aztec-packages/blob/master/l1-contracts/src/governance/proposer/EmpireBase.sol) contract, which implements the round-based signaling mechanism. ### Governance Staking Escrow (GSE)[​](#governance-staking-escrow-gse "Direct link to Governance Staking Escrow (GSE)") The [GSE](https://github.com/AztecProtocol/aztec-packages/blob/master/l1-contracts/src/governance/GSE.sol) bridges staking and governance. It: * Holds validator stakes on behalf of rollup contracts * Tracks voting power delegation * Enables stake to automatically move to new rollup versions * Allows validators to vote independently or delegate to the rollup See [GSE and Stake Mobility](/participate/governance/gse.md) for details. ## Key Concepts[​](#key-concepts "Direct link to Key Concepts") ### Payloads[​](#payloads "Direct link to Payloads") A **payload** is a contract that defines the actions to be executed if a proposal passes. Payloads implement the [IPayload](https://github.com/AztecProtocol/aztec-packages/blob/master/l1-contracts/src/governance/interfaces/IPayload.sol) interface, which has a `getActions()` function returning a list of calls to make. For example, a payload to register a new rollup would include a call to `Registry.addRollup(newRollupAddress)`. ### Rounds and Slots[​](#rounds-and-slots "Direct link to Rounds and Slots") The signaling system operates in **rounds**, where each round consists of a fixed number of **slots** (e.g., 300 slots per round). A slot corresponds to approximately 36 seconds of L2 time. During each slot, only the designated block proposer can signal for a payload. This prevents timing games and ensures signaling reflects genuine validator support. ### Quorum[​](#quorum "Direct link to Quorum") For a payload to become a proposal, it must receive signals from a quorum of slots within a single round. For example, if quorum is set to 151 out of 300 slots, at least 151 different block proposers must signal for the same payload address within one round. ### Voting Power[​](#voting-power "Direct link to Voting Power") Voting power in Governance comes from depositing tokens. Key points: * Power is timestamped at deposit time * When voting on a proposal, only power you had *before* the proposal became active counts * Withdrawing requires a two-step process with a delay (on the order of days) * Partial voting is allowed (e.g., vote "yea" with half your power, "nay" with the other half) ## Topics in This Section[​](#topics-in-this-section "Direct link to Topics in This Section") * [Proposal Lifecycle](/participate/governance/proposal-lifecycle.md) - The complete journey from payload to execution * [Voting](/participate/governance/voting.md) - How voting power works and how votes are cast * [GSE and Stake Mobility](/participate/governance/gse.md) - How the GSE enables seamless rollup upgrades * [Upgrades](/participate/governance/upgrades.md) - The end-to-end process for network upgrades * [L1 Contracts](/participate/governance/contracts.md) - Smart contracts that power governance ## Related Guides[​](#related-guides "Direct link to Related Guides") For Sequencer Operators To participate in governance as a sequencer (signaling and voting), see [Governance Participation](/operate/operators/sequencer-management/creating_and_voting_on_proposals.md). For Token Holders To vote on proposals with your staked tokens, see [Voting on Proposals](/participate/token/voting.md). --- # L1 Contracts Work in Progress This page provides a high-level overview of Aztec's governance contracts. The contract interfaces and implementations are still evolving. For the authoritative source, see the [l1-contracts repository](https://github.com/AztecProtocol/aztec-packages/tree/master/l1-contracts/src/governance). ## Contract Overview[​](#contract-overview "Direct link to Contract Overview") The Aztec governance system consists of several L1 smart contracts: | Contract | Purpose | | ---------------------- | --------------------------------------------------------------------- | | **Registry** | Tracks all rollup instances; determines which is canonical | | **Governance** | Handles proposal voting and execution | | **GovernanceProposer** | Manages sequencer signaling and proposal submission | | **GSE** | Governance Staking Escrow - manages validator stakes and voting power | | **Rollup** | The rollup contract itself; validators stake here | ## Registry[​](#registry "Direct link to Registry") The [Registry](https://github.com/AztecProtocol/aztec-packages/blob/master/l1-contracts/src/governance/Registry.sol) maintains an append-only list of rollup instances. Only the Governance contract (as owner) can add new rollups. Key properties: * **Backwards compatible**: All historical rollups remain accessible * **Canonical selection**: Only the latest rollup receives block rewards * **Immutable entries**: Once added, rollup addresses cannot be removed ## Governance[​](#governance "Direct link to Governance") The [Governance](https://github.com/AztecProtocol/aztec-packages/blob/master/l1-contracts/src/governance/Governance.sol) contract is the decision-making body that executes approved proposals. Key functions: * `deposit()` / `initiateWithdraw()` - Manage voting power * `vote()` - Cast votes on proposals * `execute()` - Execute approved proposals See [Proposal Lifecycle](/participate/governance/proposal-lifecycle.md) for how proposals move through the system. ## GovernanceProposer[​](#governanceproposer "Direct link to GovernanceProposer") The [GovernanceProposer](https://github.com/AztecProtocol/aztec-packages/blob/master/l1-contracts/src/governance/proposer/GovernanceProposer.sol) handles the signaling phase where sequencers vote to promote payloads to proposals. Key functions: * `signal()` - Sequencers signal support for a payload during their slot * `submitRoundWinner()` - Submit a payload that reached quorum as a proposal ## GSE (Governance Staking Escrow)[​](#gse-governance-staking-escrow "Direct link to GSE (Governance Staking Escrow)") The [GSE](https://github.com/AztecProtocol/aztec-packages/blob/master/l1-contracts/src/governance/GSE.sol) holds validator stakes and manages voting power delegation. Key features: * **Stake mobility**: Stakes can automatically follow rollup upgrades * **Voting delegation**: Validators can delegate voting power * **Escape hatch**: `proposeWithLock()` for emergency proposals See [GSE and Stake Mobility](/participate/governance/gse.md) for details. ## Related Topics[​](#related-topics "Direct link to Related Topics") * [Governance Overview](/participate/governance.md) - How the governance system works * [Proposal Lifecycle](/participate/governance/proposal-lifecycle.md) - Stages from signaling to execution * [Network Upgrades](/participate/governance/upgrades.md) - How upgrades use these contracts --- # Governance Staking Escrow (GSE) The Governance Staking Escrow (GSE) solves a critical challenge in network upgrades: how do validators transition their stake from an old rollup to a new one without lengthy exit and entry delays? ## The Problem[​](#the-problem "Direct link to The Problem") When the network upgrades to a new rollup contract, validators face a dilemma: 1. Their stake is locked in the old rollup 2. Exiting the old rollup has a delay (for security) 3. Entering the new rollup has a queue 4. During this transition, they can't validate on either rollup Without a solution, upgrades would cause significant disruption as the validator set scrambles to migrate. ## The Solution: GSE[​](#the-solution-gse "Direct link to The Solution: GSE") The GSE acts as a neutral escrow that holds validator stakes on behalf of rollup contracts. Instead of validators depositing directly into each rollup, they deposit into the GSE, which: 1. Tracks stakes per validator per rollup 2. Allows stakes to automatically move with rollup upgrades 3. Maintains voting power delegation for governance 4. Enables seamless transitions between rollup versions ## How It Works[​](#how-it-works "Direct link to How It Works") ### Depositing with Move Flag[​](#depositing-with-move-flag "Direct link to Depositing with Move Flag") When a validator deposits stake, they choose whether their stake should follow upgrades: ``` function deposit( address _attester, address _withdrawer, bool _moveWithLatestRollup ) external; ``` The `_moveWithLatestRollup` flag determines stake behavior: | Flag Value | Behavior | | ---------- | ----------------------------------------------- | | `false` | Stake is tied to the specific rollup address | | `true` | Stake follows the "latest" rollup automatically | ### The Bonus Instance[​](#the-bonus-instance "Direct link to The Bonus Instance") The GSE uses a special address called the "bonus instance" to track stakes that should move: ``` address public constant BONUS_INSTANCE_ADDRESS = address(uint160(uint256(keccak256("bonus-instance")))); ``` This is the one exception where "instance" doesn't refer to a rollup contract—it's a virtual address for accounting purposes. ### Stake Visibility[​](#stake-visibility "Direct link to Stake Visibility") When the GSE determines which validators have stake in a rollup: * Stakes deposited with `_moveWithLatestRollup = false` are tied to that rollup's address * Stakes deposited with `_moveWithLatestRollup = true` are tied to the bonus instance * Only the **latest** rollup in the GSE can access stakes from the bonus instance ### Example Scenario[​](#example-scenario "Direct link to Example Scenario") ``` Initial State: ├── Rollup A is canonical ├── Alice deposits 100 tokens (moveWithLatest = false) ├── Bob deposits 100 tokens (moveWithLatest = true) └── Both can validate on Rollup A After Upgrade to Rollup B: ├── Rollup B is now canonical ├── Alice's stake: Still on Rollup A only ├── Bob's stake: Now available to Rollup B ├── Alice must manually exit A and enter B └── Bob validates on B immediately ``` ## GSE Payload Verification[​](#gse-payload-verification "Direct link to GSE Payload Verification") When the Governance Proposer wraps payloads in a [GSEPayload](https://github.com/AztecProtocol/aztec-packages/blob/master/l1-contracts/src/governance/GSEPayload.sol), it adds checks to ensure the new rollup has sufficient stake: After executing the original payload actions, the GSEPayload verifies: ``` (stake in latest rollup) + (stake in bonus instance) > 2/3 of total stake ``` This prevents proposals that would leave the network without a supermajority of validators on the active rollup. ### Why This Matters[​](#why-this-matters "Direct link to Why This Matters") Without this check: * A malicious proposal could switch to a rollup with zero validators * Block production would halt * The network couldn't even propose a fix through governance The GSEPayload ensures continuity of the validator set across upgrades. ## GSE Accounting[​](#gse-accounting "Direct link to GSE Accounting") The GSE maintains several pieces of information per rollup: ### Per Validator (Attester)[​](#per-validator-attester "Direct link to Per Validator (Attester)") * Balance in the rollup * Withdrawer address (who can initiate withdrawals) * Delegation target (who votes with their power) ### Per Rollup[​](#per-rollup "Direct link to Per Rollup") * Total stake deposited * Entry/exit queues * Latest rollup pointer ### For Governance[​](#for-governance "Direct link to For Governance") * Voting power per delegatee * Power used per proposal * Timestamp snapshots for voting ## Voting Power Delegation[​](#voting-power-delegation "Direct link to Voting Power Delegation") By default, when validators deposit stake: 1. Their voting power is delegated to the rollup contract 2. The rollup votes "yea" on proposals its block producers signaled for 3. This creates automatic alignment between validators and governance ### Custom Delegation[​](#custom-delegation "Direct link to Custom Delegation") Validators can change their delegation: ``` function delegate( address _rollup, address _attester, address _delegatee ) external; ``` Requirements: * Must be called by the withdrawer * Delegatee can be any address (including the validator themselves) * Once delegated, the delegatee can vote directly through the GSE ### Voting Through GSE[​](#voting-through-gse "Direct link to Voting Through GSE") After custom delegation, validators can vote directly: ``` function vote( uint256 _proposalId, uint256 _amount, bool _support ) external; ``` This allows: * Voting against proposals (rollups always vote "yea") * Partial voting with specific amounts * More granular control ## Propose With Lock[​](#propose-with-lock "Direct link to Propose With Lock") The GSE provides an escape hatch for creating proposals without sequencer signaling: ``` function proposeWithLock(IPayload _payload, address _to) external returns (uint256); ``` This function: 1. Takes tokens from `msg.sender` 2. Deposits them into Governance 3. Calls `proposeWithLock` on Governance 4. Creates a proposal with a long withdrawal lock The long lock prevents governance attacks while ensuring the network can always upgrade if needed. ## State Transitions[​](#state-transitions "Direct link to State Transitions") ``` ┌─────────────────────────────────────────────────────────────────┐ │ GSE State │ ├─────────────────────────────────────────────────────────────────┤ │ Rollup A (old) │ Bonus Instance │ Rollup B (new) │ │ ───────────────── │ ─────────────── │ ────────────── │ │ Alice: 100 │ Bob: 100 │ (empty) │ │ │ │ │ │ Access: Rollup A only │ Access: Latest │ Access: B only │ └─────────────────────────────────────────────────────────────────┘ │ ▼ (Upgrade to B) ┌─────────────────────────────────────────────────────────────────┐ │ Rollup A (old) │ Bonus Instance │ Rollup B (new) │ │ ───────────────── │ ─────────────── │ ────────────── │ │ Alice: 100 │ Bob: 100 │ (empty) │ │ │ │ │ │ │ Alice validates on A │ └───────────┼──▶ Bob can │ │ (must exit to move) │ │ validate │ └─────────────────────────────────────────────────────────────────┘ ``` ## Best Practices[​](#best-practices "Direct link to Best Practices") ### For Validators[​](#for-validators "Direct link to For Validators") 1. **Use `moveWithLatestRollup = true`** if you want automatic migration 2. **Delegate to yourself** if you want to vote independently 3. **Monitor governance proposals** even if your stake moves automatically ### For Governance Proposals[​](#for-governance-proposals "Direct link to For Governance Proposals") 1. **Ensure new rollups use the same GSE** for stake continuity 2. **Test that sufficient stake will be available** before signaling 3. **Allow time for validators to prepare** before execution ## Related Topics[​](#related-topics "Direct link to Related Topics") * [Voting](/participate/governance/voting.md) - How voting power and delegation work * [Proposal Lifecycle](/participate/governance/proposal-lifecycle.md) - How GSEPayload wrapping fits in * [Upgrades](/participate/governance/upgrades.md) - End-to-end upgrade process * [Staking Tokens](/participate/token/staking.md) - How staking works at the protocol level --- # Proposal Lifecycle A proposal's journey from idea to execution involves multiple stages, each with specific requirements and timing constraints. This page details each stage of the lifecycle. ## Overview[​](#overview "Direct link to Overview") ``` ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Payload │────▶│ Signaling │────▶│ Queued │────▶│ Active │────▶│ Executable │────▶│ Executed │ │ Deployed │ │ (Round) │ │(Voting Delay│ │ (Voting) │ │(Exec. Delay)│ │ │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ │ ▼ ▼ ┌─────────────┐ ┌─────────────┐ │ Expired │ │ Rejected │ │ (No Quorum) │ │(Insufficient│ └─────────────┘ │ Support) │ └─────────────┘ ``` ## Stage 1: Payload Deployment[​](#stage-1-payload-deployment "Direct link to Stage 1: Payload Deployment") Before any governance process begins, someone must deploy the contracts that define what the proposal will do: 1. **New Contracts**: Deploy any new contracts the proposal requires (e.g., a new rollup version) 2. **Payload Contract**: Deploy a payload contract implementing `IPayload` that returns the actions to execute ``` interface IPayload { struct Action { address target; bytes data; } function getActions() external view returns (Action[] memory); } ``` For example, a payload to add a new rollup to the Registry might look like: ``` contract RegisterRollupPayload is IPayload { IRegistry public immutable REGISTRY; address public immutable NEW_ROLLUP; constructor(IRegistry _registry, address _newRollup) { REGISTRY = _registry; NEW_ROLLUP = _newRollup; } function getActions() external view returns (Action[] memory) { Action[] memory actions = new Action[](1); actions[0] = Action({ target: address(REGISTRY), data: abi.encodeWithSelector(REGISTRY.addRollup.selector, NEW_ROLLUP) }); return actions; } } ``` ## Stage 2: Signaling[​](#stage-2-signaling "Direct link to Stage 2: Signaling") Once a payload is deployed, block producers on the canonical rollup can signal support for it. ### How Signaling Works[​](#how-signaling-works "Direct link to How Signaling Works") The Governance Proposer operates in **rounds**. Each round consists of `ROUND_SIZE` slots (e.g., 300 slots, approximately 180 minutes at 36 seconds per slot). During each slot: 1. The Governance Proposer queries the canonical rollup for the current proposer 2. Only that proposer can successfully call `signal(payloadAddress)` 3. The signal is recorded for the current round ### Reaching Quorum[​](#reaching-quorum "Direct link to Reaching Quorum") A payload reaches quorum when it receives `QUORUM` signals within a single round. For example: * Round size: 300 slots * Quorum: 151 signals (>50% of round size) If multiple block proposers signal for the same payload address and it reaches 151 signals before the round ends, that payload wins the round. ### Round Expiration[​](#round-expiration "Direct link to Round Expiration") If no payload reaches quorum by the end of a round: * All signals for that round are discarded * A new round begins * Signaling can start fresh for any payload ## Stage 3: Proposal Submission[​](#stage-3-proposal-submission "Direct link to Stage 3: Proposal Submission") When a payload reaches quorum, anyone can call `submitRoundWinner()` on the Governance Proposer to formally create a proposal. ### GSE Payload Wrapping[​](#gse-payload-wrapping "Direct link to GSE Payload Wrapping") The Governance Proposer doesn't submit the original payload directly. Instead, it wraps it in a [GSEPayload](https://github.com/AztecProtocol/aztec-packages/blob/master/l1-contracts/src/governance/GSEPayload.sol) that: 1. Copies all actions from the original payload 2. Appends an `amIValid()` validation check as the final action 3. When executed, verifies that >2/3 of total stake remains with the latest rollup Validation Timing The GSEPayload validation runs at **execution time**, not at proposal submission. This means a proposal could pass voting but fail execution if stake distribution changes during the voting and execution delay periods. This wrapping prevents proposals that would leave the network without a supermajority of validators on the active rollup. ## Stage 4: Queued (Voting Delay)[​](#stage-4-queued-voting-delay "Direct link to Stage 4: Queued (Voting Delay)") After submission, the proposal enters a mandatory waiting period before voting opens. * **Purpose**: Give the community time to review the proposal * **Duration**: Configurable (e.g., 12 hours on testnet) * **State**: Proposal exists but no votes can be cast yet During this period: * Anyone can review the payload code * Community discussion can occur offchain * Token holders can prepare their voting power Voting Power Snapshot Voting power is snapshotted at the moment the proposal transitions from Queued to Active. If you want to vote on a proposal, you must have deposited tokens *before* the voting delay ends. ## Stage 5: Active (Voting Period)[​](#stage-5-active-voting-period "Direct link to Stage 5: Active (Voting Period)") Once the voting delay passes, the proposal becomes active and voting opens. * **Duration**: Configurable (e.g., 24 hours on testnet) * **Who can vote**: Anyone with voting power at the snapshot timestamp * **Vote options**: "Yea" (support) or "Nay" (oppose) ### Voting Mechanics[​](#voting-mechanics "Direct link to Voting Mechanics") * Votes are weighted by voting power at the snapshot * Partial voting is allowed (split your power between yea and nay) * Votes cannot be changed once cast * The rollup contract automatically votes "yea" on proposals it signaled for See [Voting](/participate/governance/voting.md) for complete details on voting mechanics. ## Stage 6: Resolution[​](#stage-6-resolution "Direct link to Stage 6: Resolution") When the voting period ends, the proposal's fate is determined: ### Passed[​](#passed "Direct link to Passed") If the proposal receives sufficient support: * "Yea" votes exceed the required threshold * Proposal transitions to Executable state ### Rejected[​](#rejected "Direct link to Rejected") If the proposal fails to gain sufficient support: * Proposal is marked as rejected * It cannot be executed * A new proposal would need to go through the entire process again ## Stage 7: Executable (Execution Delay)[​](#stage-7-executable-execution-delay "Direct link to Stage 7: Executable (Execution Delay)") Proposals that pass voting enter another waiting period before execution. * **Purpose**: Allow node operators time to prepare for changes * **Duration**: Configurable (e.g., 12 hours on testnet) * **State**: Approved but not yet executed This delay is critical for upgrades because: * Operators may need to update node software * Services can prepare for the transition * Any last-minute issues can be identified ## Stage 8: Execution[​](#stage-8-execution "Direct link to Stage 8: Execution") After the execution delay, anyone can call `execute(proposalId)` on the Governance contract. Execution: 1. Retrieves the proposal's payload 2. Calls `getActions()` on the payload 3. Executes each action in sequence 4. Marks the proposal as executed If any action reverts, the entire execution reverts and the proposal remains executable. ## Timeline Example (Testnet)[​](#timeline-example-testnet "Direct link to Timeline Example (Testnet)") | Stage | Duration | Cumulative Time | | --------------- | ------------------------- | -------------------- | | Signaling | Up to 1 round (\~3 hours) | 3 hours | | Voting Delay | 12 hours | 15 hours | | Voting Period | 24 hours | 39 hours | | Execution Delay | 12 hours | 51 hours | | **Total** | | **\~2 days minimum** | Note: Mainnet parameters will likely be longer to provide more security. ## Escape Hatch: Propose With Lock[​](#escape-hatch-propose-with-lock "Direct link to Escape Hatch: Propose With Lock") What if block producers cannot or will not signal for a critical proposal? The governance system includes an escape hatch. The `proposeWithLock()` function allows anyone with sufficient voting power to bypass the signaling process: ``` function proposeWithLock(IPayload _proposal, address _to) external returns (uint256); ``` This requires: * A large amount of voting power (configured in governance) * The power is immediately locked with a long withdrawal delay * The proposal still goes through normal voting This mechanism protects against governance capture while ensuring the network can always upgrade if needed. ## Related Topics[​](#related-topics "Direct link to Related Topics") * [Voting](/participate/governance/voting.md) - Detailed voting mechanics * [GSE and Stake Mobility](/participate/governance/gse.md) - How GSEPayload ensures stake continuity * [Upgrades](/participate/governance/upgrades.md) - End-to-end upgrade process --- # Network Upgrades Network upgrades transition the Aztec network to a new rollup contract instance. This page explains why upgrades happen, how the Registry model works, and how validators migrate to new versions. For the detailed governance stages (signaling, voting, execution), see [Proposal Lifecycle](/participate/governance/proposal-lifecycle.md). ## Why Upgrades Happen[​](#why-upgrades-happen "Direct link to Why Upgrades Happen") The Aztec protocol evolves over time. Common reasons for upgrades include: * **Security Fixes**: Patching vulnerabilities discovered in the rollup * **Feature Additions**: Adding new functionality to the protocol * **Performance Improvements**: Optimizing proof generation or block processing * **Parameter Changes**: Adjusting staking requirements, fees, or timing ## The Registry Model[​](#the-registry-model "Direct link to The Registry Model") The Aztec governance system maintains a [Registry](https://github.com/AztecProtocol/aztec-packages/blob/master/l1-contracts/src/governance/Registry.sol) of all rollup contract instances. This design enables: 1. **Backwards Compatibility**: Every rollup that has ever existed remains accessible. Users can always bridge assets in or out of any historical rollup version. 2. **Canonical Selection**: Only the most recent rollup in the Registry is "canonical" and receives block rewards. When an upgrade passes governance, the new rollup address is added to the Registry via `addRollup()`. ## Validator Transition[​](#validator-transition "Direct link to Validator Transition") The [GSE](/participate/governance/gse.md) enables smooth validator transitions during upgrades. ### Automatic Migration[​](#automatic-migration "Direct link to Automatic Migration") Validators who deposited with `moveWithLatestRollup = true`: * Stake automatically becomes available to the new rollup * No action required—they can validate immediately * Voting power moves with their stake ### Manual Migration[​](#manual-migration "Direct link to Manual Migration") Validators who deposited with `moveWithLatestRollup = false`: * Must initiate withdrawal from the old rollup (has a delay) * Then deposit into the new rollup (may have an entry queue) * Temporary gap in validation ability ### Best Practice[​](#best-practice "Direct link to Best Practice") Use `moveWithLatestRollup = true` for seamless upgrades. Only use `false` if you specifically want to remain on an older rollup version. ## After an Upgrade[​](#after-an-upgrade "Direct link to After an Upgrade") Once an upgrade is executed: 1. **New Rollup is Canonical**: Block rewards go to the new rollup; Governance Proposer accepts signals only from new rollup validators 2. **Old Rollup Remains Accessible**: Users can still bridge assets in/out; validators with unmoved stake can still operate ## Related Topics[​](#related-topics "Direct link to Related Topics") * [Proposal Lifecycle](/participate/governance/proposal-lifecycle.md) - Detailed stages from signaling to execution * [GSE and Stake Mobility](/participate/governance/gse.md) - How stake transitions work * [Governance Participation](/operate/operators/sequencer-management/creating_and_voting_on_proposals.md) - How to participate as a sequencer --- # Voting Voting is how the Aztec community decides which proposals should be executed. This page explains how voting power is acquired, managed, and used. ## Voting Power[​](#voting-power "Direct link to Voting Power") Voting power in Governance comes from depositing tokens into the Governance contract. The amount of tokens deposited equals your voting power. ### Acquiring Voting Power[​](#acquiring-voting-power "Direct link to Acquiring Voting Power") To get voting power, call `deposit()` on the Governance contract: ``` function deposit(address beneficiary, uint256 amount) external; ``` This: 1. Transfers tokens from `msg.sender` to the Governance contract 2. Increases the `beneficiary`'s voting power 3. Records the power with a timestamp ### Timestamped Power[​](#timestamped-power "Direct link to Timestamped Power") Voting power is **timestamped** at the moment of deposit. This is crucial because: * When voting on a proposal, you can only use power you had *before* the proposal became active * This prevents flash loan attacks where someone borrows tokens just to vote * Your current balance doesn't matter; only your historical balance at the snapshot Example: ``` Timeline: ├── Day 1: Alice deposits 1000 tokens ├── Day 2: Proposal becomes Active (snapshot taken) ├── Day 3: Alice deposits 500 more tokens └── Day 4: Alice votes Alice can only vote with 1000 tokens (her balance at the Day 2 snapshot) ``` ## Deposit Control[​](#deposit-control "Direct link to Deposit Control") By default, not everyone can deposit into Governance. A **deposit control** mechanism restricts who can hold voting power. ### GSE as Deposit Controller[​](#gse-as-deposit-controller "Direct link to GSE as Deposit Controller") In the standard configuration, only the Governance Staking Escrow (GSE) can deposit into Governance. This means: * Validators who stake into the rollup automatically get voting power * The rollup contract can vote on behalf of its validators * Non-validators cannot directly hold governance power ### Disabling Deposit Control[​](#disabling-deposit-control "Direct link to Disabling Deposit Control") Governance can vote to disable deposit control, allowing anyone to hold voting power. This requires executing a proposal that calls the appropriate function on the Governance contract. Current Status On mainnet, deposit control was disabled at launch by passing the zero address as the GSE to the Governance constructor. This means anyone can deposit tokens and participate in governance. ## Withdrawing[​](#withdrawing "Direct link to Withdrawing") Withdrawing voting power is a two-step process with a mandatory delay. ### Step 1: Initiate Withdrawal[​](#step-1-initiate-withdrawal "Direct link to Step 1: Initiate Withdrawal") Call `initiateWithdraw()` to start the withdrawal process: ``` function initiateWithdraw(address to, uint256 amount) external; ``` This: 1. Reduces your voting power immediately 2. Creates a pending withdrawal record 3. Starts the withdrawal delay timer ### Step 2: Finalize Withdrawal[​](#step-2-finalize-withdrawal "Direct link to Step 2: Finalize Withdrawal") After the delay period passes, call `finaliseWithdraw()`: ``` function finalizeWithdraw(uint256 withdrawalId) external; ``` This transfers the tokens to the specified recipient. ### Why the Delay?[​](#why-the-delay "Direct link to Why the Delay?") The withdrawal delay (typically on the order of days) prevents governance attacks: * Attackers cannot quickly deposit, vote, and withdraw * The community has time to react to suspicious voting patterns * Long-term stakeholders have more influence than short-term speculators ## Delegation[​](#delegation "Direct link to Delegation") Validators can delegate their voting power to another address, allowing for flexible voting arrangements. ### Default Delegation[​](#default-delegation "Direct link to Default Delegation") When a validator deposits stake into a rollup: 1. The stake is held in the GSE 2. Voting power is delegated to the rollup contract by default 3. The rollup votes automatically on proposals its block producers signaled for ### Custom Delegation[​](#custom-delegation "Direct link to Custom Delegation") Validators can delegate to themselves or any other address: ``` // On the GSE function delegate(address rollup, address attester, address delegatee) external; ``` After delegating to yourself, you can vote directly on the GSE: ``` function vote(uint256 proposalId, uint256 amount, bool support) external; ``` ### Delegation Accounting[​](#delegation-accounting "Direct link to Delegation Accounting") The GSE tracks: `delegatee => proposal => power used` This allows: * Partial voting (use some power for "yea", some for "nay") * Split delegation (delegate to multiple addresses) * Transparent power tracking ## Casting Votes[​](#casting-votes "Direct link to Casting Votes") ### Voting Through the Rollup[​](#voting-through-the-rollup "Direct link to Voting Through the Rollup") The rollup contract has a `vote(uint256 proposalId)` function that: 1. Checks the rollup is canonical according to the Registry 2. Verifies it was canonical when the proposal was created 3. Votes "yea" using all delegated voting power The rollup only votes on proposals that: * Were submitted through the Governance Proposer * Had their block producers signal for the payload * Match the current governance configuration ### Voting Through the GSE[​](#voting-through-the-gse "Direct link to Voting Through the GSE") If you've delegated to yourself, vote directly on the GSE: ``` function vote( uint256 proposalId, uint256 amount, bool support // true = yea, false = nay ) external; ``` This allows: * Voting against proposals (the rollup always votes "yea") * Partial voting with specific amounts * More granular control over your voting power ## Partial Voting[​](#partial-voting "Direct link to Partial Voting") You can split your voting power between "yea" and "nay" on the same proposal: ``` // Vote "yea" with half your power gse.vote(proposalId, 500, true); // Vote "nay" with the other half gse.vote(proposalId, 500, false); ``` This is useful when you: * Have mixed feelings about a proposal * Want to signal nuanced support * Are voting on behalf of multiple stakeholders ## Vote Finality[​](#vote-finality "Direct link to Vote Finality") Once cast, votes cannot be changed. Consider carefully before voting, as you cannot: * Switch from "yea" to "nay" or vice versa * Increase or decrease your vote amount * Revoke your vote ## Quorum and Thresholds[​](#quorum-and-thresholds "Direct link to Quorum and Thresholds") For a proposal to pass, it must meet certain thresholds: * **Participation Quorum**: Minimum total votes (yea + nay) required * **Approval Threshold**: Minimum percentage of "yea" votes required These parameters are configured in the Governance contract and can be changed through governance proposals. ## Timeline Considerations[​](#timeline-considerations "Direct link to Timeline Considerations") | Event | Voting Power Impact | | ----------------------- | ------------------------------------------ | | Deposit tokens | Power recorded with current timestamp | | Proposal becomes Active | Snapshot taken of all power at this moment | | Vote on proposal | Must have power from before the snapshot | | Initiate withdrawal | Power reduced immediately | | Finalize withdrawal | Tokens returned after delay | ## Related Topics[​](#related-topics "Direct link to Related Topics") * [Proposal Lifecycle](/participate/governance/proposal-lifecycle.md) - When voting occurs in the proposal process * [GSE and Stake Mobility](/participate/governance/gse.md) - How validator stakes translate to voting power * [Voting on Proposals](/participate/token/voting.md) - Practical guide for token holders --- # $AZTEC Token Overview The $AZTEC token is the native token of the Aztec network. It serves multiple essential functions that keep the network secure and operational. ## Token Specifications[​](#token-specifications "Direct link to Token Specifications") | Property | Value | | -------------------- | -------------------------------------------- | | **Token Name** | Aztec | | **Ticker** | AZTEC | | **Standard** | ERC-20 (Ethereum) | | **Contract Address** | `0xA27EC0006e59f245217Ff08CD52A7E8b169E62D2` | | **Decimals** | 18 | | **Total Supply** | 10,350,000,000 AZTEC | note Please verify the contract address and other specifications with official Aztec sources before interacting with the token. ## Token Utility[​](#token-utility "Direct link to Token Utility") The $AZTEC token has three primary uses: ### 1. Transaction Fees[​](#1-transaction-fees "Direct link to 1. Transaction Fees") All transactions on Aztec require fees paid in $AZTEC. This includes: * Sending private transactions * Interacting with smart contracts * Deploying new contracts ### 2. Staking[​](#2-staking "Direct link to 2. Staking") Sequencers and validators must stake $AZTEC to participate in block production: * Provides economic security for the network * Creates incentives for honest behavior * Enables slashing for malicious actions ### 3. Governance[​](#3-governance "Direct link to 3. Governance") $AZTEC holders can participate in protocol governance: * Vote on protocol upgrades * Influence network parameters * Shape the future of Aztec ## Tokenomics[​](#tokenomics "Direct link to Tokenomics") ### Supply[​](#supply "Direct link to Supply") The $AZTEC token has a fixed initial supply with a controlled inflation mechanism to fund network rewards. ### Inflation Rate[​](#inflation-rate "Direct link to Inflation Rate") The protocol has a nominal annual inflation rate defined in the CoinIssuer contract. This rate: * Funds rewards for sequencers and provers * Is capped and cannot be changed after deployment * Represents the maximum possible inflation (actual may be lower) ### Token Distribution[​](#token-distribution "Direct link to Token Distribution") Checkpoint rewards are distributed each slot: * **70%** to block proposers (sequencers) * **30%** to provers See [Economics & Rewards](/participate/token/economics.md) for detailed information on how rewards work. ## How to Participate[​](#how-to-participate "Direct link to How to Participate") As a token holder, you have several options: | Action | What It Does | Requirements | | ------------------------------------------------ | ------------------------------------------- | --------------------------------------- | | **[Stake](/participate/token/staking.md)** | Secure the network, earn rewards | Meet minimum stake threshold | | **[Delegate](/participate/token/delegation.md)** | Earn rewards without running infrastructure | Choose an operator to delegate to | | **[Vote](/participate/token/voting.md)** | Participate in governance | Hold staked or governance-locked tokens | ## Understanding the Risks[​](#understanding-the-risks "Direct link to Understanding the Risks") Before staking or participating, understand: * **Slashing** - Validators can lose stake for misbehavior * **Lock-up periods** - Unstaking requires waiting through exit delays * **Market risk** - Token value can fluctuate See [Staking](/participate/token/staking.md) for details on staking requirements and risks. *** Want to run infrastructure? If you're interested in operating a node, sequencer, or prover, see the [Operator Guides](/operate/operators.md). --- # Delegating Stake If you want to participate in staking but don't want to run your own infrastructure, you can delegate your tokens to professional operators who run sequencers on your behalf. ## Before You Delegate[​](#before-you-delegate "Direct link to Before You Delegate") Understanding these concepts will help you choose the right operator: * [Staking Tokens](/participate/token/staking.md) - how proof of stake works * [Economics & Rewards](/participate/token/economics.md) - how rewards are distributed * [How governance works](/participate/governance.md) - understand voting power ## How Delegation Works[​](#how-delegation-works "Direct link to How Delegation Works") When you delegate tokens to an operator: 1. **Your tokens are staked** through the operator's validator 2. **The operator runs infrastructure** on your behalf 3. **Rewards are shared** between you and the operator based on their fee structure 4. **Slashing risk is shared** - if the operator misbehaves, your delegated stake can be slashed ## Choosing an Operator[​](#choosing-an-operator "Direct link to Choosing an Operator") When selecting an operator to delegate to, consider: ### Performance Metrics[​](#performance-metrics "Direct link to Performance Metrics") * **Uptime**: How reliably does the operator maintain their infrastructure? * **Attestation Rate**: Do they consistently participate in consensus? * **Slashing History**: Have they been slashed before? ### Economic Terms[​](#economic-terms "Direct link to Economic Terms") * **Commission Rate**: What percentage of rewards does the operator keep? * **Minimum Delegation**: Is there a minimum amount required? ### Reputation[​](#reputation "Direct link to Reputation") * **Track Record**: How long have they been operating? * **Community Standing**: Are they known in the Aztec community? * **Transparency**: Do they communicate openly about their operations? ## Delegation Process[​](#delegation-process "Direct link to Delegation Process") ### Prerequisites[​](#prerequisites "Direct link to Prerequisites") * An Ethereum wallet that owns an Aztec Token Vault (the same wallet you connected during the token sale) * A Token Vault balance of at least 200,000 AZTEC tokens Minimum Stake You must stake a minimum of 200,000 AZTEC per validator — similar to how Ethereum requires 32 ETH per validator. ### Step 1: Connect Your Wallet[​](#step-1-connect-your-wallet "Direct link to Step 1: Connect Your Wallet") Navigate to [staking.aztec.network](https://staking.aztec.network) and click **Connect Wallet**. Connect the wallet that owns your Token Vaults. The dashboard displays all your Token Vaults and an overview of the assets under your control. Click on any Token Vault to view details such as its vesting schedule. ### Step 2: Navigate to the Stake Tab[​](#step-2-navigate-to-the-stake-tab "Direct link to Step 2: Navigate to the Stake Tab") Above the Token Vaults overview, select the **Stake** tab. You are presented with two options: **Delegate** and **Self-Stake**. Choose **Delegate**. ![Stake tab showing delegate and self-stake options](/assets/images/stake-choice-53d27ec33d59daaf251aa053c0fc0083.png) With delegation, you pay a commission to a provider who runs a sequencer on your behalf. With self-stake, you run your own sequencer, pay no commission, and contribute directly to network decentralization. See the [Sequencer Setup Guide](/operate/operators/setup/sequencer_management.md) if you prefer self-staking. Transaction Queue The following steps add transactions to a queue — nothing is submitted to the chain until you reach [Step 9](#step-9-execute-batch). At that point, Gnosis Safe wallets execute everything as a single batched transaction, while EOA wallets submit each transaction one by one. ### Step 3: Choose a Provider[​](#step-3-choose-a-provider "Direct link to Step 3: Choose a Provider") Click **Choose Provider** and inspect the delegation table to find your preferred operator. Click on any provider to view details including their contact information, commission rate, and capacity. ![Delegation table showing available providers](/assets/images/delegation-table-f8bfca203d1f5d959a9748a93a48e3bb.png) Click delegate stake to continue. ![Delegation operator](/assets/images/delegation-info-0bf13e8a1f764608ac1f6768c8d263c1.png) Greyed-out Providers Some providers appear greyed out because they are not currently accepting delegations (usually because they have not registered enough sequencer keys). You can contact them directly or choose another provider. Avoid Centralization One of your responsibilities as a delegator is choosing good providers without overly centralizing the network. Avoid providers that already have very high staking concentration. ### Step 4: Select Token Vault and Amount[​](#step-4-select-token-vault-and-amount "Direct link to Step 4: Select Token Vault and Amount") Choose a Token Vault with at least 200,000 tokens available. Then select how much you want to delegate. Your delegation amount is capped by: * The provider's remaining capacity (they must have registered enough keys to run additional validators), or * Your available token balance whichever is lower. ![Token vault selection and delegation amount](/assets/images/delegation-token-vault-0af6d3d21681a91c332d1404711c95a1.png) note You cannot consolidate multiple Token Vaults into a single delegation. Each vault must be staked individually. ### Step 5: Set Operator Address[​](#step-5-set-operator-address "Direct link to Step 5: Set Operator Address") The operator address controls block submissions for this vault. Confirm the address is correct — this determines who manages sequencer operations and receives reward attribution. The staking dashboard defaults the operator to the connected wallet address. If you need a separate operator address for security separation, interact directly with the staking contracts via the CLI. Click **Add to Batch** to continue. One-time Action Setting the operator address is a one-time action per Token Vault. If the vault already has an operator configured, this step is skipped automatically. ### Step 6: Select Staking Version[​](#step-6-select-staking-version "Direct link to Step 6: Select Staking Version") Every Token Vault uses a **Staker Contract** that handles staking and unstaking operations. Governance may periodically approve new staker contract versions that add features (such as unstaking) or improve security. On the **Set Staker Version** screen, upgrade to **Latest** to stay current with governance-approved contracts, or select a specific older version. The dashboard describes each version's capabilities. Click **Add to Batch** to continue. ![Staking version selection screen](/assets/images/staker-version-3cf84aba729053c33ddcc05489811cf7.png) One-time Action Selecting the staking version is a one-time action per Token Vault. If the vault already has a staker version configured, this step is skipped automatically. ### Step 7: Approve Tokens[​](#step-7-approve-tokens "Direct link to Step 7: Approve Tokens") Approve the staker contract to move funds from your Token Vault. Each validator requires 200,000 tokens, so the approval amount matches your delegation. ### Step 8: Delegate[​](#step-8-delegate "Direct link to Step 8: Delegate") Review your delegation configuration and click **Delegate** / **Add to Batch**. ### Step 9: Execute Batch[​](#step-9-execute-batch "Direct link to Step 9: Execute Batch") Review the full set of queued transactions and click **Execute All**. ![Batch execution review screen](/assets/images/batch-execute-e6103c0101ee3e6c772807d124b273ae.png) Subsequent Delegations When you delegate again from a vault that already has an operator and staker version configured, Steps 5 and 6 are skipped — making future delegations faster. ## Managing Your Delegation[​](#managing-your-delegation "Direct link to Managing Your Delegation") ### Monitoring Performance[​](#monitoring-performance "Direct link to Monitoring Performance") Keep track of your delegated stake: * Check operator uptime and performance * Monitor for any slashing events * Review reward distributions ### Changing Operators[​](#changing-operators "Direct link to Changing Operators") If you want to switch to a different operator: 1. Initiate undelegation from your current operator 2. Wait for the unbonding period 3. Delegate to your new chosen operator ## Voting with Delegated Stake[​](#voting-with-delegated-stake "Direct link to Voting with Delegated Stake") By default, when you delegate to an operator, they may vote on your behalf in governance decisions. To maintain control over your votes: * Check if the operator allows custom voting preferences * Consider delegating voting power separately from stake * See [Voting on Proposals](/participate/token/voting.md) for voting options ## Next Steps[​](#next-steps "Direct link to Next Steps") * [Learn about voting](/participate/token/voting.md) with your staked or delegated tokens * [Staking Tokens](/participate/token/staking.md) to understand slashing risks * [Run your own validator](/operate/operators/setup/sequencer_management.md) if you prefer direct control --- # Economics & Rewards The Aztec network uses economic incentives to encourage honest participation and consistent operation. This page explains how rewards are distributed and what factors influence earnings. ## Reward Sources[​](#reward-sources "Direct link to Reward Sources") Network participants earn rewards from two sources: 1. **Checkpoint Rewards**: Protocol-funded rewards accruing for each proven slot 2. **Transaction Fees**: Fees paid by users for transaction processing ## Checkpoint Rewards[​](#checkpoint-rewards "Direct link to Checkpoint Rewards") Tokens are minted in advance to the RewardDistributor contract. The Rollup contract then claims from the RewardDistributor each slot and distributes them as checkpoint rewards — these are not net new inflation, but they are net new circulating tokens. The current checkpoint reward is **400 $AZTEC** per slot, split between sequencers and provers: | Recipient | Share | Amount Per Slot | | -------------- | ----- | --------------- | | **Sequencers** | 70% | 280 $AZTEC | | **Provers** | 30% | 120 $AZTEC | This value can be adjusted through governance. ### How Checkpoint Rewards Flow[​](#how-checkpoint-rewards-flow "Direct link to How Checkpoint Rewards Flow") ## Sequencer Rewards[​](#sequencer-rewards "Direct link to Sequencer Rewards") Sequencers earn rewards for successfully proposing and finalizing blocks: * **Checkpoint share**: 70% of each checkpoint reward (280 $AZTEC) goes to the block proposer, paid when the block is finalized on L1 * **Transaction fees**: Sequencers collect fees from users; a portion (the congestion cost) is burned, and 70% of the remainder is awarded to the sequencer ## Prover Rewards[​](#prover-rewards "Direct link to Prover Rewards") Provers earn rewards for generating validity proofs that finalize blocks: * **Checkpoint share**: 30% of each checkpoint reward (120 $AZTEC), distributed among provers who participated in the epoch * **Transaction fees**: Provers receive 30% of the unburnt transaction fees ### Activity Score and Reward Distribution[​](#activity-score-and-reward-distribution "Direct link to Activity Score and Reward Distribution") Prover checkpoint rewards are not split equally. They are distributed based on each prover's **activity score**, which measures consistency of participation. The score: * **Increases** by 125,000 per epoch of active proving * **Decreases** by 100,000 per epoch of inactivity * **Maximum**: 15,000,000 points A prover's share of the reward pool is determined by a quadratic penalty formula: ``` shares = k - (a × (maxScore - score)²) / 1e10 ``` Where `k = 1,000,000`, `a = 1,000`, and the minimum share is `100,000`. At maximum activity score, a prover receives the full `k` shares. As the score drops, the quadratic term reduces shares increasingly aggressively, meaning small drops have minimal impact but extended inactivity significantly reduces earnings. This design rewards long-term, consistent provers and discourages sporadic participation. *** Learn More * [Staking Tokens](/participate/token/staking.md) - How to stake and earn rewards * [Governance](/participate/governance.md) - How protocol parameters (including rewards) can change * [Running a Prover](/operate/operators/setup/running_a_prover.md) - Technical setup for provers --- # Staking Tokens Staking allows you to participate in securing the Aztec network while earning rewards. This guide explains how staking works and how to get started. ## Before You Stake[​](#before-you-stake "Direct link to Before You Stake") Understanding these concepts will help you make informed decisions: * [Blocks and Epochs](/participate/basics/blocks.md) - how block production works * [Economics & Rewards](/participate/token/economics.md) - how rewards are distributed ## Overview[​](#overview "Direct link to Overview") When you stake tokens on the Aztec network, your tokens are locked in a smart contract and used to secure the network. In return, you earn a share of the network rewards proportional to your stake. ### Key Concepts[​](#key-concepts "Direct link to Key Concepts") * **Activation Threshold**: The minimum amount required to become an active validator * **Staking Period**: Tokens must remain staked for a minimum period before withdrawal * **Rewards**: Earned based on your stake proportion and network activity * **Slashing Risk**: Validators who misbehave may have a portion of their stake slashed ## Staking Options[​](#staking-options "Direct link to Staking Options") ### Option 1: Run Your Own Validator[​](#option-1-run-your-own-validator "Direct link to Option 1: Run Your Own Validator") If you have the technical expertise and infrastructure, you can run your own sequencer node and stake directly. **Requirements:** * Meet the minimum stake threshold * Run and maintain sequencer infrastructure * Ensure high availability and proper operation See the [Sequencer Setup Guide](/operate/operators/setup/sequencer_management.md) for details. ### Option 2: Delegate to an Operator[​](#option-2-delegate-to-an-operator "Direct link to Option 2: Delegate to an Operator") If you don't want to run infrastructure, you can delegate your stake to a professional operator. See [Delegating Stake](/participate/token/delegation.md) for details. ## Understanding Slashing Risk[​](#understanding-slashing-risk "Direct link to Understanding Slashing Risk") Before staking, understand that your stake can be partially slashed if: * The validator you stake with (or delegate to) commits protocol violations * The validator is inactive for extended periods * The validator proposes or attests to invalid blocks Slashing is managed through governance voting based on evidence collected both onchain and offchain. ## Unstaking[​](#unstaking "Direct link to Unstaking") When you want to withdraw your staked tokens, you must go through an unstaking process with mandatory delays. ### Exit Delays[​](#exit-delays "Direct link to Exit Delays") | Delay Type | Alpha (Mainnet) | Testnet | | ------------------------------- | --------------- | ---------- | | **Staking Exit Delay** | 4 days | 2 days | | **Governance Withdrawal Delay** | \~38 days | \~1.6 days | The **staking exit delay** is the minimum time after initiating withdrawal before you can claim your tokens. It allows time for pending slashing conditions to be detected. If your tokens are deposited in the Governance Staking Escrow (GSE) for voting, the **governance withdrawal delay** also applies. This delay is calculated as `votingDelay/5 + votingDuration + executionDelay` to ensure voted-on proposals can be executed before voters exit. On mainnet this is approximately 38 days (0.6 + 7 + 30 days). ### How to Unstake[​](#how-to-unstake "Direct link to How to Unstake") To unstake your tokens, use the [Aztec Staking Dashboard](https://stake.aztec.network/). The dashboard guides you through the unstaking process: 1. **Initiate withdrawal**: Select your validator and begin the exit process 2. **Wait for the exit delay**: Your tokens remain locked during this period (if your tokens are also in the Governance Staking Escrow, the longer governance withdrawal delay applies) 3. **Finalize withdrawal**: After the delay, complete the withdrawal to receive your tokens If you've delegated stake, contact your operator or use the delegation interface to request unstaking. ### Important Considerations[​](#important-considerations "Direct link to Important Considerations") * **Slashing Risk**: You can still be slashed during the exit delay if misbehavior is detected from when you were active * **No Rewards During Exit**: You do not earn staking rewards during the exit delay period ## Next Steps[​](#next-steps "Direct link to Next Steps") * [Delegate your stake](/participate/token/delegation.md) if you prefer not to run infrastructure * [Learn about voting](/participate/token/voting.md) to participate in governance with your staked tokens * [Understand governance](/participate/governance.md) to know how protocol decisions are made --- # Voting on Proposals As a token holder, you can vote on governance proposals that shape the future of the Aztec protocol. You don't need to be a staker to participate in governance - you can lock tokens directly for voting power. Conceptual Background Understanding these concepts will help you participate effectively: * [How governance works](/participate/governance.md) - Overview of the governance system * [Proposal lifecycle](/participate/governance/proposal-lifecycle.md) - The stages from signaling to execution * [Voting mechanics](/participate/governance/voting.md) - How voting power and timestamps work ## Two Paths to Voting Power[​](#two-paths-to-voting-power "Direct link to Two Paths to Voting Power") There are two ways to acquire voting power on the Aztec network: ### Path 1: Through Staking (Default)[​](#path-1-through-staking-default "Direct link to Path 1: Through Staking (Default)") If you've staked tokens as a sequencer or delegated to one, you automatically have voting power. Your voting power is delegated to the rollup contract by default, which votes "yea" on proposals that reached quorum through sequencer signaling. #### How Default Voting Works for Stakers[​](#how-default-voting-works-for-stakers "Direct link to How Default Voting Works for Stakers") When you stake tokens (either by running a sequencer or delegating to one): 1. **Your tokens are held in the GSE** (Governance Staking Escrow) contract 2. **Voting power is automatically delegated** to the rollup contract 3. **The rollup votes on your behalf** - it votes "yea" on any proposal that passed the sequencer signaling phase 4. **You earn staking rewards** while participating in governance This means **most stakers don't need to do anything** to participate in governance. The system is designed so that proposals with broad sequencer support automatically pass, while controversial proposals require active community engagement. #### When to Take Action as a Staker[​](#when-to-take-action-as-a-staker "Direct link to When to Take Action as a Staker") You should consider taking manual action if: * You **disagree** with a proposal that passed sequencer signaling * You want to vote "nay" on a specific proposal * You want more control over how your voting power is used ### Path 2: Direct Governance Participation (Non-Stakers)[​](#path-2-direct-governance-participation-non-stakers "Direct link to Path 2: Direct Governance Participation (Non-Stakers)") If you want governance participation without staking, you can lock tokens directly in the Governance contract. This is useful for token holders who don't want to run infrastructure or delegate, and want to vote without slashing risk. To lock tokens for voting, visit the [Governance section of the Staking Dashboard](https://stake.aztec.network/governance). Connect your wallet, choose the amount to lock, and confirm the transaction. After depositing, your voting power will be active for any proposals that enter the voting phase after your deposit. Note that locked governance tokens do not earn staking rewards and are subject to a withdrawal delay (\~1.6 days on testnet, \~38 days on mainnet). ## How Voting Works[​](#how-voting-works "Direct link to How Voting Works") ### Voting Power[​](#voting-power "Direct link to Voting Power") Your voting power is determined by the amount of tokens you have locked in the Governance contract. Key points: * **Locking Required**: You must lock tokens in the Governance contract to activate voting power * **No Slashing on Votes**: Locked voting tokens are not subject to slashing (unlike staked tokens) * **Withdrawal Delay**: After voting, there's a delay before you can withdraw tokens to prevent governance attacks (\~1.6 days on testnet, \~38 days on mainnet) ### Voting Timeline[​](#voting-timeline "Direct link to Voting Timeline") Each proposal goes through these stages: 1. **Signaling** - Sequencers signal support for a payload 2. **Proposal Creation** - Once quorum is reached, the proposal is submitted 3. **Voting Delay** (\~12 hours) - Mandatory waiting period for community review 4. **Voting Period** (\~24 hours) - Token holders vote on the proposal 5. **Execution Delay** (\~12 hours) - Delay before approved proposals execute 6. **Execution** - Anyone can trigger execution of passed proposals Testnet Values These timeline values are specific to testnet and may change for future network phases. ## Finding Active Proposals[​](#finding-active-proposals "Direct link to Finding Active Proposals") To see what proposals are currently up for vote: 1. **[Aztec Discord](https://discord.gg/aztec)**: Join the governance channels for proposal discussions and announcements 2. **[Aztec Forum](https://forum.aztec.network/)**: In-depth discussions about proposed changes 3. **Query the Governance contract**: Check proposal state directly on L1 4. **Etherscan**: View proposal transactions and payload contracts ## Best Practices[​](#best-practices "Direct link to Best Practices") 1. **Research Before Voting**: Always review proposal details and community discussions 2. **Delegate Early**: Complete delegation well before voting opens 3. **Verify Payloads**: For technical proposals, review the payload code on Etherscan 4. **Stay Informed**: Follow governance discussions to understand proposal implications ## Next Steps[​](#next-steps "Direct link to Next Steps") * [Learn about staking](/participate/token/staking.md) to acquire voting power through staking * [Learn about unstaking](/participate/token/staking.md#unstaking) to understand withdrawal delays * [Understand governance concepts](/participate/governance.md) for deeper knowledge * [Become a sequencer](/operate/operators/setup/sequencer_management.md) to participate in proposal signaling --- # Aztec Connect Sunset Deprecated Aztec Connect is no longer being actively developed. The rollup instance operated by Aztec stopped accepting deposits on March 21st, 2023. Read the full announcement [here](https://medium.com/aztec-protocol/sunsetting-aztec-connect-a786edce5cae). We will continue to process transactions and withdrawals for funds that are already in the rollup until March 31st, 2024, at which point we will stop running the sequencer. Users should withdraw funds immediately. See the [zk.money](#zkmoney) section below for details on how to withdraw funds. ## Run your own AC[​](#run-your-own-ac "Direct link to Run your own AC") All of the infrastructure and associated code required to run and interact with the Aztec Connect rollup is open source, so anyone can publish blocks after we stop, or run their own instance of the rollup software. You can find the old documentation site that includes all of the pertinent information on the [`aztec-connect` branch](https://github.com/AztecProtocol/docs/tree/aztec-connect) of the docs repository. The code has been open sourced and you can find the relevant repositories linked below. ### Source Code[​](#source-code "Direct link to Source Code") Follow the links for more information about each package. * [Running the rollup service](https://github.com/AztecProtocol/aztec-connect/blob/master/yarn-project/README.md) * [Sequencer](https://github.com/AztecProtocol/aztec-connect/tree/master/yarn-project/falafel) * [Contracts](https://github.com/AztecProtocol/aztec-connect/tree/master/contracts) * [SDK](https://github.com/AztecProtocol/aztec-connect/tree/master/yarn-project/sdk) * [Block Explorer](https://github.com/AztecProtocol/aztec-connect-explorer) * [Alpha SDK](https://github.com/AztecProtocol/aztec-connect/tree/master/yarn-project/alpha-sdk) * [Wallet UI](https://github.com/AztecProtocol/wallet-ui) ## Zk.money[​](#zkmoney "Direct link to Zk.money") ### Exiting Defi Positions[​](#exiting-defi-positions "Direct link to Exiting Defi Positions") 1. Navigate to your zk.money homepage and click “Wallet”. 2. Scroll down to “Tokens” and “Earn Positions”. 3. Click “Earn Positions”. 4. Click “Claim & Exit” on the position you wish to exit. ![](/assets/ideal-img/defiexit1.31560b8.640.png) 5. All exit transactions are free in “Batched Mode” proceed to step 6 to get a free transaction. 6. Click “Max” to exit the full amount, and then select a speed for your transaction. ![](/assets/ideal-img/defiexit2.988021f.640.png) 7. Once you have done so, click “Next”. 8. Review the amount you will receive is correct, tick the disclaimer, and click “Confirm Transaction”. ![](/assets/ideal-img/defiexit3.a8e7f3f.640.png) 9. After clicking confirm transaction, sign the signature request using your connected wallet (e.g. Metamask in this example). ![](/assets/ideal-img/defiexit4.10abe40.640.png) 10. Wait until your transaction is confirmed. ![](/assets/ideal-img/defiexit5.4a604d5.640.png) 11. Navigate back to your wallet homepage and click “Earn Positions”. 12. The status of your exit will be displayed here, as shown by “Exiting” (1 tick). ![](/assets/ideal-img/defiexit6.0cb5e46.640.png) 13. To the left, click the transaction hash icon to be taken to the block explorer page to see the transaction status. ![](/assets/ideal-img/defiexit7.38d76d5.640.png) 14. Your funds will appear in your dashboard once the transaction has settled. ### Exiting LUSD Borrowing[​](#exiting-lusd-borrowing "Direct link to Exiting LUSD Borrowing") Your LUSD debt is repaid using a flash loan. Part of your ETH collateral then repays the flash loan, and the remaining ETH is returned to your account. Your total TB-275 tokens represents the entirety of your share of the collateral. Spending all your TB-275 will release your entire share of the collateral (minus the market value of the debt to be repaid). Liquity: 1. Navigate to your zk.money homepage and click “Wallet”. 2. Scroll down to “Tokens” and “Earn Positions”. 3. Click “Earn Positions”. 4. On your Liquity Trove position, click “Repay & Exit”. ![](/assets/ideal-img/lusdexit1.f85eaa2.640.png) 5. Click “Max” to exit the full amount, then select a speed for your transaction. ![](/assets/ideal-img/lusdexit2.16fd4fe.640.png) 6. Once you have done so, click “Next”. 7. Review the amount you will receive is correct, tick the disclaimer, and click “Confirm Transaction”. ![](/assets/ideal-img/lusdexit3.1783b50.640.png) 8. After clicking confirm transaction, sign the signature request using your connected wallet (e.g. Metamask). 9. Wait until your transaction is confirmed. 10. Navigate to your zk.money wallet homepage and click “Earn Positions”. 11. The status of your exit will be displayed here, as shown by “Exiting” (1 tick). ![](/assets/ideal-img/lusdexit4.2176172.640.png) 12. Click the transaction hash icon to be taken to the block explorer page to see the transaction status. ![](/assets/ideal-img/lusdexit5.075f855.640.png) 13. Your funds will appear in your dashboard once the transaction has settled. ### Withdrawing Assets[​](#withdrawing-assets "Direct link to Withdrawing Assets") How to withdraw ETH, DAI and LUSD. 1. Navigate to your zk.money homepage and click “Wallet”. 2. Scroll down to “Tokens” and “Earn Positions”. 3. Click “Tokens”. 4. Click “Exit” on the desired token you would like to withdraw. ![](/assets/ideal-img/withdraw1.06ca79c.640.png) 5. Click “Withdraw to L1”. ![](/assets/ideal-img/withdraw2.b3ac13e.640.png) 6. Enter your recipient address. 7. Click “Max” to withdraw the full amount. 8. Select a speed for your transaction (transactions are free in “Batched Mode”). 9. Click “Next”. 10. Review the amount you are withdrawing is correct, tick the disclaimer, and click “Confirm Transaction”. ![](/assets/ideal-img/withdraw3.a3638fd.640.png) 11. Sign the signature request using your connected wallet (e.g. Metamask). 12. Wait until your transaction is confirmed. ![](/assets/ideal-img/withdraw4.8df91b6.640.png) 13. Navigate back to your wallet homepage, under Transaction History. Click the transaction hash to check the status of your transaction on the block explorer. ![](/assets/ideal-img/withdraw5.4338567.640.png) 14. Your funds will appear in your recipient wallet once the transaction has settled. --- # Aztec networks overview The Aztec Protocol operates across multiple networks, each serving specific purposes and audiences. This page gives builders and node operators the technical details to connect to each network: live version, RPC and bootnode endpoints, contract addresses, and governance parameters. Not sure which network or version to pin against? Jump to the [Network selection guide](#network-selection-guide). For release channels and what is coming next, see [Versions and releases](#versions-and-releases). ## Network technical information[​](#network-technical-information "Direct link to Network technical information") | Parameter | Alpha (Mainnet) | Testnet | | ------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | | **Version** | `4.3.1` | `5.0.0-rc.2` | | **L1 Chain ID** | `1` (Mainnet) | `11155111` (Sepolia) | | **Rollup Version** | `2934756905` | `2787991301` | | **RPC Endpoint** | `https://aztec-mainnet.drpc.org` | `https://v5.testnet.rpc.aztec-labs.com` | | **Bootnodes** | | | | **Block Explorer** | [Aztecscan](https://aztecscan.xyz), [Aztecexplorer](https://aztecexplorer.xyz/?network=mainnet) | [Aztecscan](https://testnet.aztecscan.xyz), [Aztecexplorer](https://aztecexplorer.xyz/?network=testnet) | | **Getting Started** | [Run a sequencer →](/operate/operators/setup/sequencer_management.md) | [Run a node →](/operate/operators/setup/running_a_node.md) | Network roles (post-Alpha) **Testnet is your production path.** It's decentralized, live, and stable: treat it as your staging environment for Alpha. If you want to deploy on Alpha, validate on Testnet first. ## Versions and releases[​](#versions-and-releases "Direct link to Versions and releases") Aztec is a monorepo. Each release publishes a single version that covers the node, [Aztec.nr](https://aztec.network/aztecnr), and [aztec.js](https://aztec.network/aztecjs) together, so a network on `4.2.0` runs the `4.2.0` node, contracts compiled with the `4.2.0` Aztec.nr, and clients built against the `4.2.0` aztec.js. The **Version** row in the table above is the build a given network is currently running. ### Release channels[​](#release-channels "Direct link to Release channels") Aztec publishes three kinds of builds, each with a different stability promise. | Channel | Example | What it is | Recommended audience | | -------------------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | **Stable** | `4.2.0`, `4.3.0` | The validated, final version for a release cycle. | Builders shipping to users. Operators running Alpha or Testnet. | | **Release candidate (RC)** | `4.3.0-rc.1`, `4.3.0-rc.2` | Pre-release of an upcoming stable, used for internal validation and Testnet rehearsals. Additional RCs ship if issues are found. | Operators participating in pre-release rehearsals. Builders verifying compatibility ahead of a stable cut. | | **Nightly** | `4.3.0-nightly.` | Latest in-progress work from the development branch. Experimental, less tested. | Builders previewing upcoming features. Not recommended for production. | An RC is not newer than its matching stable: `4.3.0-rc.1` is a checkpoint on the way to `4.3.0`, and `4.3.0` supersedes every `4.3.0-rc.*`. Release notes for each version are generated from the commit range since the previous release and published on the [GitHub releases page](https://github.com/AztecProtocol/aztec-packages/releases). The Git history is the source of truth for what changed between two versions. ### Cadence[​](#cadence "Direct link to Cadence") Stable releases target roughly one per month, typically mid-month. Dates are not strictly fixed; the cadence is intended to be regular rather than ad hoc. ## Contract addresses[​](#contract-addresses "Direct link to Contract addresses") ### L1 contract addresses[​](#l1-contract-addresses "Direct link to L1 contract addresses") | Contract Name | Alpha (Mainnet) | Testnet | | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | **Registry** | [`0x35b22e09ee0390539439e24f06da43d83f90e298`](https://etherscan.io/address/0x35b22e09ee0390539439e24f06da43d83f90e298) | [`0xa0bfb1b494fb49041e5c6e8c2c1be09cd171c6ba`](https://sepolia.etherscan.io/address/0xa0bfb1b494fb49041e5c6e8c2c1be09cd171c6ba) | | **Rollup** | [`0xae2001f7e21d5ecabf6234e9fdd1e76f50f74962`](https://etherscan.io/address/0xae2001f7e21d5ecabf6234e9fdd1e76f50f74962) | [`0xfe6061806cac748085904a010d2d9e33b8031741`](https://sepolia.etherscan.io/address/0xfe6061806cac748085904a010d2d9e33b8031741) | | **L1 → L2 Inbox** | [`0x8dbf0b6ed495baab6062f5d5365af3c1b2ed4578`](https://etherscan.io/address/0x8dbf0b6ed495baab6062f5d5365af3c1b2ed4578) | [`0x917bb0538c680b71dacc90f0c9cee37ed3b18541`](https://sepolia.etherscan.io/address/0x917bb0538c680b71dacc90f0c9cee37ed3b18541) | | **L2 → L1 Outbox** | [`0xc9698b7adef9ee63f3bf5cff38086e4e836579f0`](https://etherscan.io/address/0xc9698b7adef9ee63f3bf5cff38086e4e836579f0) | [`0xbd9513e770b7b0b98b65ecdd79db093dab1f9b66`](https://sepolia.etherscan.io/address/0xbd9513e770b7b0b98b65ecdd79db093dab1f9b66) | | **Fee Juice** | [`0xa27ec0006e59f245217ff08cd52a7e8b169e62d2`](https://etherscan.io/address/0xa27ec0006e59f245217ff08cd52a7e8b169e62d2) | [`0x762c132040fda6183066fa3b14d985ee55aa3c18`](https://sepolia.etherscan.io/address/0x762c132040fda6183066fa3b14d985ee55aa3c18) | | **Staking Asset** | [`0xa27ec0006e59f245217ff08cd52a7e8b169e62d2`](https://etherscan.io/address/0xa27ec0006e59f245217ff08cd52a7e8b169e62d2) | [`0x5595cb9ed193cac2c0bc5393313bc6115817954b`](https://sepolia.etherscan.io/address/0x5595cb9ed193cac2c0bc5393313bc6115817954b) | | **Fee Juice Portal** | [`0x2891f8b941067f8b5a3f34545a30cf71e3e23617`](https://etherscan.io/address/0x2891f8b941067f8b5a3f34545a30cf71e3e23617) | [`0xb06ac8156af9c4b369a7ae3e11708baaa1990a3a`](https://sepolia.etherscan.io/address/0xb06ac8156af9c4b369a7ae3e11708baaa1990a3a) | | **Fee Asset Handler** | N/A | [`0x5602c39a6e9c5ace589f64f754927bcda4f4bfc9`](https://sepolia.etherscan.io/address/0x5602c39a6e9c5ace589f64f754927bcda4f4bfc9) | | **Coin Issuer** | [`0x02fadf157d551aa6d761b2a2237d03af68e41ca6`](https://etherscan.io/address/0x02fadf157d551aa6d761b2a2237d03af68e41ca6) | [`0xe05d0a62045b4237556c1ec423e59eea9a24eaee`](https://sepolia.etherscan.io/address/0xe05d0a62045b4237556c1ec423e59eea9a24eaee) | | **Reward Distributor** | [`0x3d6a1b00c830c5f278fc5dfb3f6ff0b74db6dfe0`](https://etherscan.io/address/0x3d6a1b00c830c5f278fc5dfb3f6ff0b74db6dfe0) | [`0x83b2a93ef343cab7be9d8bba7317f314975e5cb0`](https://sepolia.etherscan.io/address/0x83b2a93ef343cab7be9d8bba7317f314975e5cb0) | | **Reward Booster** | [`0x1cbb707bd7b4fd2bced6d96d84372fb428e93d80`](https://etherscan.io/address/0x1cbb707bd7b4fd2bced6d96d84372fb428e93d80) | [`0x178eA27Ee73aE45cB4bA051FC1c4754C0F93B15a`](https://sepolia.etherscan.io/address/0x178eA27Ee73aE45cB4bA051FC1c4754C0F93B15a) | | **Governance Proposer** | [`0x06ef1dcf87e419c48b94a331b252819fadbd63ef`](https://etherscan.io/address/0x06ef1dcf87e419c48b94a331b252819fadbd63ef) | [`0x01c7d4ca153748d2377968fef22894cb162e9480`](https://sepolia.etherscan.io/address/0x01c7d4ca153748d2377968fef22894cb162e9480) | | **Governance** | [`0x1102471eb3378fee427121c9efcea452e4b6b75e`](https://etherscan.io/address/0x1102471eb3378fee427121c9efcea452e4b6b75e) | [`0xcaf7447721447b22cd0076ac7c63877c3afd329f`](https://sepolia.etherscan.io/address/0xcaf7447721447b22cd0076ac7c63877c3afd329f) | | **Governance Staking Escrow** | [`0xa92ecfd0e70c9cd5e5cd76c50af0f7da93567a4f`](https://etherscan.io/address/0xa92ecfd0e70c9cd5e5cd76c50af0f7da93567a4f) | [`0xb6a38a51a6c1de9012f9d8ea9745ef957212eaac`](https://sepolia.etherscan.io/address/0xb6a38a51a6c1de9012f9d8ea9745ef957212eaac) | | **Staking Registry** | [`0x042dF8f42790d6943F41C25C2132400fd727f452`](https://etherscan.io/address/0x042dF8f42790d6943F41C25C2132400fd727f452) | [`0xC6EcC1832c8BF6a41c927BEb4E9ec610FBeDd1C2`](https://sepolia.etherscan.io/address/0xC6EcC1832c8BF6a41c927BEb4E9ec610FBeDd1C2) | | **Slash Factory** | N/A | [`0x9CF4a0094c8696d5110dd0f0cF3FA5deA174BB17`](https://sepolia.etherscan.io/address/0x9CF4a0094c8696d5110dd0f0cF3FA5deA174BB17) | | **Slasher** | [`0x64E6e9Bb9f1E33D319578B9f8a9C719Ca6D46eBb`](https://etherscan.io/address/0x64E6e9Bb9f1E33D319578B9f8a9C719Ca6D46eBb) | [`0x01DbE910c31940986B9e074974Fbbb396F8EF540`](https://sepolia.etherscan.io/address/0x01DbE910c31940986B9e074974Fbbb396F8EF540) | | **Tally Slashing Proposer** | [`0xa4a38fD0108C00983E75616b638Ff3321FD26958`](https://etherscan.io/address/0xa4a38fD0108C00983E75616b638Ff3321FD26958) | [`0xf5Aa08D6F331Fed2a0F09229568D9A391D02d387`](https://sepolia.etherscan.io/address/0xf5Aa08D6F331Fed2a0F09229568D9A391D02d387) | | **Honk Verifier** | [`0x70aedda427f26480d240bc0f4308cedec8d31348`](https://etherscan.io/address/0x70aedda427f26480d240bc0f4308cedec8d31348) | [`0xBF54Bc748F5213164e02A6C000CB2a8585cD70cb`](https://sepolia.etherscan.io/address/0xBF54Bc748F5213164e02A6C000CB2a8585cD70cb) | | **Register New Rollup Version Payload** | N/A | N/A | | **Slash Payload Cloneable** | [`0xAA43220b7eb7c8Ffe75bc9C483f3C07b0a55B445`](https://etherscan.io/address/0xAA43220b7eb7c8Ffe75bc9C483f3C07b0a55B445) | [`0xa1A0D73F6803A277F841cfDd0Ad358cE13Cd3A84`](https://sepolia.etherscan.io/address/0xa1A0D73F6803A277F841cfDd0Ad358cE13Cd3A84) | ### L2 contract addresses[​](#l2-contract-addresses "Direct link to L2 contract addresses") | Contract Name | Alpha (Mainnet) | Testnet | | ------------------------ | -------------------------------------------------------------------- | -------------------------------------------------------------------- | | **Instance Registry** | `0x0000000000000000000000000000000000000000000000000000000000000002` | `0x0000000000000000000000000000000000000000000000000000000000000002` | | **Class Registry** | `0x0000000000000000000000000000000000000000000000000000000000000003` | `0x0000000000000000000000000000000000000000000000000000000000000001` | | **MultiCall Entrypoint** | `0x0000000000000000000000000000000000000000000000000000000000000004` | `0x2d1803ae8e30d5fa993a7624231b5ddcf4133ff7475b80a0fba782404b5a09c1` | | **Fee Juice** | `0x0000000000000000000000000000000000000000000000000000000000000005` | `0x0000000000000000000000000000000000000000000000000000000000000003` | | **SponsoredFPC** | Not deployed | `0x1969946536f0c09269e2c75e414eef4e21a76e763c5514125208db33d7d944d7` | ## Governance parameters[​](#governance-parameters "Direct link to Governance parameters") | Parameter | Alpha (Mainnet) | Testnet | | ----------------------- | --------------- | --------- | | **Proposer Quorum** | 600/1000 | 60/100 | | **Voting Delay** | 3 days | 12 hours | | **Voting Duration** | 7 days | 24 hours | | **Execution Delay** | 30 days | 12 hours | | **Slashing Quorum** | 65% | 33% | | **Slashing Round Size** | 128 epochs | 64 epochs | *** ## Network selection guide[​](#network-selection-guide "Direct link to Network selection guide") ### Alpha (Mainnet)[​](#alpha-mainnet "Direct link to Alpha (Mainnet)") Alpha is the Aztec **mainnet** in its initial operational phase, with governance, networking, and transaction processing fully active. Alpha is live but early, so bugs (including critical ones) are expected. For a full explanation of what this means, see the **[Alpha Network](/participate/alpha.md)** page. #### Overview[​](#overview "Direct link to Overview") Alpha is connected to Ethereum mainnet and supports user transactions. Governance and staking infrastructure are fully operational. This network requires real stakes for sequencer participation. **Target users:** * Sequencers who want to contribute to the decentralized Aztec Network * Governance participants * Developers deploying production applications * Infrastructure operators **Key features:** * Governance system fully operational * Staking required for sequencer participation * Connected to Ethereum Mainnet * User transactions supported *** ### Testnet[​](#testnet "Direct link to Testnet") Testnet is the production path for Aztec. It operates as a fully decentralized network with multiple sequencers and closely mirrors Alpha conditions. If you plan to deploy on Alpha, Testnet is where you validate your application. Think of it as your staging environment for the real thing. #### Overview[​](#overview-1 "Direct link to Overview") Testnet is ideal for testing node configurations, governance proposals, and understanding network dynamics without real financial risk. **Target users:** * Future Alpha sequencer operators testing configurations * Developers requiring production-like testing conditions * Governance participants practicing proposal workflows * Infrastructure operators validating monitoring setups **Key features:** * Fully decentralized sequencer set * Connected to Ethereum Sepolia * Transactions are proven * Sponsored FPC available for free transactions * Good environment for testing node operations ### Choosing a version[​](#choosing-a-version "Direct link to Choosing a version") Once you have picked a network, choose a release channel that matches your role: * **Building on Aztec.** Pin Aztec.nr and aztec.js to the stable version that matches the network you are targeting (see the **Version** row in the [Network technical information](#network-technical-information) table). Validate on Testnet before deploying to Alpha. Use nightlies only when you need an unreleased feature, and expect breakage. * **Running a node or sequencer.** Run the stable version listed for your network. Switch to an RC only when an upcoming-release rehearsal is announced on Testnet. * **Tracking what is coming.** Watch [Releases](https://github.com/AztecProtocol/aztec-packages/releases) for the current stable, any RCs in flight, and nightly tags. A public release calendar is on the roadmap; until then, the releases page is the authoritative timeline. ## Next steps[​](#next-steps "Direct link to Next steps") Based on your use case: * **Building an application?** Start with [Getting started](/developers/getting_started_on_local_network.md). * **Running infrastructure?** Review the [Network operator guide](/operate/operators.md). * **Joining as a sequencer?** See [Sequencer management](/operate/operators/setup/sequencer_management.md). * **Tracking releases?** See [Versions and releases](#versions-and-releases) above and the [GitHub releases page](https://github.com/AztecProtocol/aztec-packages/releases). --- --- ## API Reference Documentation ## Aztec.nr API Reference Auto-generated API documentation for Aztec.nr (v4.3.1) ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/authwit/account/global.AccountFeePaymentMethodOptions.html # Global AccountFeePaymentMethodOptions ``` pub global AccountFeePaymentMethodOptions: [AccountFeePaymentMethodOptionsEnum](../../../noir_aztec/authwit/account/struct.AccountFeePaymentMethodOptionsEnum.html); ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/authwit/account/index.html # Module account ## Structs - [AccountActions](struct.AccountActions.html) - [AccountFeePaymentMethodOptionsEnum](struct.AccountFeePaymentMethodOptionsEnum.html) ## Globals - [AccountFeePaymentMethodOptions](global.AccountFeePaymentMethodOptions.html) --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/authwit/account/struct.AccountActions.html # Struct AccountActions ``` pub struct AccountActions { /* private fields */ } ``` ## Implementations ### `impl [AccountActions](../../../noir_aztec/authwit/account/struct.AccountActions.html)` `pub fn [init](#init)( context: Context, is_valid_impl: fn(&mut [PrivateContext](../../../noir_aztec/context/struct.PrivateContext.html), [Field](../../../std/primitive.Field.html)) -> [bool](../../../std/primitive.bool.html), ) -> Self` ### `impl [AccountActions](../../../noir_aztec/authwit/account/struct.AccountActions.html)<&mut [PrivateContext](../../../noir_aztec/context/struct.PrivateContext.html)>` `pub fn [entrypoint](#entrypoint)( self, app_payload: [AppPayload](../../../noir_aztec/authwit/entrypoint/app/struct.AppPayload.html), fee_payment_method: [u8](../../../std/primitive.u8.html), cancellable: [bool](../../../std/primitive.bool.html), )` Verifies that the `app_hash` is authorized and executes the `app_payload`. @param app_payload The payload that contains the calls to be executed in the app phase. @param fee_payment_method The mechanism via which the account contract will pay for the transaction: - EXTERNAL (0): Signals that some other contract is in charge of paying the fee, nothing needs to be done. - PREEXISTING_FEE_JUICE (1): Makes the account contract publicly pay for the transaction with its own FeeJuice balance, which it must already have prior to this transaction. The contract will set itself as the fee payer and end the setup phase. - FEE_JUICE_WITH_CLAIM (2): Makes the account contract publicly pay for the transaction with its own FeeJuice balance which is being claimed in the same transaction. The contract will set itself as the fee payer but not end setup phase - this is done by the FeeJuice contract after enqueuing a public call, which unlike most public calls is whitelisted to be executable during setup. @param cancellable Controls whether to emit app_payload.tx_nonce as a nullifier, allowing a subsequent transaction to be sent with a higher priority fee. This can be used to cancel the first transaction sent, assuming it hasn't been mined yet. `pub fn [verify_private_authwit](#verify_private_authwit)(self, inner_hash: [Field](../../../std/primitive.Field.html)) -> [Field](../../../std/primitive.Field.html)` Verifies that the `msg_sender` is authorized to consume `inner_hash` by the account. Computes the `message_hash` using the `msg_sender`, `chain_id`, `version` and `inner_hash`. Then executes the `is_valid_impl` function to verify that the message is authorized. Will revert if the message is not authorized. @param inner_hash The hash of the message that the `msg_sender` is trying to consume. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/authwit/account/struct.AccountFeePaymentMethodOptionsEnum.html # Struct AccountFeePaymentMethodOptionsEnum ``` pub struct AccountFeePaymentMethodOptionsEnum { pub EXTERNAL: [u8](../../../std/primitive.u8.html), pub PREEXISTING_FEE_JUICE: [u8](../../../std/primitive.u8.html), pub FEE_JUICE_WITH_CLAIM: [u8](../../../std/primitive.u8.html), } ``` ## Fields `EXTERNAL: [u8](../../../std/primitive.u8.html)` `PREEXISTING_FEE_JUICE: [u8](../../../std/primitive.u8.html)` `FEE_JUICE_WITH_CLAIM: [u8](../../../std/primitive.u8.html)` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/authwit/auth/fn.assert_current_call_valid_authwit.html # Function assert_current_call_valid_authwit ``` pub fn assert_current_call_valid_authwit( context: &mut [PrivateContext](../../../noir_aztec/context/struct.PrivateContext.html), on_behalf_of: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), ) ``` Assert that `on_behalf_of` has authorized the current call with a valid authentication witness Compute the `inner_hash` using the `msg_sender`, `selector` and `args_hash` and then make a call out to the `on_behalf_of` contract to verify that the `inner_hash` is valid. Additionally, this function emits the identifying information of the call as an offchain effect so PXE can rely the information to the user/wallet in a readable way. To that effect, it is generic over N, where N is the number of arguments the authorized functions takes. This is used to load the arguments from the execution cache. This function is intended to be called via a macro, which will use the turbofish operator to specify the number of arguments. @param on_behalf_of The address that has allegedly authorized the current call --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/authwit/auth/fn.assert_current_call_valid_authwit_public.html # Function assert_current_call_valid_authwit_public ``` pub unconstrained fn assert_current_call_valid_authwit_public( context: [PublicContext](../../../noir_aztec/context/struct.PublicContext.html), on_behalf_of: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), ) ``` Assert that `on_behalf_of` has authorized the current call in the authentication registry Compute the `inner_hash` using the `msg_sender`, `selector` and `args_hash` and then make a call out to the `on_behalf_of` contract to verify that the `inner_hash` is valid. Note that the authentication registry will take the `msg_sender` into account as the consumer, so this will only work if the `msg_sender` is the same as the `consumer` when the `message_hash` was inserted into the registry. @param on_behalf_of The address that has allegedly authorized the current call --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/authwit/auth/fn.assert_inner_hash_valid_authwit.html # Function assert_inner_hash_valid_authwit ``` pub fn assert_inner_hash_valid_authwit( context: &mut [PrivateContext](../../../noir_aztec/context/struct.PrivateContext.html), on_behalf_of: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), inner_hash: [Field](../../../std/primitive.Field.html), ) ``` Assert that a specific `inner_hash` is valid for the `on_behalf_of` address Used as an internal function for `assert_current_call_valid_authwit` and can be used as a standalone function when the `inner_hash` is from a different source, e.g., say a block of text etc. @param on_behalf_of The address that has allegedly authorized the current call @param inner_hash The hash of the message to authorize --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/authwit/auth/fn.assert_inner_hash_valid_authwit_public.html # Function assert_inner_hash_valid_authwit_public ``` pub unconstrained fn assert_inner_hash_valid_authwit_public( context: [PublicContext](../../../noir_aztec/context/struct.PublicContext.html), on_behalf_of: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), inner_hash: [Field](../../../std/primitive.Field.html), ) ``` Assert that `on_behalf_of` has authorized a specific `inner_hash` in the authentication registry Compute the `inner_hash` using the `msg_sender`, `selector` and `args_hash` and then make a call out to the `on_behalf_of` contract to verify that the `inner_hash` is valid. Note that the authentication registry will take the `msg_sender` into account as the consumer, so this will only work if the `msg_sender` is the same as the `consumer` when the `message_hash` was inserted into the registry. @param on_behalf_of The address that has allegedly authorized the `inner_hash` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/authwit/auth/fn.compute_authwit_message_hash.html # Function compute_authwit_message_hash ``` pub fn compute_authwit_message_hash( consumer: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), chain_id: [Field](../../../std/primitive.Field.html), version: [Field](../../../std/primitive.Field.html), inner_hash: [Field](../../../std/primitive.Field.html), ) -> [Field](../../../std/primitive.Field.html) ``` Computes the `message_hash` for the authentication witness @param consumer The address of the contract that is consuming the message @param chain_id The chain id of the chain that the message is being consumed on @param version The version of the chain that the message is being consumed on @param inner_hash The hash of the "inner" message that is being consumed --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/authwit/auth/fn.compute_authwit_message_hash_from_call.html # Function compute_authwit_message_hash_from_call ``` pub fn compute_authwit_message_hash_from_call( caller: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), consumer: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), chain_id: [Field](../../../std/primitive.Field.html), version: [Field](../../../std/primitive.Field.html), selector: [FunctionSelector](../../../protocol_types/abis/function_selector/struct.FunctionSelector.html), args: [[Field](../../../std/primitive.Field.html); N], ) -> [Field](../../../std/primitive.Field.html) ``` Compute the `message_hash` from a function call to be used by an authentication witness Useful for when you need a non-account contract to approve during execution. For example if you need a contract to make a call to nested contract, e.g., contract A wants to exit token T to L1 using bridge B, so it needs to allow B to transfer T on its behalf. @param caller The address of the contract that is calling the function, in the example above, this would be B @param consumer The address of the contract that is consuming the message, in the example above, this would be T @param chain_id The chain id of the chain that the message is being consumed on @param version The version of the chain that the message is being consumed on @param selector The function selector of the function that is being called @param args The arguments of the function that is being called --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/authwit/auth/fn.compute_authwit_nullifier.html # Function compute_authwit_nullifier ``` pub fn compute_authwit_nullifier(on_behalf_of: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), inner_hash: [Field](../../../std/primitive.Field.html)) -> [Field](../../../std/primitive.Field.html) ``` Computes the `authwit_nullifier` for a specific `on_behalf_of` and `inner_hash` Using the `on_behalf_of` and the `inner_hash` to ensure that the nullifier is siloed for a specific `on_behalf_of`. @param on_behalf_of The address that has authorized the `inner_hash` @param inner_hash The hash of the message to authorize --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/authwit/auth/fn.compute_inner_authwit_hash.html # Function compute_inner_authwit_hash ``` pub fn compute_inner_authwit_hash(args: [[Field](../../../std/primitive.Field.html); N]) -> [Field](../../../std/primitive.Field.html) ``` Computes the `inner_hash` of the authentication witness This is used internally, but also useful in cases where you want to compute the `inner_hash` for a specific message that is not necessarily a call, but just some "bytes" or text. @param args The arguments to hash --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/authwit/auth/fn.set_authorized.html # Function set_authorized ``` pub unconstrained fn set_authorized( context: [PublicContext](../../../noir_aztec/context/struct.PublicContext.html), message_hash: [Field](../../../std/primitive.Field.html), authorize: [bool](../../../std/primitive.bool.html), ) ``` Helper function to set the authorization status of a message hash Wraps a public call to the authentication registry to set the authorization status of a `message_hash` @param message_hash The hash of the message to authorize @param authorize True if the message should be authorized, false if it should be revoked --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/authwit/auth/fn.set_reject_all.html # Function set_reject_all ``` pub unconstrained fn set_reject_all(context: [PublicContext](../../../noir_aztec/context/struct.PublicContext.html), reject: [bool](../../../std/primitive.bool.html)) ``` Helper function to reject all authwits Wraps a public call to the authentication registry to set the `reject_all` flag @param reject True if all authwits should be rejected, false otherwise --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/authwit/auth/global.IS_VALID_SELECTOR.html # Global IS_VALID_SELECTOR ``` pub global IS_VALID_SELECTOR: [Field](../../../std/primitive.Field.html); ``` Authentication witness helper library Authentication Witness is a scheme for authenticating actions on Aztec, so users can allow third-parties (e.g. protocols or other users) to execute an action on their behalf. This library provides helper functions to manage such witnesses. The authentication witness, is some "witness" (data) that authenticates a `message_hash`. The simplest example of an authentication witness, is a signature. The signature is the "evidence", that the signer has seen the message, agrees with it, and has allowed it. It does not need to be a signature. It could be any kind of "proof" that the message is allowed. Another proof could be knowing some kind of secret, or having some kind of "token" that allows the message. The `message_hash` is a hash of the following structure: hash(consumer, chain_id, version, inner_hash) - consumer: the address of the contract that is "consuming" the message, - chain_id: the chain id of the chain that the message is being consumed on, - version: the version of the chain that the message is being consumed on, - inner_hash: the hash of the "inner" message that is being consumed, this is the "actual" message or action. While the `inner_hash` could be anything, such as showing you signed a specific message, it will often be a hash of the "action" to approve, along with who made the call. As part of this library, we provide a few helper functions to deal with such messages. For example, we provide helper function that is used for checking that the message is an encoding of the current call. This can be used to let some contract "allow" another contract to act on its behalf, as long as it can show that it is acting on behalf of the contract. If we take a case of allowing a contract to transfer tokens on behalf of an account, the `inner_hash` can be derived as: inner_hash = hash(caller, "transfer", hash(to, amount)) Where the `caller` would be the address of the contract that is trying to transfer the tokens, and `to` and `amount` the arguments for the transfer. Note that we have both a `caller` and a `consumer`, the `consumer` will be the contract that is consuming the message, in the case of the transfer, it would be the `Token` contract itself, while the caller, will be the actor that is allowed to transfer the tokens. The authentication mechanism works differently in public and private contexts. In private, we recall that everything is executed on the user's device, so we can use `oracles` to "ask" the user (not contract) for information. In public we cannot do this, since it is executed by the sequencer (someone else). Therefore we can instead use a "registry" to store the messages that we have approved. A simple example would be a "token" that is being "pulled" from one account into another. We will first outline how this would look in private, and then in public later. Say that a user `Alice` wants to deposit some tokens into a DeFi protocol (say a DEX). `Alice` would make a `deposit` transaction, that she is executing using her account contract. The account would call the `DeFi` contract to execute `deposit`, which would try to pull funds from the `Token` contract. Since the `DeFi` contract is trying to pull funds from an account that is not its own, it needs to convince the `Token` contract that it is allowed to do so. This is where the authentication witness comes in The `Token` contract computes a `message_hash` from the `transfer` call, and then asks `Alice Account` contract to verify that the `DeFi` contract is allowed to execute that call. `Alice Account` contract can then ask `Alice` if she wants to allow the `DeFi` contract to pull funds from her account. If she does, she will sign the `message_hash` and return the signature to the `Alice Account` which will validate it and return success to the `Token` contract which will then allow the `DeFi` contract to pull funds from `Alice`. To ensure that the same "approval" cannot be used multiple times, we also compute a `nullifier` for the authentication witness, and emit it from the `Token` contract (consumer). Note that we can do this flow as we are in private were we can do oracle calls out from contracts. Person Contract Contract Contract Alice Alice Account Token DeFi | | | | | Defi.deposit(Token, 1000) | | |----------------->| | | | | deposit(Token, 1000) | | |---------------------------------------->| | | | | | | | transfer(Alice, Defi, 1000) | | |<---------------------| | | | | | | Check if Defi may call transfer(Alice, Defi, 1000) | |<-----------------| | | | | | | Please give me AuthWit for DeFi | | | calling transfer(Alice, Defi, 1000) | | |<-----------------| | | | | | | | | | | | AuthWit for transfer(Alice, Defi, 1000) | |----------------->| | | | | AuthWit validity | | | |----------------->| | | | | | | | throw if invalid AuthWit | | | | | | | emit AuthWit nullifier | | | | | | | transfer(Alice, Defi, 1000) | | | | | | | | | | | | success | | | |--------------------->| | | | | | | | | | | | deposit(Token, 1000) | | | | | | | | If we instead were in public, we cannot do the same flow. Instead we would use an authentication registry to store the messages that we have approved. To approve a message, `Alice Account` can make a `set_authorized` call to the registry, to set a `message_hash` as authorized. This is essentially a mapping from `message_hash` to `true` for `Alice Contract`. Every account has its own map in the registry, so `Alice` cannot approve a message for `Bob`. The `Token` contract can then try to "spend" the approval by calling `consume` on the registry. If the message was approved, the value is updated to `false`, and we return the success flag. For more information on the registry, see `main.nr` in `auth_registry_contract`. Person Contract Contract Contract Contract Alice Alice Account Registry Token DeFi | | | | | | Registry.set_authorized(..., true) | | | |----------------->| | | | | | set_authorized(..., true) | | | |------------------->| | | | | | | | | | set authorized to true | | | | | | | | | | | | | Defi.deposit(Token, 1000) | | | |----------------->| | | | | | deposit(Token, 1000) | | | |-------------------------------------------------------------->| | | | | | | | | transfer(Alice, Defi, 1000) | | | | |<---------------------| | | | | | | | | Check if Defi may call transfer(Alice, Defi, 1000) | | |<------------------| | | | | | | | | throw if invalid AuthWit | | | | | | | | | | | | | | set authorized to false | | | | | | | | | | | | | | | AuthWit validity | | | | |------------------>| | | | | | | | | | | transfer(Alice, Defi, 1000) | | | |<-------------------->| | | | | | | | | | success | | | | |--------------------->| | | | | | | | | | deposit(Token, 1000) | | | | | --- FAQ --- Q: Why are we using a success flag of `poseidon2_hash_bytes("IS_VALID()")` instead of just returning a boolean? A: We want to make sure that we don't accidentally return `true` if there is a collision in the function selector. By returning a hash of `IS_VALID()`, it becomes very unlikely that there is both a collision and we return a success flag. Q: Why are we using static calls? A: We are using static calls to ensure that the account contract cannot re-enter. If it was a normal call, it could make a new call and do a re-entry attack. Using a static ensures that it cannot update any state. Q: Would it not be cheaper to use a nullifier instead of updating state in public? A: At a quick glance, a public state update + nullifier is 96 bytes, but two state updates are 128, so it would be cheaper to use a nullifier, if this is the way it would always be done. However, if both the approval and the consumption is done in the same transaction, then we will be able to squash the updates (only final tx state diff is posted to DA), and now it is cheaper. Q: Why is the chain id and the version part of the message hash? A: The chain id and the version is part of the message hash to ensure that the message is only valid on a specific chain to avoid a case where the same message could be used across multiple chains. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/authwit/auth/index.html # Module auth ## Functions - [assert_current_call_valid_authwit](fn.assert_current_call_valid_authwit.html)Assert that `on_behalf_of` has authorized the current call with a valid authentication witness - [assert_current_call_valid_authwit_public](fn.assert_current_call_valid_authwit_public.html)Assert that `on_behalf_of` has authorized the current call in the authentication registry - [assert_inner_hash_valid_authwit](fn.assert_inner_hash_valid_authwit.html)Assert that a specific `inner_hash` is valid for the `on_behalf_of` address - [assert_inner_hash_valid_authwit_public](fn.assert_inner_hash_valid_authwit_public.html)Assert that `on_behalf_of` has authorized a specific `inner_hash` in the authentication registry - [compute_authwit_message_hash](fn.compute_authwit_message_hash.html)Computes the `message_hash` for the authentication witness - [compute_authwit_message_hash_from_call](fn.compute_authwit_message_hash_from_call.html)Compute the `message_hash` from a function call to be used by an authentication witness - [compute_authwit_nullifier](fn.compute_authwit_nullifier.html)Computes the `authwit_nullifier` for a specific `on_behalf_of` and `inner_hash` - [compute_inner_authwit_hash](fn.compute_inner_authwit_hash.html)Computes the `inner_hash` of the authentication witness - [set_authorized](fn.set_authorized.html)Helper function to set the authorization status of a message hash - [set_reject_all](fn.set_reject_all.html)Helper function to reject all authwits ## Globals - [IS_VALID_SELECTOR](global.IS_VALID_SELECTOR.html)Authentication witness helper library --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/authwit/authorization_interface/index.html # Module authorization_interface ## Traits - [AuthorizationInterface](trait.AuthorizationInterface.html)Allows getting the selector for an authorization struct (see src/macros/authorization.nr) used to uniquely identify them and avoiding collisions. This is important because authorizations are emitted as offchain effects and their unique selector allows users/wallets to decode them correctly --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/authwit/authorization_interface/trait.AuthorizationInterface.html # Trait AuthorizationInterface ``` pub trait AuthorizationInterface { // Required methods pub fn [get_authorization_selector](#get_authorization_selector)(self) -> [AuthorizationSelector](../../../noir_aztec/authwit/struct.AuthorizationSelector.html); } ``` Allows getting the selector for an authorization struct (see src/macros/authorization.nr) used to uniquely identify them and avoiding collisions. This is important because authorizations are emitted as offchain effects and their unique selector allows users/wallets to decode them correctly ## Required methods `pub fn [get_authorization_selector](#get_authorization_selector)(self) -> [AuthorizationSelector](../../../noir_aztec/authwit/struct.AuthorizationSelector.html)` Returns the unique identifier of the authorization type. ## Implementors ### `impl [AuthorizationInterface](../../../noir_aztec/authwit/authorization_interface/trait.AuthorizationInterface.html) for CallAuthorization` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/authwit/entrypoint/app/struct.AppPayload.html # Struct AppPayload ``` pub struct AppPayload { pub tx_nonce: [Field](../../../../std/primitive.Field.html), /* private fields */ } ``` ## Fields `tx_nonce: [Field](../../../../std/primitive.Field.html)` ## Implementations ### `impl [AppPayload](../../../../noir_aztec/authwit/entrypoint/app/struct.AppPayload.html)` `pub fn [execute_calls](#execute_calls)(self, context: &mut [PrivateContext](../../../../noir_aztec/context/struct.PrivateContext.html))` ## Trait implementations ### `impl [Deserialize](../../../../serde/serialization/trait.Deserialize.html) for [AppPayload](../../../../noir_aztec/authwit/entrypoint/app/struct.AppPayload.html)` `pub fn deserialize(fields: [[Field](../../../../std/primitive.Field.html); 31]) -> Self` `pub fn stream_deserialize(reader: &mut [Reader](../../../../serde/reader/struct.Reader.html)) -> Self` ### `impl [Hash](../../../../protocol_types/traits/trait.Hash.html) for [AppPayload](../../../../noir_aztec/authwit/entrypoint/app/struct.AppPayload.html)` `pub fn hash(self) -> [Field](../../../../std/primitive.Field.html)` ### `impl [Serialize](../../../../serde/serialization/trait.Serialize.html) for [AppPayload](../../../../noir_aztec/authwit/entrypoint/app/struct.AppPayload.html)` `pub fn serialize(self) -> [[Field](../../../../std/primitive.Field.html); 31]` `pub fn stream_serialize(self, writer: &mut [Writer](../../../../serde/writer/struct.Writer.html))` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/authwit/entrypoint/index.html # Module entrypoint ## Modules - [app](app/index.html) ## Structs - [FunctionCall](struct.FunctionCall.html) --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/authwit/entrypoint/struct.FunctionCall.html # Struct FunctionCall ``` pub struct FunctionCall { pub args_hash: [Field](../../../std/primitive.Field.html), pub function_selector: [FunctionSelector](../../../protocol_types/abis/function_selector/struct.FunctionSelector.html), pub target_address: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), pub is_public: [bool](../../../std/primitive.bool.html), pub hide_msg_sender: [bool](../../../std/primitive.bool.html), pub is_static: [bool](../../../std/primitive.bool.html), } ``` ## Fields `args_hash: [Field](../../../std/primitive.Field.html)` `function_selector: [FunctionSelector](../../../protocol_types/abis/function_selector/struct.FunctionSelector.html)` `target_address: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html)` `is_public: [bool](../../../std/primitive.bool.html)` `hide_msg_sender: [bool](../../../std/primitive.bool.html)` `is_static: [bool](../../../std/primitive.bool.html)` ## Trait implementations ### `impl [Deserialize](../../../serde/serialization/trait.Deserialize.html) for [FunctionCall](../../../noir_aztec/authwit/entrypoint/struct.FunctionCall.html)` `pub fn deserialize(fields: [[Field](../../../std/primitive.Field.html); 6]) -> Self` `pub fn stream_deserialize(reader: &mut [Reader](../../../serde/reader/struct.Reader.html)) -> Self` ### `impl [Serialize](../../../serde/serialization/trait.Serialize.html) for [FunctionCall](../../../noir_aztec/authwit/entrypoint/struct.FunctionCall.html)` `pub fn serialize(self) -> [[Field](../../../std/primitive.Field.html); 6]` `pub fn stream_serialize(self, writer: &mut [Writer](../../../serde/writer/struct.Writer.html))` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/authwit/index.html # Module authwit Authorization. ## Modules - [account](account/index.html) - [auth](auth/index.html) - [authorization_interface](authorization_interface/index.html) - [entrypoint](entrypoint/index.html) ## Structs - [AuthorizationSelector](struct.AuthorizationSelector.html) --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/authwit/struct.AuthorizationSelector.html # Struct AuthorizationSelector ``` pub struct AuthorizationSelector { /* private fields */ } ``` ## Implementations ### `impl [AuthorizationSelector](../../noir_aztec/authwit/struct.AuthorizationSelector.html)` `pub fn [from_u32](#from_u32)(value: [u32](../../std/primitive.u32.html)) -> Self` `pub fn [from_signature](#from_signature)(signature: [str](../../std/primitive.str.html)) -> Self` `pub fn [zero](#zero)() -> Self` ## Trait implementations ### `impl [Deserialize](../../serde/serialization/trait.Deserialize.html) for [AuthorizationSelector](../../noir_aztec/authwit/struct.AuthorizationSelector.html)` `pub fn deserialize(fields: [[Field](../../std/primitive.Field.html); 1]) -> Self` `pub fn stream_deserialize(reader: &mut [Reader](../../serde/reader/struct.Reader.html)) -> Self` ### `impl [Empty](../../protocol_types/traits/trait.Empty.html) for [AuthorizationSelector](../../noir_aztec/authwit/struct.AuthorizationSelector.html)` `pub fn empty() -> Self` `pub fn is_empty(self) -> [bool](../../std/primitive.bool.html)` `pub fn assert_empty(self, msg: [str](../../std/primitive.str.html))` ### `impl [Eq](../../std/cmp/trait.Eq.html) for [AuthorizationSelector](../../noir_aztec/authwit/struct.AuthorizationSelector.html)` `pub fn eq(_self: Self, _other: Self) -> [bool](../../std/primitive.bool.html)` ### `impl [FromField](../../protocol_types/traits/trait.FromField.html) for [AuthorizationSelector](../../noir_aztec/authwit/struct.AuthorizationSelector.html)` `pub fn from_field(field: [Field](../../std/primitive.Field.html)) -> Self` ### `impl [Serialize](../../serde/serialization/trait.Serialize.html) for [AuthorizationSelector](../../noir_aztec/authwit/struct.AuthorizationSelector.html)` `pub fn serialize(self) -> [[Field](../../std/primitive.Field.html); 1]` `pub fn stream_serialize(self, writer: &mut [Writer](../../serde/writer/struct.Writer.html))` ### `impl [ToField](../../protocol_types/traits/trait.ToField.html) for [AuthorizationSelector](../../noir_aztec/authwit/struct.AuthorizationSelector.html)` `pub fn to_field(self) -> [Field](../../std/primitive.Field.html)` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/capsules/index.html # Module capsules ## Structs - [CapsuleArray](struct.CapsuleArray.html)A dynamically sized array backed by PXE's non-volatile database (called capsules). Values are persisted until deleted, so they can be e.g. stored during simulation of a transaction and later retrieved during witness generation. All values are scoped per contract address, so external contracts cannot access them. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/capsules/struct.CapsuleArray.html # Struct CapsuleArray ``` pub struct CapsuleArray { /* private fields */ } ``` A dynamically sized array backed by PXE's non-volatile database (called capsules). Values are persisted until deleted, so they can be e.g. stored during simulation of a transaction and later retrieved during witness generation. All values are scoped per contract address, so external contracts cannot access them. ## Implementations ### `impl [CapsuleArray](../../noir_aztec/capsules/struct.CapsuleArray.html)` `pub unconstrained fn [at](#at)( contract_address: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html), base_slot: [Field](../../std/primitive.Field.html), scope: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html), ) -> Self` Returns a CapsuleArray scoped to a specific address. Array elements are stored in contiguous slots following the base slot, so there should be sufficient space between array base slots to accommodate elements. A reasonable strategy is to make the base slot a hash of a unique value. `pub unconstrained fn [len](#len)(self) -> [u32](../../std/primitive.u32.html)` Returns the number of elements stored in the array. `pub unconstrained fn [push](#push)(self, value: T) where T: [Serialize](../../serde/serialization/trait.Serialize.html)` Stores a value at the end of the array. `pub unconstrained fn [get](#get)(self, index: [u32](../../std/primitive.u32.html)) -> T where T: [Deserialize](../../serde/serialization/trait.Deserialize.html)` Retrieves the value stored in the array at `index`. Throws if the index is out of bounds. `pub unconstrained fn [remove](#remove)(self, index: [u32](../../std/primitive.u32.html))` Deletes the value stored in the array at `index`. Throws if the index is out of bounds. `pub unconstrained fn [for_each](#for_each)(self, f: unconstrained fn[Env]([u32](../../std/primitive.u32.html), T)) where T: [Deserialize](../../serde/serialization/trait.Deserialize.html)` Calls a function on each element of the array. The function `f` is called once with each array value and its corresponding index. The order in which values are processed is arbitrary. #### Array Mutation It is safe to delete the current element (and only the current element) from inside the callback via `remove`: ``` array.for_each(|index, value| { if some_condition(value) { array.remove(index); // safe only for this index } } ``` If all elements in the array need to iterated over and then removed, then using `for_each` results in optimal efficiency. It is not safe to push new elements into the array from inside the callback. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/context/calls/index.html # Module calls ## Structs - [PrivateCall](struct.PrivateCall.html) - [PrivateStaticCall](struct.PrivateStaticCall.html) - [PublicCall](struct.PublicCall.html) - [PublicStaticCall](struct.PublicStaticCall.html) - [UtilityCall](struct.UtilityCall.html) --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/context/calls/struct.PrivateCall.html # Struct PrivateCall ``` pub struct PrivateCall { pub target_contract: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), pub selector: [FunctionSelector](../../../protocol_types/abis/function_selector/struct.FunctionSelector.html), pub name: [str](../../../std/primitive.str.html), pub args: [[Field](../../../std/primitive.Field.html); N], /* private fields */ } ``` ## Fields `target_contract: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html)` `selector: [FunctionSelector](../../../protocol_types/abis/function_selector/struct.FunctionSelector.html)` `name: [str](../../../std/primitive.str.html)` `args: [[Field](../../../std/primitive.Field.html); N]` ## Implementations ### `impl [PrivateCall](../../../noir_aztec/context/calls/struct.PrivateCall.html)` `pub fn [call](#call)(self, context: &mut [PrivateContext](../../../noir_aztec/context/struct.PrivateContext.html)) -> T where T: [Deserialize](../../../serde/serialization/trait.Deserialize.html)` DEPRECATED. Please use the new contract API: `self.call(MyContract::at(address).my_private_function(...args))` instead of manually constructing and calling `PrivateCall`. ### `impl [PrivateCall](../../../noir_aztec/context/calls/struct.PrivateCall.html)` `pub fn [new](#new)( target_contract: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), selector: [FunctionSelector](../../../protocol_types/abis/function_selector/struct.FunctionSelector.html), name: [str](../../../std/primitive.str.html), args: [[Field](../../../std/primitive.Field.html); N], ) -> Self` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/context/calls/struct.PrivateStaticCall.html # Struct PrivateStaticCall ``` pub struct PrivateStaticCall { pub target_contract: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), pub selector: [FunctionSelector](../../../protocol_types/abis/function_selector/struct.FunctionSelector.html), pub name: [str](../../../std/primitive.str.html), pub args: [[Field](../../../std/primitive.Field.html); N], /* private fields */ } ``` ## Fields `target_contract: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html)` `selector: [FunctionSelector](../../../protocol_types/abis/function_selector/struct.FunctionSelector.html)` `name: [str](../../../std/primitive.str.html)` `args: [[Field](../../../std/primitive.Field.html); N]` ## Implementations ### `impl [PrivateStaticCall](../../../noir_aztec/context/calls/struct.PrivateStaticCall.html)` `pub fn [new](#new)( target_contract: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), selector: [FunctionSelector](../../../protocol_types/abis/function_selector/struct.FunctionSelector.html), name: [str](../../../std/primitive.str.html), args: [[Field](../../../std/primitive.Field.html); N], ) -> Self` `pub fn [view](#view)(self, context: &mut [PrivateContext](../../../noir_aztec/context/struct.PrivateContext.html)) -> T where T: [Deserialize](../../../serde/serialization/trait.Deserialize.html)` DEPRECATED. Please use the new contract API: `self.view(MyContract::at(address).my_private_static_function(...args))` instead of manually constructing and calling `PrivateCall`. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/context/calls/struct.PublicCall.html # Struct PublicCall ``` pub struct PublicCall { pub target_contract: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), pub selector: [FunctionSelector](../../../protocol_types/abis/function_selector/struct.FunctionSelector.html), pub name: [str](../../../std/primitive.str.html), pub args: [[Field](../../../std/primitive.Field.html); N], /* private fields */ } ``` ## Fields `target_contract: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html)` `selector: [FunctionSelector](../../../protocol_types/abis/function_selector/struct.FunctionSelector.html)` `name: [str](../../../std/primitive.str.html)` `args: [[Field](../../../std/primitive.Field.html); N]` ## Implementations ### `impl [PublicCall](../../../noir_aztec/context/calls/struct.PublicCall.html)` `pub fn [new](#new)( target_contract: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), selector: [FunctionSelector](../../../protocol_types/abis/function_selector/struct.FunctionSelector.html), name: [str](../../../std/primitive.str.html), args: [[Field](../../../std/primitive.Field.html); N], ) -> Self` `pub fn [with_gas](#with_gas)(self, gas_opts: [GasOpts](../../../noir_aztec/context/gas/struct.GasOpts.html)) -> Self` `pub unconstrained fn [call](#call)(self, context: [PublicContext](../../../noir_aztec/context/struct.PublicContext.html)) -> T where T: [Deserialize](../../../serde/serialization/trait.Deserialize.html)` DEPRECATED. Please use the new contract API: `self.call(MyContract::at(address).my_public_function(...args))` instead of manually constructing and calling `PublicCall`. `pub fn [enqueue](#enqueue)(self, context: &mut [PrivateContext](../../../noir_aztec/context/struct.PrivateContext.html))` DEPRECATED. Please use the new contract API: `self.enqueue(MyContract::at(address).my_public_function(...args))` instead of manually constructing and calling `PublicCall`. `pub fn [enqueue_incognito](#enqueue_incognito)(self, context: &mut [PrivateContext](../../../noir_aztec/context/struct.PrivateContext.html))` DEPRECATED. Please use the new contract API: `self.enqueue_incognito(MyContract::at(address).my_public_function(...args))` instead of manually constructing and calling `PublicCall`. `pub fn [set_as_teardown](#set_as_teardown)(self, context: &mut [PrivateContext](../../../noir_aztec/context/struct.PrivateContext.html))` DEPRECATED. Please use the new contract API: `self.set_as_teardown(MyContract::at(address).my_public_function(...args))` instead of manually constructing and setting the teardown function `PublicCall`. `pub fn [set_as_teardown_incognito](#set_as_teardown_incognito)(self, context: &mut [PrivateContext](../../../noir_aztec/context/struct.PrivateContext.html))` DEPRECATED. Please use the new contract API: `self.set_as_teardown_incognito(MyContract::at(address).my_public_function(...args))` instead of manually constructing and setting the teardown function `PublicCall`. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/context/calls/struct.PublicStaticCall.html # Struct PublicStaticCall ``` pub struct PublicStaticCall { pub target_contract: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), pub selector: [FunctionSelector](../../../protocol_types/abis/function_selector/struct.FunctionSelector.html), pub name: [str](../../../std/primitive.str.html), pub args: [[Field](../../../std/primitive.Field.html); N], /* private fields */ } ``` ## Fields `target_contract: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html)` `selector: [FunctionSelector](../../../protocol_types/abis/function_selector/struct.FunctionSelector.html)` `name: [str](../../../std/primitive.str.html)` `args: [[Field](../../../std/primitive.Field.html); N]` ## Implementations ### `impl [PublicStaticCall](../../../noir_aztec/context/calls/struct.PublicStaticCall.html)` `pub fn [new](#new)( target_contract: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), selector: [FunctionSelector](../../../protocol_types/abis/function_selector/struct.FunctionSelector.html), name: [str](../../../std/primitive.str.html), args: [[Field](../../../std/primitive.Field.html); N], ) -> Self` `pub fn [with_gas](#with_gas)(self, gas_opts: [GasOpts](../../../noir_aztec/context/gas/struct.GasOpts.html)) -> Self` `pub unconstrained fn [view](#view)(self, context: [PublicContext](../../../noir_aztec/context/struct.PublicContext.html)) -> T where T: [Deserialize](../../../serde/serialization/trait.Deserialize.html)` DEPRECATED. Please use the new contract API: `self.view(MyContract::at(address).my_public_static_function(...args))` instead of manually constructing and calling `PublicStaticCall`. `pub fn [enqueue_view](#enqueue_view)(self, context: &mut [PrivateContext](../../../noir_aztec/context/struct.PrivateContext.html))` DEPRECATED. Please use the new contract API: `self.enqueue_view(MyContract::at(address).my_public_static_function(...args))` instead of manually constructing and calling `PublicStaticCall`. `pub fn [enqueue_view_incognito](#enqueue_view_incognito)(self, context: &mut [PrivateContext](../../../noir_aztec/context/struct.PrivateContext.html))` DEPRECATED. Please use the new contract API: `self.enqueue_view_incognito(MyContract::at(address).my_public_static_function(...args))` instead of manually constructing and calling `PublicStaticCall`. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/context/calls/struct.UtilityCall.html # Struct UtilityCall ``` pub struct UtilityCall { pub target_contract: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), pub selector: [FunctionSelector](../../../protocol_types/abis/function_selector/struct.FunctionSelector.html), pub name: [str](../../../std/primitive.str.html), pub args: [[Field](../../../std/primitive.Field.html); N], /* private fields */ } ``` ## Fields `target_contract: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html)` `selector: [FunctionSelector](../../../protocol_types/abis/function_selector/struct.FunctionSelector.html)` `name: [str](../../../std/primitive.str.html)` `args: [[Field](../../../std/primitive.Field.html); N]` ## Implementations ### `impl [UtilityCall](../../../noir_aztec/context/calls/struct.UtilityCall.html)` `pub fn [new](#new)( target_contract: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), selector: [FunctionSelector](../../../protocol_types/abis/function_selector/struct.FunctionSelector.html), name: [str](../../../std/primitive.str.html), args: [[Field](../../../std/primitive.Field.html); N], ) -> Self` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/context/gas/struct.GasOpts.html # Struct GasOpts ``` pub struct GasOpts { pub l2_gas: [Option](../../../std/option/struct.Option.html)<[u32](../../../std/primitive.u32.html)>, pub da_gas: [Option](../../../std/option/struct.Option.html)<[u32](../../../std/primitive.u32.html)>, } ``` ## Fields `l2_gas: [Option](../../../std/option/struct.Option.html)<[u32](../../../std/primitive.u32.html)>` `da_gas: [Option](../../../std/option/struct.Option.html)<[u32](../../../std/primitive.u32.html)>` ## Implementations ### `impl [GasOpts](../../../noir_aztec/context/gas/struct.GasOpts.html)` `pub fn [default](#default)() -> Self` `pub fn [new](#new)(l2_gas: [u32](../../../std/primitive.u32.html), da_gas: [u32](../../../std/primitive.u32.html)) -> Self` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/context/index.html # Module context Private, public and utility execution contexts. ## Re-exports - `pub use noir_aztec::context::calls::[PrivateCall](../../noir_aztec/context/calls/struct.PrivateCall.html);` - `pub use noir_aztec::context::calls::[PrivateStaticCall](../../noir_aztec/context/calls/struct.PrivateStaticCall.html);` - `pub use noir_aztec::context::calls::[PublicCall](../../noir_aztec/context/calls/struct.PublicCall.html);` - `pub use noir_aztec::context::calls::[PublicStaticCall](../../noir_aztec/context/calls/struct.PublicStaticCall.html);` - `pub use noir_aztec::context::calls::[UtilityCall](../../noir_aztec/context/calls/struct.UtilityCall.html);` ## Modules - [calls](calls/index.html) - [gas](gas/index.html) - [inputs](inputs/index.html) ## Structs - [NoteExistenceRequest](struct.NoteExistenceRequest.html)A request to assert the existence of a note. - [NullifierExistenceRequest](struct.NullifierExistenceRequest.html)A request to assert the existence of a nullifier. - [PrivateContext](struct.PrivateContext.html)PrivateContext - [PublicContext](struct.PublicContext.html)PublicContext - [ReturnsHash](struct.ReturnsHash.html)The hash of a private contract function call's return value. - [UtilityContext](struct.UtilityContext.html) --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/context/inputs/struct.PrivateContextInputs.html # Struct PrivateContextInputs ``` pub struct PrivateContextInputs { pub call_context: [CallContext](../../../protocol_types/abis/call_context/struct.CallContext.html), pub anchor_block_header: [BlockHeader](../../../protocol_types/abis/block_header/struct.BlockHeader.html), pub tx_context: [TxContext](../../../protocol_types/abis/transaction/tx_context/struct.TxContext.html), pub start_side_effect_counter: [u32](../../../std/primitive.u32.html), } ``` ## Fields `call_context: [CallContext](../../../protocol_types/abis/call_context/struct.CallContext.html)` `anchor_block_header: [BlockHeader](../../../protocol_types/abis/block_header/struct.BlockHeader.html)` `tx_context: [TxContext](../../../protocol_types/abis/transaction/tx_context/struct.TxContext.html)` `start_side_effect_counter: [u32](../../../std/primitive.u32.html)` ## Trait implementations ### `impl [Empty](../../../protocol_types/traits/trait.Empty.html) for [PrivateContextInputs](../../../noir_aztec/context/inputs/struct.PrivateContextInputs.html)` `pub fn empty() -> Self` `pub fn is_empty(self) -> [bool](../../../std/primitive.bool.html)` `pub fn assert_empty(self, msg: [str](../../../std/primitive.str.html))` ### `impl [Eq](../../../std/cmp/trait.Eq.html) for [PrivateContextInputs](../../../noir_aztec/context/inputs/struct.PrivateContextInputs.html)` `pub fn eq(_self: Self, _other: Self) -> [bool](../../../std/primitive.bool.html)` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/context/struct.NoteExistenceRequest.html # Struct NoteExistenceRequest ``` pub struct NoteExistenceRequest { /* private fields */ } ``` A request to assert the existence of a note. Used by [`crate::context::PrivateContext::assert_note_exists`](../../noir_aztec/context/struct.PrivateContext.html#assert_note_exists). ## Implementations ### `impl [NoteExistenceRequest](../../noir_aztec/context/struct.NoteExistenceRequest.html)` `pub fn [for_pending](#for_pending)(unsiloed_note_hash: [Field](../../std/primitive.Field.html), contract_address: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html)) -> Self` Creates an existence request for a pending note. Pending notes have not been yet assigned a nonce, and they therefore have no unique note hash. Instead, these requests are created using the unsiloed note hash (i.e. from [`crate::note::note_interface::NoteHash::compute_note_hash`](../../noir_aztec/note/note_interface/trait.NoteHash.html#compute_note_hash)) and address of the contract that created the note. `pub fn [for_settled](#for_settled)(unique_note_hash: [Field](../../std/primitive.Field.html)) -> Self` Creates an existence request for a settled note. Unlike pending notes, settled notes have a nonce, and their existence request is created using the unique note hash. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/context/struct.NullifierExistenceRequest.html # Struct NullifierExistenceRequest ``` pub struct NullifierExistenceRequest { /* private fields */ } ``` A request to assert the existence of a nullifier. Used by [`crate::context::private_context::PrivateContext::assert_nullifier_exists`](../../noir_aztec/context/struct.PrivateContext.html#assert_nullifier_exists). ## Implementations ### `impl [NullifierExistenceRequest](../../noir_aztec/context/struct.NullifierExistenceRequest.html)` `pub fn [for_pending](#for_pending)(unsiloed_nullifier: [Field](../../std/primitive.Field.html), contract_address: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html)) -> Self` Creates an existence request for a pending nullifier. Pending nullifiers have not been siloed with the contract address. These requests are created using the unsiloed value and address of the contract that emitted the nullifier. `pub fn [for_settled](#for_settled)(siloed_nullifier: [Field](../../std/primitive.Field.html)) -> Self` Creates an existence request for a settled nullifier. Unlike pending nullifiers, settled nullifiers have been siloed with their contract addresses before adding to the nullifier tree, and their existence request is created using the siloed value. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/context/struct.PrivateContext.html # Struct PrivateContext ``` pub struct PrivateContext { pub inputs: [PrivateContextInputs](../../noir_aztec/context/inputs/struct.PrivateContextInputs.html), pub side_effect_counter: [u32](../../std/primitive.u32.html), pub min_revertible_side_effect_counter: [u32](../../std/primitive.u32.html), pub is_fee_payer: [bool](../../std/primitive.bool.html), pub args_hash: [Field](../../std/primitive.Field.html), pub return_hash: [Field](../../std/primitive.Field.html), pub expiration_timestamp: [u64](../../std/primitive.u64.html), pub note_hashes: [BoundedVec](../../std/collections/bounded_vec/struct.BoundedVec.html)<[Counted](../../protocol_types/side_effect/counted/struct.Counted.html)<[NoteHash](../../protocol_types/abis/note_hash/type.NoteHash.html)>, 16>, pub nullifiers: [BoundedVec](../../std/collections/bounded_vec/struct.BoundedVec.html)<[Counted](../../protocol_types/side_effect/counted/struct.Counted.html)<[Nullifier](../../protocol_types/abis/nullifier/struct.Nullifier.html)>, 16>, pub private_call_requests: [BoundedVec](../../std/collections/bounded_vec/struct.BoundedVec.html)<[PrivateCallRequest](../../protocol_types/abis/private_call_request/struct.PrivateCallRequest.html), 8>, pub public_call_requests: [BoundedVec](../../std/collections/bounded_vec/struct.BoundedVec.html)<[Counted](../../protocol_types/side_effect/counted/struct.Counted.html)<[PublicCallRequest](../../protocol_types/abis/public_call_request/struct.PublicCallRequest.html)>, 32>, pub public_teardown_call_request: [PublicCallRequest](../../protocol_types/abis/public_call_request/struct.PublicCallRequest.html), pub l2_to_l1_msgs: [BoundedVec](../../std/collections/bounded_vec/struct.BoundedVec.html)<[Counted](../../protocol_types/side_effect/counted/struct.Counted.html)<[L2ToL1Message](../../protocol_types/messaging/l2_to_l1_message/struct.L2ToL1Message.html)>, 8>, pub anchor_block_header: [BlockHeader](../../protocol_types/abis/block_header/struct.BlockHeader.html), pub private_logs: [BoundedVec](../../std/collections/bounded_vec/struct.BoundedVec.html)<[Counted](../../protocol_types/side_effect/counted/struct.Counted.html)<[PrivateLogData](../../protocol_types/abis/private_log/struct.PrivateLogData.html)>, 16>, pub contract_class_logs_hashes: [BoundedVec](../../std/collections/bounded_vec/struct.BoundedVec.html)<[Counted](../../protocol_types/side_effect/counted/struct.Counted.html)<[LogHash](../../protocol_types/abis/log_hash/struct.LogHash.html)>, 1>, pub last_key_validation_requests: [[Option](../../std/option/struct.Option.html)<[KeyValidationRequest](../../protocol_types/abis/validation_requests/key_validation_request/struct.KeyValidationRequest.html)>; 4], pub expected_non_revertible_side_effect_counter: [u32](../../std/primitive.u32.html), pub expected_revertible_side_effect_counter: [u32](../../std/primitive.u32.html), /* private fields */ } ``` ## PrivateContext The main interface between an #[external("private")] function and the Aztec blockchain. An instance of the PrivateContext is initialized automatically at the outset of every private function, within the #[external("private")] macro, so you'll never need to consciously instantiate this yourself. The instance is always named `context`, and it is always be available within the body of every #[external("private")] function in your smart contract. For those used to "vanilla" Noir, it might be jarring to have access to > `context` without seeing a declaration `let context = PrivateContext::new(...)` > within the body of your function. This is just a consequence of using > macros to tidy-up verbose boilerplate. You can use `nargo expand` to > expand all macros, if you dare. Typical usage for a smart contract developer will be to call getter methods of the PrivateContext. Pushing data and requests to the context is mostly handled within aztec-nr's own functions, so typically a smart contract developer won't need to call any setter methods directly. Advanced users might occasionally wish to push data to the context > directly for lower-level control. If you find yourself doing this, please > open an issue on GitHub to describe your use case: it might be that > new functionality should be added to aztec-nr. ## Responsibilities - Exposes contextual data to a private function: - Data relating to how this private function was called. - msg_sender - this_address - (the contract address of the private function being executed) - See `CallContext` for more data. - Data relating to the transaction in which this private function is being executed. - chain_id - version - gas_settings - Provides state access: - Access to the "Anchor block" header. Recall, a private function cannot read from the "current" block header, but must read from some historical block header, because as soon as private function execution begins (asynchronously, on a user's device), the public state of the chain (the "current state") will have progressed forward. We call this reference the "Anchor block". See `BlockHeader`. Enables consumption of L1->L2 messages. - Enables calls to functions of other smart contracts: - Private function calls - Enqueueing of public function call requests (Since public functions are executed at a later time, by a block proposer, we say they are "enqueued"). - Writes data to the blockchain: - New notes - New nullifiers - Private logs (for sending encrypted note contents or encrypted events) New L2->L1 messages. - Provides args to the private function (handled by the #[external("private")] macro). - Returns the return values of this private function (handled by the #[external("private")] macro). - Makes Key Validation Requests. - Private functions are not allowed to see master secret keys, because we do not trust them. They are instead given "app-siloed" secret keys with a claim that they relate to a master public key. They can then request validation of this claim, by making a "key validation request" to the protocol's kernel circuits (which are allowed to see certain master secret keys). ## Advanced Responsibilities - Ultimately, the PrivateContext is responsible for constructing the PrivateCircuitPublicInputs of the private function being executed. All private functions on Aztec must have public inputs which adhere to the rigid layout of the PrivateCircuitPublicInputs, in order to be compatible with the protocol's kernel circuits. A well-known misnomer: - "public inputs" contain both inputs and outputs of this function. - By "outputs" we mean a lot more side-effects than just the "return values" of the function. - Most of the so-called "public inputs" are kept private, and never leak to the outside world, because they are 'swallowed' by the protocol's kernel circuits before the tx is sent to the network. Only the following are exposed to the outside world: - New note_hashes - New nullifiers - New private logs New L2->L1 messages - New enqueued public function call requests All the above-listed arrays of side-effects can be padded by the user's wallet (through instructions to the kernel circuits, via the PXE) to obscure their true lengths. ## Syntax Justification Both user-defined functions and most functions in aztec-nr need access to the PrivateContext instance to read/write data. This is why you'll see the arguably-ugly pervasiveness of the "context" throughout your smart contract and the aztec-nr library. For example, `&mut context` is prevalent. In some languages, you can access and mutate a global variable (such as a PrivateContext instance) from a function without polluting the function's parameters. With Noir, a function must explicitly pass control of a mutable variable to another function, by reference. Since many functions in aztec-nr need to be able to push new data to the PrivateContext, they need to be handed a mutable reference to the context as a parameter. For example, `Context` is prevalent as a generic parameter, to give better type safety at compile time. Many `aztec-nr` functions don't make sense if they're called in a particular runtime (private, public or utility), and so are intentionally only implemented over certain [Private|Public|Utility]Context structs. This gives smart contract developers a much faster feedback loop if they're making a mistake, as an error will be thrown by the LSP or when they compile their contract. ## Fields `inputs: [PrivateContextInputs](../../noir_aztec/context/inputs/struct.PrivateContextInputs.html)` `side_effect_counter: [u32](../../std/primitive.u32.html)` `min_revertible_side_effect_counter: [u32](../../std/primitive.u32.html)` `is_fee_payer: [bool](../../std/primitive.bool.html)` `args_hash: [Field](../../std/primitive.Field.html)` `return_hash: [Field](../../std/primitive.Field.html)` `expiration_timestamp: [u64](../../std/primitive.u64.html)` `note_hashes: [BoundedVec](../../std/collections/bounded_vec/struct.BoundedVec.html)<[Counted](../../protocol_types/side_effect/counted/struct.Counted.html)<[NoteHash](../../protocol_types/abis/note_hash/type.NoteHash.html)>, 16>` `nullifiers: [BoundedVec](../../std/collections/bounded_vec/struct.BoundedVec.html)<[Counted](../../protocol_types/side_effect/counted/struct.Counted.html)<[Nullifier](../../protocol_types/abis/nullifier/struct.Nullifier.html)>, 16>` `private_call_requests: [BoundedVec](../../std/collections/bounded_vec/struct.BoundedVec.html)<[PrivateCallRequest](../../protocol_types/abis/private_call_request/struct.PrivateCallRequest.html), 8>` `public_call_requests: [BoundedVec](../../std/collections/bounded_vec/struct.BoundedVec.html)<[Counted](../../protocol_types/side_effect/counted/struct.Counted.html)<[PublicCallRequest](../../protocol_types/abis/public_call_request/struct.PublicCallRequest.html)>, 32>` `public_teardown_call_request: [PublicCallRequest](../../protocol_types/abis/public_call_request/struct.PublicCallRequest.html)` `l2_to_l1_msgs: [BoundedVec](../../std/collections/bounded_vec/struct.BoundedVec.html)<[Counted](../../protocol_types/side_effect/counted/struct.Counted.html)<[L2ToL1Message](../../protocol_types/messaging/l2_to_l1_message/struct.L2ToL1Message.html)>, 8>` `anchor_block_header: [BlockHeader](../../protocol_types/abis/block_header/struct.BlockHeader.html)` `private_logs: [BoundedVec](../../std/collections/bounded_vec/struct.BoundedVec.html)<[Counted](../../protocol_types/side_effect/counted/struct.Counted.html)<[PrivateLogData](../../protocol_types/abis/private_log/struct.PrivateLogData.html)>, 16>` `contract_class_logs_hashes: [BoundedVec](../../std/collections/bounded_vec/struct.BoundedVec.html)<[Counted](../../protocol_types/side_effect/counted/struct.Counted.html)<[LogHash](../../protocol_types/abis/log_hash/struct.LogHash.html)>, 1>` `last_key_validation_requests: [[Option](../../std/option/struct.Option.html)<[KeyValidationRequest](../../protocol_types/abis/validation_requests/key_validation_request/struct.KeyValidationRequest.html)>; 4]` `expected_non_revertible_side_effect_counter: [u32](../../std/primitive.u32.html)` `expected_revertible_side_effect_counter: [u32](../../std/primitive.u32.html)` ## Implementations ### `impl [PrivateContext](../../noir_aztec/context/struct.PrivateContext.html)` `pub fn [new](#new)(inputs: [PrivateContextInputs](../../noir_aztec/context/inputs/struct.PrivateContextInputs.html), args_hash: [Field](../../std/primitive.Field.html)) -> Self` `pub fn [maybe_msg_sender](#maybe_msg_sender)(self) -> [Option](../../std/option/struct.Option.html)<[AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html)>` Returns the contract address that initiated this function call. This is similar to `msg.sender` in Solidity (hence the name). Important Note: Since Aztec doesn't have a concept of an EoA (Externally-owned Account), the msg_sender is "none" for the first function call of every transaction. The first function call of a tx is likely to be a call to the user's account contract, so this quirk will most often be handled by account contract developers. #### Returns - `Option` - The address of the smart contract that called this function (be it an app contract or a user's account contract). Returns `Option::none` for the first function call of the tx. No other private function calls in the tx will have a `none` msg_sender, but public function calls might (see the PublicContext). `pub fn [this_address](#this_address)(self) -> [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html)` Returns the contract address of the current function being executed. This is equivalent to `address(this)` in Solidity (hence the name). Use this to identify the current contract's address, commonly needed for access control or when interacting with other contracts. #### Returns - `AztecAddress` - The contract address of the current function being executed. `pub fn [chain_id](#chain_id)(self) -> [Field](../../std/primitive.Field.html)` Returns the chain ID of the current network. This is similar to `block.chainid` in Solidity. Returns the unique identifier for the blockchain network this transaction is executing on. Helps prevent cross-chain replay attacks. Useful if implementing multi-chain contract logic. #### Returns - `Field` - The chain ID as a field element `pub fn [version](#version)(self) -> [Field](../../std/primitive.Field.html)` Returns the Aztec protocol version that this transaction is executing under. Different versions may have different rules, opcodes, or cryptographic primitives. This is similar to how Ethereum has different EVM versions. Useful for forward/backward compatibility checks Not to be confused with contract versions; this is the protocol version. #### Returns - `Field` - The protocol version as a field element `pub fn [gas_settings](#gas_settings)(self) -> [GasSettings](../../protocol_types/abis/gas_settings/struct.GasSettings.html)` Returns the gas settings for the current transaction. This provides information about gas limits and pricing for the transaction, similar to `tx.gasprice` and gas limits in Ethereum. However, Aztec has a more sophisticated gas model with separate accounting for L2 computation and data availability (DA) costs. #### Returns - `GasSettings` - Struct containing gas limits and fee information `pub fn [selector](#selector)(self) -> [FunctionSelector](../../protocol_types/abis/function_selector/struct.FunctionSelector.html)` Returns the function selector of the currently executing function. Low-level function: Ordinarily, smart contract developers will not need to access this. This is similar to `msg.sig` in Solidity, which returns the first 4 bytes of the function signature. In Aztec, the selector uniquely identifies which function within the contract is being called. #### Returns - `FunctionSelector` - The 4-byte function identifier #### Advanced Only #[external("private")] functions have a function selector as a protocol- enshrined concept. The function selectors of private functions are baked into the preimage of the contract address, and are used by the protocol's kernel circuits to identify each private function and ensure the correct one is being executed. Used internally for function dispatch and call verification. `pub fn [get_args_hash](#get_args_hash)(self) -> [Field](../../std/primitive.Field.html)` Returns the hash of the arguments passed to the current function. Very low-level function: You shouldn't need to call this. The #[external("private")] macro calls this, and it makes the arguments neatly available to the body of your private function. #### Returns - `Field` - Hash of the function arguments #### Advanced - Arguments are hashed to reduce proof size and verification time - Enables efficient argument passing in recursive function calls - The hash can be used to retrieve the original arguments from the PXE. `pub fn [push_note_hash](#push_note_hash)(&mut self, note_hash: [Field](../../std/primitive.Field.html))` Pushes a new note_hash to the Aztec blockchain's global Note Hash Tree (a state tree). A note_hash is a commitment to a piece of private state. Low-level function: Ordinarily, smart contract developers will not need to manually call this. Aztec-nr's state variables (see `../state_vars/`) are designed to understand when to create and push new note hashes. #### Arguments - `note_hash` - The new note_hash. #### Advanced From here, the protocol's kernel circuits will take over and insert the note_hash into the protocol's "note hash tree" (in the Base Rollup circuit). Before insertion, the protocol will: - "Silo" the `note_hash` with the contract address of this function, to yield a `siloed_note_hash`. This prevents state collisions between different smart contracts. - Ensure uniqueness of the `siloed_note_hash`, to prevent Faerie-Gold attacks, by hashing the `siloed_note_hash` with a unique value, to yield a `unique_siloed_note_hash` (see the protocol spec for more). In addition to calling this function, aztec-nr provides the contents of the newly-created note to the PXE, via the `notify_created_note` oracle. Advanced users might occasionally wish to push data to the context > directly for lower-level control. If you find yourself doing this, > please open an issue on GitHub to describe your use case: it might be > that new functionality should be added to aztec-nr. `pub fn [push_nullifier](#push_nullifier)(&mut self, nullifier: [Field](../../std/primitive.Field.html))` Creates a new [nullifier](../../noir_aztec/nullifier/index.html). #### Safety This is a low-level function that must be used with great care to avoid subtle corruption of contract state. Instead of calling this function, consider using the higher-level [`crate::state_vars::SingleUseClaim`](../../noir_aztec/state_vars/struct.SingleUseClaim.html). In particular, callers must ensure all nullifiers created by a contract are properly domain-separated, so that unrelated components don't interfere with one another (e.g. a transaction nullifier accidentally marking a variable as initialized). Only [`PrivateContext::push_nullifier_for_note_hash`](../../noir_aztec/context/struct.PrivateContext.html#push_nullifier_for_note_hash) should be used for note nullifiers, never this one. #### Advanced The raw `nullifier` is not what is inserted into the Aztec state tree: it will be first siloed by contract address via [`crate::protocol::hash::compute_siloed_nullifier`](../../protocol_types/hash/fn.compute_siloed_nullifier.html) in order to prevent accidental or malicious interference of nullifiers from different contracts. `pub fn [push_nullifier_for_note_hash](#push_nullifier_for_note_hash)( &mut self, nullifier: [Field](../../std/primitive.Field.html), nullification_note_hash: [Field](../../std/primitive.Field.html), )` Creates a new [nullifier](../../noir_aztec/nullifier/index.html) associated with a note. This is a variant of [`PrivateContext::push_nullifier`](../../noir_aztec/context/struct.PrivateContext.html#push_nullifier) that is used for note nullifiers, i.e. nullifiers that correspond to a note. If a note and its nullifier are created in the same transaction, then the private kernels will 'squash' these values, deleting them both as if they never existed and reducing transaction fees. The `nullification_note_hash` must be the result of calling [`crate::note::utils::compute_confirmed_note_hash_for_nullification`](../../noir_aztec/note/utils/fn.compute_confirmed_note_hash_for_nullification.html) for pending notes, and `0` for settled notes (which cannot be squashed). #### Safety This is a low-level function that must be used with great care to avoid subtle corruption of contract state. Instead of calling this function, consider using the higher-level [`crate::note::lifecycle::destroy_note`](../../noir_aztec/note/lifecycle/fn.destroy_note.html). The precautions listed for [`PrivateContext::push_nullifier`](../../noir_aztec/context/struct.PrivateContext.html#push_nullifier) apply here as well, and callers should additionally ensure `nullification_note_hash` corresponds to a note emitted by this contract, with its hash computed in the same transaction execution phase as the call to this function. Finally, only this function should be used for note nullifiers, never [`PrivateContext::push_nullifier`](../../noir_aztec/context/struct.PrivateContext.html#push_nullifier). Failure to do these things can result in unprovable contexts, accidental deletion of notes, or double-spend attacks. `pub fn [get_anchor_block_header](#get_anchor_block_header)(self) -> [BlockHeader](../../protocol_types/abis/block_header/struct.BlockHeader.html)` Returns the anchor block header - the historical block header that this private function is reading from. A private function CANNOT read from the "current" block header, but must read from some older block header, because as soon as private function execution begins (asynchronously, on a user's device), the public state of the chain (the "current state") will have progressed forward. #### Returns - `BlockHeader` - The anchor block header. #### Advanced - All private functions of a tx read from the same anchor block header. - The protocol asserts that the `expiration_timestamp` of every tx is at most 24 hours beyond the timestamp of the tx's chosen anchor block header. This enables the network's nodes to safely prune old txs from the mempool. Therefore, the chosen block header must be one from within the last 24 hours. `pub fn [get_block_header_at](#get_block_header_at)(self, block_number: [u32](../../std/primitive.u32.html)) -> [BlockHeader](../../protocol_types/abis/block_header/struct.BlockHeader.html)` Returns the header of any historical block at or before the anchor block. This enables private contracts to access information from even older blocks than the anchor block header. Useful for time-based contract logic that needs to compare against multiple historical points. #### Arguments - `block_number` - The block number to retrieve (must be <= anchor block number) #### Returns - `BlockHeader` - The header of the requested historical block #### Advanced This function uses an oracle to fetch block header data from the user's PXE. Depending on how much blockchain data the user's PXE has been set up to store, this might require a query from the PXE to another Aztec node to get the data. > This is generally true of all oracle getters (see `../oracle`). Each block header gets hashed and stored as a leaf in the protocol's Archive Tree. In fact, the i-th block header gets stored at the i-th leaf index of the Archive Tree. Behind the scenes, this `get_block_header_at` function will add Archive Tree merkle-membership constraints (~3k) to your smart contract function's circuit, to prove existence of the block header in the Archive Tree. Note: we don't do any caching, so avoid making duplicate calls for the same block header, because each call will add duplicate constraints. Calling this function is more expensive (constraint-wise) than getting the anchor block header (via `get_block_header`). This is because the anchor block's merkle membership proof is handled by Aztec's protocol circuits, and is only performed once for the entire tx because all private functions of a tx share a common anchor block header. Therefore, the cost (constraint-wise) of calling `get_block_header` is effectively free. `pub fn [set_return_hash](#set_return_hash)(&mut self, serialized_return_values: [[Field](../../std/primitive.Field.html); N])` Sets the hash of the return values for this private function. Very low-level function: this is called by the #[external("private")] macro. #### Arguments - `serialized_return_values` - The serialized return values as a field array `pub fn [finish](#finish)(self) -> [PrivateCircuitPublicInputs](../../protocol_types/abis/private_circuit_public_inputs/struct.PrivateCircuitPublicInputs.html)` Builds the PrivateCircuitPublicInputs for this private function, to ensure compatibility with the protocol's kernel circuits. Very low-level function: This function is automatically called by the #[external("private")] macro. `pub fn [set_as_fee_payer](#set_as_fee_payer)(&mut self)` Designates this contract as the fee payer for the transaction. Unlike Ethereum, where the transaction sender always pays fees, Aztec allows any contract to voluntarily pay transaction fees. This enables patterns like sponsored transactions or fee abstraction where users don't need to hold fee-juice themselves. (Fee juice is a fee-paying asset for Aztec). Only one contract per transaction can declare itself as the fee payer, and it must have sufficient fee-juice balance (>= the gas limits specified in the TxContext) by the time we reach the public setup phase of the tx. `pub fn [in_revertible_phase](#in_revertible_phase)(&mut self) -> [bool](../../std/primitive.bool.html)` `pub fn [end_setup](#end_setup)(&mut self)` Declares the end of the "setup phase" of this tx. Only one function per tx can declare the end of the setup phase. Niche function: Only wallet developers and paymaster contract developers (aka Fee-payment contracts) will need to make use of this function. Aztec supports a three-phase execution model: setup, app logic, teardown. The phases exist to enable a fee payer to take on the risk of paying a transaction fee, safe in the knowledge that their payment (in whatever token or method the user chooses) will succeed, regardless of whether the app logic will succeed. The "setup" phase enables such a payment to be made, because the setup phase cannot revert: a reverting function within the setup phase would result in an invalid block which cannot be proven. Any side-effects generated during that phase are guaranteed to be inserted into Aztec's state trees (except for squashed notes & nullifiers, of course). Even though the end of the setup phase is declared within a private function, you might have noticed that public functions can also execute within the setup phase. This is because any public function calls which were enqueued within the setup phase by a private function are considered part of the setup phase. #### Advanced - Sets the minimum revertible side effect counter of this tx to be the PrivateContext's current side effect counter. `pub fn [set_expiration_timestamp](#set_expiration_timestamp)(&mut self, expiration_timestamp: [u64](../../std/primitive.u64.html))` Sets a deadline (an "include-by timestamp") for when this transaction must be included in a block. Other functions in this tx might call this setter with differing values for the include-by timestamp. To ensure that all functions' deadlines are met, the minimum of all these include-by timestamps will be exposed when this tx is submitted to the network. If the transaction is not included in a block by its include-by timestamp, it becomes invalid and it will never be included. This expiry timestamp is publicly visible. See the "Advanced" section for privacy concerns. #### Arguments - `expiration_timestamp` - Unix timestamp (seconds) deadline for inclusion. The include-by timestamp of this tx will be at most the timestamp specified. #### Advanced - If multiple functions set differing `expiration_timestamp`s, the kernel circuits will set it to be the minimum of the two. This ensures the tx expiry requirements of all functions in the tx are met. - Rollup circuits will reject expired txs. - The protocol enforces that all transactions must be included within 24 hours of their chosen anchor block's timestamp, to enable safe mempool pruning. - The DelayedPublicMutable design makes heavy use of this functionality, to enable private functions to read public state. - A sophisticated Wallet should cleverly set an include-by timestamp to improve the privacy of the user and the network as a whole. For example, if a contract interaction sets include-by to some publicly-known value (e.g. the time when a contract upgrades), then the wallet might wish to set an even lower one to avoid revealing that this tx is interacting with said contract. Ideally, all wallets should standardize on an approach in order to provide users with a large privacy set -- although the exact approach will need to be discussed. Wallets that deviate from a standard might accidentally reveal which wallet each transaction originates from. `pub fn [assert_note_exists](#assert_note_exists)(&mut self, note_existence_request: [NoteExistenceRequest](../../noir_aztec/context/struct.NoteExistenceRequest.html))` Asserts that a note has been created. This function will cause the transaction to fail unless the requested note exists. This is the preferred mechanism for performing this check, and the only one that works for pending notes. #### Pending Notes Both settled notes (created in prior transactions) and pending notes (created in the current transaction) will be considered by this function. Pending notes must have been created before this call is made for the check to pass. #### Historical Notes If you need to assert that a note existed by some specific block in the past, instead of simply proving that it exists by the current anchor block, use [`crate::history::note::assert_note_existed_by`](../../noir_aztec/history/note/fn.assert_note_existed_by.html) instead. #### Cost This uses up one of the call's kernel note hash read requests, which are limited. Like all kernel requests, proving time costs are only incurred when the total number of requests exceeds the kernel's capacity, requiring an additional invocation of the kernel reset circuit. `pub fn [assert_nullifier_exists](#assert_nullifier_exists)( &mut self, nullifier_existence_request: [NullifierExistenceRequest](../../noir_aztec/context/struct.NullifierExistenceRequest.html), )` Asserts that a nullifier has been emitted. This function will cause the transaction to fail unless the requested nullifier exists. This is the preferred mechanism for performing this check, and the only one that works for pending nullifiers. #### Pending Nullifiers Both settled nullifiers (emitted in prior transactions) and pending nullifiers (emitted in the current transaction) will be considered by this function. Pending nullifiers must have been emitted before this call is made for the check to pass. #### Historical Nullifiers If you need to assert that a nullifier existed by some specific block in the past, instead of simply proving that it exists by the current anchor block, use [`crate::history::nullifier::assert_nullifier_existed_by`](../../noir_aztec/history/nullifier/fn.assert_nullifier_existed_by.html) instead. #### Public vs Private In general, it is unsafe to check for nullifier non-existence in private, as that will not consider the possibility of the nullifier having been emitted in any transaction between the anchor block and the inclusion block. Private functions instead prove existence via this function and 'prove' non-existence by emitting the nullifer, which would cause the transaction to fail if the nullifier existed. This is not the case in public functions, which do have access to the tip of the blockchain and so can reliably prove whether a nullifier exists or not via [`crate::context::public_context::PublicContext::nullifier_exists_unsafe`](../../noir_aztec/context/struct.PublicContext.html#nullifier_exists_unsafe). #### Cost This uses up one of the call's kernel nullifier read requests, which are limited. Like all kernel requests, proving time costs are only incurred when the total number of requests exceeds the kernel's capacity, requiring an additional invocation of the kernel reset circuit. `pub fn [request_nhk_app](#request_nhk_app)(&mut self, npk_m_hash: [Field](../../std/primitive.Field.html)) -> [Field](../../std/primitive.Field.html)` Requests the app-siloed nullifier hiding key (nhk_app) for the given (hashed) master nullifier public key (npk_m), from the user's PXE. Advanced function: Only needed if you're designing your own notes and/or nullifiers. Contracts are not allowed to compute nullifiers for other contracts, as that would let them read parts of their private state. Because of this, a contract is only given an "app-siloed key", which is constructed by hashing the user's master nullifier hiding key with the contract's address. However, because contracts cannot be trusted with a user's master nullifier hiding key (because we don't know which contracts are honest or malicious), the PXE refuses to provide any master secret keys to any app smart contract function. This means app functions are unable to prove that the derivation of an app-siloed nullifier hiding key has been computed correctly. Instead, an app function can request to the kernel (via `request_nhk_app`) that it validates the siloed derivation, since the kernel has been vetted to not leak any master secret keys. A common nullification scheme is to inject a nullifier hiding key into the preimage of a nullifier, to make the nullifier deterministic but random-looking. This function enables that flow. #### Arguments - `npk_m_hash` - A hash of the master nullifier public key of the user whose PXE is executing this function. #### Returns - The app-siloed nullifier hiding key that corresponds to the given `npk_m_hash`. `pub fn [request_ovsk_app](#request_ovsk_app)(&mut self, ovpk_m_hash: [Field](../../std/primitive.Field.html)) -> [Field](../../std/primitive.Field.html)` Requests the app-siloed nullifier secret key (nsk_app) for the given (hashed) master nullifier public key (npk_m), from the user's PXE. See `request_nsk_app` and `request_sk_app` for more info. The intention of the "outgoing" keypair is to provide a second secret key for all of a user's outgoing activity (i.e. for notes that a user creates, as opposed to notes that a user receives from others). The separation of incoming and outgoing data was a distinction made by zcash, with the intention of enabling a user to optionally share with a 3rd party a controlled view of only incoming or outgoing notes. Similar functionality of sharing select data can be achieved with offchain zero-knowledge proofs. It is up to an app developer whether they choose to make use of a user's outgoing keypair within their application logic, or instead simply use the same keypair (the address keypair (which is effectively the same as the "incoming" keypair)) for all incoming & outgoing messages to a user. Currently, all of the exposed encryption functions in aztec-nr ignore the outgoing viewing keys, and instead encrypt all note logs and event logs to a user's address public key. #### Arguments - `ovpk_m_hash` - Hash of the outgoing viewing public key master #### Returns - The application-specific outgoing viewing secret key `pub fn [message_portal](#message_portal)(&mut self, recipient: [EthAddress](../../protocol_types/address/eth_address/struct.EthAddress.html), content: [Field](../../std/primitive.Field.html))` Sends an "L2 -> L1 message" from this function (Aztec, L2) to a smart contract on Ethereum (L1). L1 contracts which are designed to send/receive messages to/from Aztec are called "Portal Contracts". Common use cases include withdrawals, cross-chain asset transfers, and triggering L1 actions based on L2 state changes. The message will be inserted into an Aztec "Outbox" contract on L1, when this transaction's block is proposed to L1. Sending the message will not result in any immediate state changes in the target portal contract. The message will need to be manually consumed from the Outbox through a separate Ethereum transaction: a user will need to call a function of the portal contract -- a function specifically designed to make a call to the Outbox to consume the message. The message will only be available for consumption once the epoch proof has been submitted. Given that there are multiple Aztec blocks within an epoch, it might take some time for this epoch proof to be submitted -- especially if the block was near the start of an epoch. #### Arguments - `recipient` - Ethereum address that will receive the message - `content` - Message content (32 bytes as a Field element). This content has a very specific layout. docs:start:context_message_portal `pub fn [consume_l1_to_l2_message](#consume_l1_to_l2_message)( &mut self, content: [Field](../../std/primitive.Field.html), secret: [Field](../../std/primitive.Field.html), sender: [EthAddress](../../protocol_types/address/eth_address/struct.EthAddress.html), leaf_index: [Field](../../std/primitive.Field.html), )` Consumes a message sent from Ethereum (L1) to Aztec (L2). Common use cases include token bridging, cross-chain governance, and triggering L2 actions based on L1 events. Use this function if you only want the message to ever be "referred to" once. Once consumed using this method, the message cannot be consumed again, because a nullifier is emitted. If your use case wants for the message to be read unlimited times, then you can always read any historic message from the L1-to-L2 messages tree; messages never technically get deleted from that tree. The message will first be inserted into an Aztec "Inbox" smart contract on L1. Sending the message will not result in any immediate state changes in the target L2 contract. The message will need to be manually consumed by the target contract through a separate Aztec transaction. The message will not be available for consumption immediately. Messages get copied over from the L1 Inbox to L2 by the next Proposer in batches. So you will need to wait until the messages are copied before you can consume them. #### Arguments - `content` - The message content that was sent from L1 - `secret` - Secret value used for message privacy (if needed) - `sender` - Ethereum address that sent the message - `leaf_index` - Index of the message in the L1-to-L2 message tree #### Advanced Validates message existence in the L1-to-L2 message tree and nullifies the message to prevent double-consumption. `pub fn [emit_private_log_unsafe](#emit_private_log_unsafe)(&mut self, tag: [Field](../../std/primitive.Field.html), log: [[Field](../../std/primitive.Field.html); 15], length: [u32](../../std/primitive.u32.html))` 👎 Deprecated: use `emit_private_log_vec_unsafe` instead Emits a private log (an array of Fields) that will be published to an Ethereum blob. Private logs are intended for the broadcasting of ciphertexts: that is, encrypted events or encrypted note contents. Since the data in the logs is meant to be encrypted, private_logs are broadcast to publicly-visible Ethereum blobs. The intended recipients of such encrypted messages can then discover and decrypt these encrypted logs using their viewing secret key. (See `../messages/discovery` for more details). Important note: This function DOES NOT do any encryption of the input `log` fields. This function blindly publishes whatever input `log` data is fed into it, so the caller of this function should have already performed the encryption, and the `log` should be the result of that encryption. The protocol does not dictate what encryption scheme should be used: a smart contract developer can choose whatever encryption scheme they like. Aztec-nr includes some off-the-shelf encryption libraries that developers might wish to use, for convenience. These libraries not only encrypt a plaintext (to produce a ciphertext); they also prepend the ciphertext with a `tag` and `ephemeral public key` for easier message discovery. This is a very dense topic, and we will be writing more libraries and docs soon. Currently, AES128 CBC encryption is the main scheme included in > aztec.nr. > We are currently making significant changes to the interfaces of the > encryption library. In some niche use cases, an app might be tempted to publish un-encrypted data via a private log, because public logs are not available to private functions. Be warned that emitting public data via private logs is strongly discouraged, and is considered a "privacy anti-pattern", because it reveals identifiable information about which function has been executed. A tx which leaks such information does not contribute to the privacy set of the network. - Unlike `emit_raw_note_log_unsafe`, this log is not tied to any specific note #### Arguments - `tag` - A tag placed at `fields[0]` of the emitted log. Used by recipients and nodes to identify and filter for relevant logs without scanning all of them. - `log` - The log data that will be publicly broadcast (so make sure it's already been encrypted before you call this function). Private logs are bounded in size (`PRIVATE_LOG_CIPHERTEXT_LEN`), to encourage all logs from all smart contracts look identical. - `length` - The actual length of `log` (measured in number of Fields). Although the input log has a max size of `PRIVATE_LOG_CIPHERTEXT_LEN`, the latter values of the array might all be 0's for small logs. This `length` should reflect the trimmed length of the array. The protocol's kernel circuits can then append random fields as "padding" after the `length`, so that the logs of this smart contract look indistinguishable from (the same length as) the logs of all other applications. It's up to wallets how much padding to apply, so ideally all wallets should agree on standards for this. #### Safety The `tag` should be domain-separated (e.g. via [`crate::protocol::hash::compute_log_tag`](../../protocol_types/hash/fn.compute_log_tag.html)) to prevent collisions between logs from different sources. Without domain separation, two unrelated log types that happen to share a raw tag value become indistinguishable. Prefer the higher-level APIs ([`crate::messages::message_delivery::MessageDelivery`](../../noir_aztec/messages/message_delivery/global.MessageDelivery.html) for messages, `self.emit(event)` for events) which handle tagging automatically. `pub fn [emit_private_log_vec_unsafe](#emit_private_log_vec_unsafe)(&mut self, tag: [Field](../../std/primitive.Field.html), log: [BoundedVec](../../std/collections/bounded_vec/struct.BoundedVec.html)<[Field](../../std/primitive.Field.html), 15>)` `BoundedVec`-based variant of [`emit_private_log_unsafe`](../../noir_aztec/context/struct.PrivateContext.html#emit_private_log_unsafe). See [`emit_private_log_unsafe`](../../noir_aztec/context/struct.PrivateContext.html#emit_private_log_unsafe) for the full description of private log semantics. `pub fn [emit_raw_note_log_unsafe](#emit_raw_note_log_unsafe)( &mut self, tag: [Field](../../std/primitive.Field.html), log: [[Field](../../std/primitive.Field.html); 15], length: [u32](../../std/primitive.u32.html), note_hash_counter: [u32](../../std/primitive.u32.html), )` 👎 Deprecated: use `emit_raw_note_log_vec_unsafe` instead Emits a private log that is explicitly tied to a newly-emitted note_hash, to convey to the kernel: "this log relates to this note". This linkage is important in case the note gets squashed (due to being read later in this same tx), since we can then squash the log as well. See `emit_private_log_unsafe` for more info about private log emission. #### Arguments - `tag` - A tag placed at `fields[0]`. See `emit_private_log_unsafe`. - `log` - The log data as an array of Field elements - `length` - The actual length of the `log` (measured in number of Fields). - `note_hash_counter` - The side-effect counter that was assigned to the new note_hash when it was pushed to this `PrivateContext`. Important: If your application logic requires the log to always be emitted regardless of note squashing, consider using `emit_private_log_unsafe` instead, or emitting additional events. #### Safety Same as [`PrivateContext::emit_private_log_unsafe`](../../noir_aztec/context/struct.PrivateContext.html#emit_private_log_unsafe): the `tag` should be domain-separated. `pub fn [emit_raw_note_log_vec_unsafe](#emit_raw_note_log_vec_unsafe)( &mut self, tag: [Field](../../std/primitive.Field.html), log: [BoundedVec](../../std/collections/bounded_vec/struct.BoundedVec.html)<[Field](../../std/primitive.Field.html), 15>, note_hash_counter: [u32](../../std/primitive.u32.html), )` `BoundedVec`-based variant of [`emit_raw_note_log_unsafe`](../../noir_aztec/context/struct.PrivateContext.html#emit_raw_note_log_unsafe). See [`emit_raw_note_log_unsafe`](../../noir_aztec/context/struct.PrivateContext.html#emit_raw_note_log_unsafe) for the full description of note-tied private log semantics. `pub fn [emit_contract_class_log](#emit_contract_class_log)(&mut self, log: [[Field](../../std/primitive.Field.html); N])` Emits large data blobs. This reuses the Contract Class Log channel to emit blobs of up to [`CONTRACT_CLASS_LOG_SIZE_IN_FIELDS`](../../protocol_types/constants/global.CONTRACT_CLASS_LOG_SIZE_IN_FIELDS.html). #### Privacy The address of the contract emitting these blobs is revelead. `pub fn [call_private_function](#call_private_function)( &mut self, contract_address: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html), function_selector: [FunctionSelector](../../protocol_types/abis/function_selector/struct.FunctionSelector.html), args: [[Field](../../std/primitive.Field.html); ArgsCount], ) -> [ReturnsHash](../../noir_aztec/context/struct.ReturnsHash.html)` Calls a private function on another contract (or the same contract). Very low-level function. #### Arguments - `contract_address` - Address of the contract containing the function - `function_selector` - 4-byte identifier of the function to call - `args` - Array of arguments to pass to the called function #### Returns - `ReturnsHash` - Hash of the called function's return values. Use `.get_preimage()` to extract the actual return values. This enables contracts to interact with each other while maintaining privacy. This "composability" of private contract functions is a key feature of the Aztec network. If a user's transaction includes multiple private function calls, then by the design of Aztec, the following information will remain private[1]: - The function selectors and contract addresses of all private function calls will remain private, so an observer of the public mempool will not be able to look at a tx and deduce which private functions have been executed. - The arguments and return values of all private function calls will remain private. - The person who initiated the tx will remain private. - The notes and nullifiers and private logs that are emitted by all private function calls will (if designed well) not leak any user secrets, nor leak which functions have been executed. [1] Caveats: Some of these privacy guarantees depend on how app developers design their smart contracts. Some actions can leak information, such as: - Calling an internal public function. - Calling a public function and not setting msg_sender to Option::none (feature not built yet - see github). - Calling any public function will always leak details about the nature of the transaction, so devs should be careful in their contract designs. If it can be done in a private function, then that will give the best privacy. - Not padding the side-effects of a tx to some standardized, uniform size. The kernel circuits can take hints to pad side-effects, so a wallet should be able to request for a particular amount of padding. Wallets should ideally agree on some standard. - Padding should include: - Padding the lengths of note & nullifier arrays - Padding private logs with random fields, up to some standardized size. See also: [https://docs.aztec.network/developers/resources/considerations/privacy_considerations](https://docs.aztec.network/developers/resources/considerations/privacy_considerations) #### Advanced - The call is added to the private call stack and executed by kernel circuits after this function completes - The called function can modify its own contract's private state - Side effects from the called function are included in this transaction - The call inherits the current transaction's context and gas limits `pub fn [static_call_private_function](#static_call_private_function)( &mut self, contract_address: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html), function_selector: [FunctionSelector](../../protocol_types/abis/function_selector/struct.FunctionSelector.html), args: [[Field](../../std/primitive.Field.html); ArgsCount], ) -> [ReturnsHash](../../noir_aztec/context/struct.ReturnsHash.html)` Makes a read-only call to a private function on another contract. This is similar to Solidity's `staticcall`. The called function cannot modify state, emit L2->L2 messages, nor emit events. Any nested calls are constrained to also be staticcalls. See `call_private_function` for more general info on private function calls. #### Arguments - `contract_address` - Address of the contract to call - `function_selector` - 4-byte identifier of the function to call - `args` - Array of arguments to pass to the called function #### Returns - `ReturnsHash` - Hash of the called function's return values. Use `.get_preimage()` to extract the actual return values. `pub fn [call_private_function_no_args](#call_private_function_no_args)( &mut self, contract_address: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html), function_selector: [FunctionSelector](../../protocol_types/abis/function_selector/struct.FunctionSelector.html), ) -> [ReturnsHash](../../noir_aztec/context/struct.ReturnsHash.html)` Calls a private function that takes no arguments. This is a convenience function for calling private functions that don't require any input parameters. It's equivalent to `call_private_function` but slightly more efficient to use when no arguments are needed. #### Arguments - `contract_address` - Address of the contract containing the function - `function_selector` - 4-byte identifier of the function to call #### Returns - `ReturnsHash` - Hash of the called function's return values. Use `.get_preimage()` to extract the actual return values. `pub fn [static_call_private_function_no_args](#static_call_private_function_no_args)( &mut self, contract_address: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html), function_selector: [FunctionSelector](../../protocol_types/abis/function_selector/struct.FunctionSelector.html), ) -> [ReturnsHash](../../noir_aztec/context/struct.ReturnsHash.html)` Makes a read-only call to a private function which takes no arguments. This combines the optimisation of `call_private_function_no_args` with the safety of `static_call_private_function`. #### Arguments - `contract_address` - Address of the contract containing the function - `function_selector` - 4-byte identifier of the function to call #### Returns - `ReturnsHash` - Hash of the called function's return values. Use `.get_preimage()` to extract the actual return values. `pub fn [call_private_function_with_args_hash](#call_private_function_with_args_hash)( &mut self, contract_address: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html), function_selector: [FunctionSelector](../../protocol_types/abis/function_selector/struct.FunctionSelector.html), args_hash: [Field](../../std/primitive.Field.html), is_static_call: [bool](../../std/primitive.bool.html), ) -> [ReturnsHash](../../noir_aztec/context/struct.ReturnsHash.html)` Low-level private function call. This is the underlying implementation used by all other private function call methods. Instead of taking raw arguments, it accepts a hash of the arguments. #### Arguments - `contract_address` - Address of the contract containing the function - `function_selector` - 4-byte identifier of the function to call - `args_hash` - Pre-computed hash of the function arguments - `is_static_call` - Whether this should be a read-only call #### Returns - `ReturnsHash` - Hash of the called function's return values `pub fn [call_public_function](#call_public_function)( &mut self, contract_address: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html), function_selector: [FunctionSelector](../../protocol_types/abis/function_selector/struct.FunctionSelector.html), args: [[Field](../../std/primitive.Field.html); ArgsCount], hide_msg_sender: [bool](../../std/primitive.bool.html), )` Enqueues a call to a public function to be executed later. Unlike private functions which execute immediately on the user's device, public function calls are "enqueued" and executed some time later by a block proposer. This means a public function cannot return any values back to a private function, because by the time the public function is being executed, the private function which called it has already completed execution. (In fact, the private function has been executed and proven, along with all other private function calls of the user's tx. A single proof of the tx has been submitted to the Aztec network, and some time later a proposer has picked the tx up from the mempool and begun executing all of the enqueued public functions). #### Privacy warning Enqueueing a public function call is an inherently leaky action. Many interesting applications will require some interaction with public state, but smart contract developers should try to use public function calls sparingly, and carefully. Internal public function calls are especially leaky, because they completely leak which private contract made the call. See also: [https://docs.aztec.network/developers/resources/considerations/privacy_considerations](https://docs.aztec.network/developers/resources/considerations/privacy_considerations) #### Arguments - `contract_address` - Address of the contract containing the function - `function_selector` - 4-byte identifier of the function to call - `args` - Array of arguments to pass to the public function - `hide_msg_sender` - the called function will see a "null" value for `msg_sender` if set to `true` `pub fn [static_call_public_function](#static_call_public_function)( &mut self, contract_address: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html), function_selector: [FunctionSelector](../../protocol_types/abis/function_selector/struct.FunctionSelector.html), args: [[Field](../../std/primitive.Field.html); ArgsCount], hide_msg_sender: [bool](../../std/primitive.bool.html), )` Enqueues a read-only call to a public function. This is similar to Solidity's `staticcall`. The called function cannot modify state or emit events. Any nested calls are constrained to also be staticcalls. See also `call_public_function` for more important information about making private -> public function calls. #### Arguments - `contract_address` - Address of the contract containing the function - `function_selector` - 4-byte identifier of the function to call - `args` - Array of arguments to pass to the public function - `hide_msg_sender` - the called function will see a "null" value for `msg_sender` if set to `true` `pub fn [call_public_function_no_args](#call_public_function_no_args)( &mut self, contract_address: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html), function_selector: [FunctionSelector](../../protocol_types/abis/function_selector/struct.FunctionSelector.html), hide_msg_sender: [bool](../../std/primitive.bool.html), )` Enqueues a call to a public function that takes no arguments. This is an optimisation for calling public functions that don't take any input parameters. It's otherwise equivalent to `call_public_function`. #### Arguments - `contract_address` - Address of the contract containing the function - `function_selector` - 4-byte identifier of the function to call - `hide_msg_sender` - the called function will see a "null" value for `msg_sender` if set to `true` `pub fn [static_call_public_function_no_args](#static_call_public_function_no_args)( &mut self, contract_address: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html), function_selector: [FunctionSelector](../../protocol_types/abis/function_selector/struct.FunctionSelector.html), hide_msg_sender: [bool](../../std/primitive.bool.html), )` Enqueues a read-only call to a public function with no arguments. This combines the optimisation of `call_public_function_no_args` with the safety of `static_call_public_function`. #### Arguments - `contract_address` - Address of the contract containing the function - `function_selector` - 4-byte identifier of the function to call - `hide_msg_sender` - the called function will see a "null" value for `msg_sender` if set to `true` `pub fn [call_public_function_with_calldata_hash](#call_public_function_with_calldata_hash)( &mut self, contract_address: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html), calldata_hash: [Field](../../std/primitive.Field.html), is_static_call: [bool](../../std/primitive.bool.html), hide_msg_sender: [bool](../../std/primitive.bool.html), )` Low-level public function call. This is the underlying implementation used by all other public function call methods. Instead of taking raw arguments, it accepts a hash of the arguments. Advanced function: Most developers should use `call_public_function` or `static_call_public_function` instead. This function is exposed for performance optimization and advanced use cases. #### Arguments - `contract_address` - Address of the contract containing the function - `calldata_hash` - Hash of the function calldata - `is_static_call` - Whether this should be a read-only call - `hide_msg_sender` - the called function will see a "null" value for `msg_sender` if set to `true` `pub fn [set_public_teardown_function](#set_public_teardown_function)( &mut self, contract_address: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html), function_selector: [FunctionSelector](../../protocol_types/abis/function_selector/struct.FunctionSelector.html), args: [[Field](../../std/primitive.Field.html); ArgsCount], hide_msg_sender: [bool](../../std/primitive.bool.html), )` Enqueues a public function call, and designates it to be the teardown function for this tx. Only one teardown function call can be made by a tx. Niche function: Only wallet developers and paymaster contract developers (aka Fee-payment contracts) will need to make use of this function. Aztec supports a three-phase execution model: setup, app logic, teardown. The phases exist to enable a fee payer to take on the risk of paying a transaction fee, safe in the knowledge that their payment (in whatever token or method the user chooses) will succeed, regardless of whether the app logic will succeed. The "setup" phase ensures the fee payer has sufficient balance to pay the proposer their fees. The teardown phase is primarily intended to: calculate exactly how much the user owes, based on gas consumption, and refund the user any change. Note: in some cases, the cost of refunding the user (i.e. DA costs of tx side-effects) might exceed the refund amount. For app logic with fairly stable and predictable gas consumption, a material refund amount is unlikely. For app logic with unpredictable gas consumption, a refund might be important to the user (e.g. if a hefty function reverts very early). Wallet/FPC/Paymaster developers should be mindful of this. #### Arguments - `contract_address` - Address of the contract containing the teardown function - `function_selector` - 4-byte identifier of the function to call - `args` - An array of fields to pass to the function. - `hide_msg_sender` - the called function will see a "null" value for `msg_sender` if set to `true` `pub fn [set_public_teardown_function_with_calldata_hash](#set_public_teardown_function_with_calldata_hash)( &mut self, contract_address: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html), calldata_hash: [Field](../../std/primitive.Field.html), is_static_call: [bool](../../std/primitive.bool.html), hide_msg_sender: [bool](../../std/primitive.bool.html), )` Low-level function to set the public teardown function. This is the underlying implementation for setting the teardown function call that will execute at the end of the transaction. Instead of taking raw arguments, it accepts a hash of the arguments. Advanced function: Most developers should use `set_public_teardown_function` instead. #### Arguments - `contract_address` - Address of the contract containing the teardown function - `calldata_hash` - Hash of the function calldata - `is_static_call` - Whether this should be a read-only call - `hide_msg_sender` - the called function will see a "null" value for `msg_sender` if set to `true` ## Trait implementations ### `impl [Empty](../../protocol_types/traits/trait.Empty.html) for [PrivateContext](../../noir_aztec/context/struct.PrivateContext.html)` `pub fn empty() -> Self` `pub fn is_empty(self) -> [bool](../../std/primitive.bool.html)` `pub fn assert_empty(self, msg: [str](../../std/primitive.str.html))` ### `impl [Eq](../../std/cmp/trait.Eq.html) for [PrivateContext](../../noir_aztec/context/struct.PrivateContext.html)` `pub fn eq(_self: Self, _other: Self) -> [bool](../../std/primitive.bool.html)` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/context/struct.PublicContext.html # Struct PublicContext ``` pub struct PublicContext { pub args_hash: [Option](../../std/option/struct.Option.html)<[Field](../../std/primitive.Field.html)>, pub compute_args_hash: fn() -> [Field](../../std/primitive.Field.html), } ``` ## PublicContext The main interface between an #[external("public")] function and the Aztec blockchain. An instance of the PublicContext is initialized automatically at the outset of every public function, within the #[external("public")] macro, so you'll never need to consciously instantiate this yourself. The instance is always named `context`, and it will always be available within the body of every #[external("public")] function in your smart contract. Typical usage for a smart contract developer will be to call getter methods of the PublicContext. Pushing data and requests to the context is mostly handled within aztec-nr's own functions, so typically a smart contract developer won't need to call any setter methods directly. ## Responsibilities - Exposes contextual data to a public function: - Data relating to how this public function was called: - msg_sender, this_address - Data relating to the current blockchain state: - timestamp, block_number, chain_id, version - Gas and fee information - Provides state access: - Read/write public storage (key-value mapping) - Check existence of notes and nullifiers (Some patterns use notes & nullifiers to store public (not private) information) Enables consumption of L1->L2 messages. - Enables calls to other public smart contract functions: - Writes data to the blockchain: - Updates to public state variables - New public logs (for events) New L2->L1 messages - New notes & nullifiers (E.g. pushing public info to notes/nullifiers, or for completing "partial notes") ## Key Differences from Private Execution Unlike private functions -- which are executed on the user's device and which can only reference historic state -- public functions are executed by a block proposer and are executed "live" on the current tip of the chain. This means public functions can: - Read and write current public state - Immediately see the effects of earlier transactions in the same block Also, public functions are executed within a zkVM (the "AVM"), so that they can revert whilst still ensuring payment to the proposer and prover. (Private functions cannot revert: they either succeed, or they cannot be included). ## Optimising Public Functions Using the AVM to execute public functions means they compile down to "AVM bytecode" instead of the ACIR that private functions (standalone circuits) compile to. Therefore the approach to optimising a public function is fundamentally different from optimising a public function. ## Fields `args_hash: [Option](../../std/option/struct.Option.html)<[Field](../../std/primitive.Field.html)>` `compute_args_hash: fn() -> [Field](../../std/primitive.Field.html)` ## Implementations ### `impl [PublicContext](../../noir_aztec/context/struct.PublicContext.html)` `pub fn [new](#new)(compute_args_hash: fn() -> [Field](../../std/primitive.Field.html)) -> Self` Creates a new PublicContext instance. Low-level function: This is called automatically by the #[external("public")] macro, so you shouldn't need to be called directly by smart contract developers. #### Arguments - `compute_args_hash` - Function to compute the args_hash #### Returns - A new PublicContext instance `pub fn [emit_public_log_unsafe](#emit_public_log_unsafe)(_self: Self, tag: [Field](../../std/primitive.Field.html), log: T) where T: [Serialize](../../serde/serialization/trait.Serialize.html)` Emits a public log that will be visible onchain to everyone. #### Arguments - `tag` - A tag placed at `fields[0]` of the emitted log. Nodes index logs by this value, allowing clients to efficiently query for matching logs without scanning all of them. - `log` - The data to log, must implement Serialize trait. #### Safety The `tag` should be domain-separated (e.g. via [`crate::protocol::hash::compute_log_tag`](../../protocol_types/hash/fn.compute_log_tag.html)) to prevent collisions between logs from different sources. Without domain separation, two unrelated log types that happen to share a raw tag value become indistinguishable. Prefer `self.emit(event)` for events, which handles tagging automatically. `pub fn [note_hash_exists](#note_hash_exists)(_self: Self, note_hash: [Field](../../std/primitive.Field.html), leaf_index: [u64](../../std/primitive.u64.html)) -> [bool](../../std/primitive.bool.html)` Checks if a given note hash exists in the note hash tree at a particular leaf_index. #### Arguments - `note_hash` - The note hash to check for existence - `leaf_index` - The index where the note hash should be located #### Returns - `bool` - True if the note hash exists at the specified index `pub fn [l1_to_l2_msg_exists](#l1_to_l2_msg_exists)(_self: Self, msg_hash: [Field](../../std/primitive.Field.html), msg_leaf_index: [Field](../../std/primitive.Field.html)) -> [bool](../../std/primitive.bool.html)` Checks if a specific L1-to-L2 message exists in the L1-to-L2 message tree at a particular leaf index. Common use cases include token bridging, cross-chain governance, and triggering L2 actions based on L1 events. This function should be called before attempting to consume an L1-to-L2 message. #### Arguments - `msg_hash` - Hash of the L1-to-L2 message to check - `msg_leaf_index` - The index where the message should be located #### Returns - `bool` - True if the message exists at the specified index #### Advanced - Uses the AVM l1_to_l2_msg_exists opcode for tree lookup - Messages are copied from L1 Inbox to L2 by block proposers `pub fn [nullifier_exists_unsafe](#nullifier_exists_unsafe)( _self: Self, unsiloed_nullifier: [Field](../../std/primitive.Field.html), contract_address: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html), ) -> [bool](../../std/primitive.bool.html)` Returns `true` if an `unsiloed_nullifier` has been emitted by `contract_address`. Note that unsiloed nullifiers are not the actual values stored in the nullifier tree: they are first siloed via [`crate::hash::compute_siloed_nullifier`](../../protocol_types/hash/fn.compute_siloed_nullifier.html) with the emitting contract's address. #### Use Cases Nullifiers are typically used as a privacy-preserving record of a one-time action, but they can also be used to efficiently record public one-time actions as well. This is cheaper than using public storage, and has the added benefit of the nullifier being emittable from a private function. An example is to check whether a contract has been published: we emit a nullifier that is deterministic and which has a public preimage. #### Public vs Private In general, one should not attempt to prove nullifier non-existence in private, as that will not consider the possibility of the nullifier having been emitted in any transaction between the anchor block and the inclusion block. Private functions instead prove existence via [`crate::context::PrivateContext::assert_nullifier_exists`](../../noir_aztec/context/struct.PrivateContext.html#assert_nullifier_exists) and 'prove' non-existence by emitting the nullifer, which would cause the transaction to fail if the nullifier existed. This is not the case in public functions, which do have access to the tip of the blockchain and so can reliably prove whether a nullifier exists or not. #### Safety While it is safe to rely on this function's return value to determine if a nullifier exists or not, it is often not safe to infer additional information from that. In particular, it is unsafe to infer that the existence of a nullifier emitted from a private function implies that all other side-effects of said private execution have been completed, more concretely that any enqueued public calls have been executed. For example, if a function in contract `A` privately emits nullifier `X` and then enqueues public function `Y`, then it is unsafe for a contract `B` to infer that `Y` has alredy executed simply because `X` exists. This is because all private transaction effects are committed before enqueued public functions are run (in order to not reveal detailed timing information about the transaction), so it is possible to observe a nullifier that was emitted alongside the enqueuing of a public call before said call has been completed. #### Cost This emits the `CHECKNULLIFIEREXISTS` opcode, which conceptually performs a merkle inclusion proof on the nullifier tree (both when the nullifier exists and when it doesn't). `pub fn [consume_l1_to_l2_message](#consume_l1_to_l2_message)( self, content: [Field](../../std/primitive.Field.html), secret: [Field](../../std/primitive.Field.html), sender: [EthAddress](../../protocol_types/address/eth_address/struct.EthAddress.html), leaf_index: [Field](../../std/primitive.Field.html), )` Consumes a message sent from Ethereum (L1) to Aztec (L2) -- effectively marking it as "read". Use this function if you only want the message to ever be "referred to" once. Once consumed using this method, the message cannot be consumed again, because a nullifier is emitted. If your use case wants for the message to be read unlimited times, then you can always read any historic message from the L1-to-L2 messages tree, using the `l1_to_l2_msg_exists` method. Messages never technically get deleted from that tree. The message will first be inserted into an Aztec "Inbox" smart contract on L1. It will not be available for consumption immediately. Messages get copied-over from the L1 Inbox to L2 by the next Proposer in batches. So you will need to wait until the messages are copied before you can consume them. #### Arguments - `content` - The message content that was sent from L1 - `secret` - Secret value used for message privacy (if needed) - `sender` - Ethereum address that sent the message - `leaf_index` - Index of the message in the L1-to-L2 message tree #### Advanced - Validates message existence in the L1-to-L2 message tree - Prevents double-consumption by emitting a nullifier - Message hash is computed from all parameters + chain context - Will revert if message doesn't exist or was already consumed `pub fn [message_portal](#message_portal)(_self: Self, recipient: [EthAddress](../../protocol_types/address/eth_address/struct.EthAddress.html), content: [Field](../../std/primitive.Field.html))` Sends an "L2 -> L1 message" from this function (Aztec, L2) to a smart contract on Ethereum (L1). L1 contracts which are designed to send/receive messages to/from Aztec are called "Portal Contracts". Common use cases include withdrawals, cross-chain asset transfers, and triggering L1 actions based on L2 state changes. The message will be inserted into an Aztec "Outbox" contract on L1, when this transaction's block is proposed to L1. Sending the message will not result in any immediate state changes in the target portal contract. The message will need to be manually consumed from the Outbox through a separate Ethereum transaction: a user will need to call a function of the portal contract -- a function specifically designed to make a call to the Outbox to consume the message. The message will only be available for consumption once the epoch proof has been submitted. Given that there are multiple Aztec blocks within an epoch, it might take some time for this epoch proof to be submitted -- especially if the block was near the start of an epoch. #### Arguments - `recipient` - Ethereum address that will receive the message - `content` - Message content (32 bytes as a Field element) `pub unconstrained fn [call_public_function](#call_public_function)( _self: Self, contract_address: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html), function_selector: [FunctionSelector](../../protocol_types/abis/function_selector/struct.FunctionSelector.html), args: [[Field](../../std/primitive.Field.html); N], gas_opts: [GasOpts](../../noir_aztec/context/gas/struct.GasOpts.html), ) -> [[Field](../../std/primitive.Field.html)]` Calls a public function on another contract. Will revert if the called function reverts or runs out of gas. #### Arguments - `contract_address` - Address of the contract to call - `function_selector` - Function to call on the target contract - `args` - Arguments to pass to the function - `gas_opts` - An optional allocation of gas to the called function. #### Returns - `[Field]` - Return data from the called function `pub unconstrained fn [static_call_public_function](#static_call_public_function)( _self: Self, contract_address: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html), function_selector: [FunctionSelector](../../protocol_types/abis/function_selector/struct.FunctionSelector.html), args: [[Field](../../std/primitive.Field.html); N], gas_opts: [GasOpts](../../noir_aztec/context/gas/struct.GasOpts.html), ) -> [[Field](../../std/primitive.Field.html)]` Makes a read-only call to a public function on another contract. This is similar to Solidity's `staticcall`. The called function cannot modify state or emit events. Any nested calls are constrained to also be staticcalls. Useful for querying data from other contracts safely. Will revert if the called function reverts or runs out of gas. #### Arguments - `contract_address` - Address of the contract to call - `function_selector` - Function to call on the target contract - `args` - Array of arguments to pass to the called function - `gas_opts` - An optional allocation of gas to the called function. #### Returns - `[Field]` - Return data from the called function `pub fn [push_note_hash](#push_note_hash)(_self: Self, note_hash: [Field](../../std/primitive.Field.html))` Adds a new note hash to the Aztec blockchain's global Note Hash Tree. Notes are ordinarily constructed and emitted by private functions, to ensure that both the content of the note, and the contract that emitted the note, stay private. There are however some useful patterns whereby a note needs to contain public data. The ability to push a new note_hash from a public function means that notes can be injected with public data immediately -- as soon as the public value is known. The slower alternative would be to submit a follow-up transaction so that a private function can inject the data. Both are possible on Aztec. Search "Partial Note" for a very common pattern which enables a note to be "partially" populated with some data in a private function, and then later "completed" with some data in a public function. #### Arguments - `note_hash` - The hash of the note to add to the tree #### Advanced - The note hash will be siloed with the contract address by the protocol `pub fn [push_nullifier](#push_nullifier)(_self: Self, nullifier: [Field](../../std/primitive.Field.html))` Creates a new [nullifier](../../noir_aztec/nullifier/index.html). While nullifiers are primarily intended as a privacy-preserving record of a one-time action, they can also be used to efficiently record public one-time actions. This function allows creating nullifiers from public contract functions, which behave just like those created from private functions. #### Safety This is a low-level function that must be used with great care to avoid subtle corruption of contract state. In particular, callers must ensure all nullifiers created by a contract are properly domain-separated, so that unrelated components don't interfere with one another (e.g. a transaction nullifier accidentally marking a variable as initialized). Note nullifiers should only be created via [`crate::context::PrivateContext::push_nullifier_for_note_hash`](../../noir_aztec/context/struct.PrivateContext.html#push_nullifier_for_note_hash). #### Advanced The raw `nullifier` is not what is inserted into the Aztec state tree: it will be first siloed by contract address via [`crate::protocol::hash::compute_siloed_nullifier`](../../protocol_types/hash/fn.compute_siloed_nullifier.html) in order to prevent accidental or malicious interference of nullifiers from different contracts. `pub fn [this_address](#this_address)(_self: Self) -> [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html)` Returns the address of the current contract being executed. This is equivalent to `address(this)` in Solidity (hence the name). Use this to identify the current contract's address, commonly needed for access control or when interacting with other contracts. #### Returns - `AztecAddress` - The contract address of the current function being executed. `pub fn [maybe_msg_sender](#maybe_msg_sender)(_self: Self) -> [Option](../../std/option/struct.Option.html)<[AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html)>` Returns the contract address that initiated this function call. This is similar to `msg.sender` in Solidity (hence the name). Important Note: If the calling function is a private function, then it had the option of hiding its address when enqueuing this public function call. In such cases, this method will return `Option::none`. If the calling function is a public function, it will always return an `Option::some` (i.e. a non-null value). #### Returns - `Option` - The address of the smart contract that called this function (be it an app contract or a user's account contract). #### Advanced - Value is provided by the AVM sender opcode - In nested calls, this is the immediate caller, not the original transaction sender `pub fn [selector](#selector)(_self: Self) -> [FunctionSelector](../../protocol_types/abis/function_selector/struct.FunctionSelector.html)` Returns the function selector of the currently-executing function. This is similar to `msg.sig` in Solidity, returning the first 4 bytes of the function signature. #### Returns - `FunctionSelector` - The 4-byte function identifier #### Advanced - Extracted from the first element of calldata - Used internally for function dispatch in the AVM `pub fn [get_args_hash](#get_args_hash)(self) -> [Field](../../std/primitive.Field.html)` Returns the hash of the arguments passed to the current function. Very low-level function: The #[external("public")] macro uses this internally. Smart contract developers typically won't need to access this directly as arguments are automatically made available. #### Returns - `Field` - Hash of the function arguments `pub fn [transaction_fee](#transaction_fee)(_self: Self) -> [Field](../../std/primitive.Field.html)` Returns the "transaction fee" for the current transaction. This is the final tx fee that will be deducted from the fee_payer's "fee-juice" balance (in the protocol's Base Rollup circuit). #### Returns - `Field` - The actual, final cost of the transaction, taking into account: the actual gas used during the setup and app-logic phases, and the fixed amount of gas that's been allocated by the user for the teardown phase. I.e. effectiveL2FeePerGas * l2GasUsed + effectiveDAFeePerGas * daGasUsed This will return `0` during the "setup" and "app-logic" phases of tx execution (because the final tx fee is not known at that time). This will only return a nonzero value during the "teardown" phase of execution, where the final tx fee can actually be computed. Regardless of when this function is called during the teardown phase, it will always return the same final tx fee value. The teardown phase does not consume a variable amount of gas: it always consumes a pre-allocated amount of gas, as specified by the user when they generate their tx. `pub fn [chain_id](#chain_id)(_self: Self) -> [Field](../../std/primitive.Field.html)` Returns the chain ID of the current network. This is similar to `block.chainid` in Solidity. Returns the unique identifier for the blockchain network this transaction is executing on. Helps prevent cross-chain replay attacks. Useful if implementing multi-chain contract logic. #### Returns - `Field` - The chain ID as a field element `pub fn [version](#version)(_self: Self) -> [Field](../../std/primitive.Field.html)` Returns the Aztec protocol version that this transaction is executing under. Different versions may have different rules, opcodes, or cryptographic primitives. This is similar to how Ethereum has different EVM versions. Useful for forward/backward compatibility checks Not to be confused with contract versions; this is the protocol version. #### Returns - `Field` - The protocol version as a field element `pub fn [block_number](#block_number)(_self: Self) -> [u32](../../std/primitive.u32.html)` Returns the current block number. This is similar to `block.number` in Solidity. Note: the current block number is only available within a public function (as opposed to a private function). Note: the time intervals between blocks should not be relied upon as being consistent: - Timestamps of blocks fall within a range, rather than at exact regular intervals. - Slots can be missed. - Protocol upgrades can completely change the intervals between blocks (and indeed the current roadmap plans to reduce the time between blocks, eventually). Use `context.timestamp()` for more-reliable time-based logic. #### Returns - `u32` - The current block number `pub fn [timestamp](#timestamp)(_self: Self) -> [u64](../../std/primitive.u64.html)` Returns the timestamp of the current block. This is similar to `block.timestamp` in Solidity. All functions of all transactions in a block share the exact same timestamp (even though technically each transaction is executed one-after-the-other). Important note: Timestamps of Aztec blocks are not at reliably-fixed intervals. The proposer of the block has some flexibility to choose a timestamp which is in a valid range: Obviously the timestamp of this block must be strictly greater than that of the previous block, and must must be less than the timestamp of whichever ethereum block the aztec block is proposed to. Furthermore, if the timestamp is not deemed close enough to the actual current time, the committee of validators will not attest to the block. #### Returns - `u64` - Unix timestamp in seconds `pub fn [min_fee_per_l2_gas](#min_fee_per_l2_gas)(_self: Self) -> [u128](../../std/primitive.u128.html)` Returns the fee per unit of L2 gas for this transaction (aka the "L2 gas price"), as chosen by the user. L2 gas covers the cost of executing public functions and handling side-effects within the AVM. #### Returns - `u128` - Fee per unit of L2 gas Wallet developers should be mindful that the choice of gas price (which is publicly visible) can leak information about the user, e.g.: - which wallet software the user is using; - the amount of time which has elapsed from the time the user's wallet chose a gas price (at the going rate), to the time of tx submission. This can give clues about the proving time, and hence the nature of the tx. - the urgency of the transaction (which is kind of unavoidable, if the tx is indeed urgent). - the wealth of the user. - the exact user (if the gas price is explicitly chosen by the user to be some unique number like 0.123456789, or their favorite number). Wallet devs might wish to consider fuzzing the choice of gas price. `pub fn [min_fee_per_da_gas](#min_fee_per_da_gas)(_self: Self) -> [u128](../../std/primitive.u128.html)` Returns the fee per unit of DA (Data Availability) gas (aka the "DA gas price"). DA gas covers the cost of making transaction data available on L1. See the warning in `min_fee_per_l2_gas` for how gas prices can be leaky. #### Returns - `u128` - Fee per unit of DA gas `pub fn [l2_gas_left](#l2_gas_left)(_self: Self) -> [u32](../../std/primitive.u32.html)` Returns the remaining L2 gas available for this transaction. Different AVM opcodes consume different amounts of gas. #### Returns - `u32` - Remaining L2 gas units `pub fn [da_gas_left](#da_gas_left)(_self: Self) -> [u32](../../std/primitive.u32.html)` Returns the remaining DA (Data Availability) gas available for this transaction. DA gas is consumed when emitting data that needs to be made available on L1, such as public logs or state updates. All of the side-effects from the private part of the tx also consume DA gas before execution of any public functions even begins. #### Returns - `u32` - Remaining DA gas units `pub fn [is_static_call](#is_static_call)(_self: Self) -> [bool](../../std/primitive.bool.html)` Checks if the current execution is within a staticcall context, where no state changes or logs are allowed to be emitted (by this function or any nested function calls). #### Returns - `bool` - True if in staticcall context, false otherwise `pub fn [raw_storage_read](#raw_storage_read)(self, storage_slot: [Field](../../std/primitive.Field.html)) -> [[Field](../../std/primitive.Field.html); N]` Reads raw field values from public storage. Reads N consecutive storage slots starting from the given slot. Very low-level function. Users should typically use the public state variable abstractions to perform reads: PublicMutable & PublicImmutable. #### Arguments - `storage_slot` - The starting storage slot to read from #### Returns - `[Field; N]` - Array of N field values from consecutive storage slots #### Generic Parameters - `N` - the number of consecutive slots to return, starting from the `storage_slot`. `pub fn [storage_read](#storage_read)(self, storage_slot: [Field](../../std/primitive.Field.html)) -> T where T: [Packable](../../protocol_types/traits/trait.Packable.html)` Reads a typed value from public storage. Low-level function. Users should typically use the public state variable abstractions to perform reads: PublicMutable & PublicImmutable. #### Arguments - `storage_slot` - The storage slot to read from #### Returns - `T` - The deserialized value from storage #### Generic Parameters - `T` - The type that the caller expects to read from the `storage_slot`. `pub fn [raw_storage_write](#raw_storage_write)( _self: Self, storage_slot: [Field](../../std/primitive.Field.html), values: [[Field](../../std/primitive.Field.html); N], )` Writes raw field values to public storage. Writes to N consecutive storage slots starting from the given slot. Very low-level function. Users should typically use the public state variable abstractions to perform writes: PublicMutable & PublicImmutable. Public storage writes take effect immediately. #### Arguments - `storage_slot` - The starting storage slot to write to - `values` - Array of N Fields to write to storage `pub fn [storage_write](#storage_write)(self, storage_slot: [Field](../../std/primitive.Field.html), value: T) where T: [Packable](../../protocol_types/traits/trait.Packable.html)` Writes a typed value to public storage. Low-level function. Users should typically use the public state variable abstractions to perform writes: PublicMutable & PublicImmutable. #### Arguments - `storage_slot` - The storage slot to write to - `value` - The typed value to write to storage #### Generic Parameters - `T` - The type to write to storage. ## Trait implementations ### `impl [Empty](../../protocol_types/traits/trait.Empty.html) for [PublicContext](../../noir_aztec/context/struct.PublicContext.html)` `pub fn empty() -> Self` `pub fn is_empty(self) -> [bool](../../std/primitive.bool.html)` `pub fn assert_empty(self, msg: [str](../../std/primitive.str.html))` ### `impl [Eq](../../std/cmp/trait.Eq.html) for [PublicContext](../../noir_aztec/context/struct.PublicContext.html)` `pub fn eq(self, other: Self) -> [bool](../../std/primitive.bool.html)` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/context/struct.ReturnsHash.html # Struct ReturnsHash ``` pub struct ReturnsHash { /* private fields */ } ``` The hash of a private contract function call's return value. Use [`ReturnsHash::get_preimage`](../../noir_aztec/context/struct.ReturnsHash.html#get_preimage) to get the underlying value. The kernels don't process the actual return values but instead their hashes, so it is up to contracts to populate oracles with the preimages of these hashes on return to make them available to their callers. Public calls don't utilize this mechanism since the AVM does process the full return values. ## Implementations ### `impl [ReturnsHash](../../noir_aztec/context/struct.ReturnsHash.html)` `pub fn [new](#new)(hash: [Field](../../std/primitive.Field.html)) -> Self` `pub fn [get_preimage](#get_preimage)(self) -> T where T: [Deserialize](../../serde/serialization/trait.Deserialize.html)` Fetches the underlying return value from an oracle, constraining that it corresponds to the return data hash. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/context/struct.UtilityContext.html # Struct UtilityContext ``` pub struct UtilityContext { /* private fields */ } ``` ## Implementations ### `impl [UtilityContext](../../noir_aztec/context/struct.UtilityContext.html)` `pub unconstrained fn [new](#new)() -> Self` `pub unconstrained fn [at](#at)(contract_address: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html)) -> Self` `pub fn [block_header](#block_header)(self) -> [BlockHeader](../../protocol_types/abis/block_header/struct.BlockHeader.html)` `pub fn [block_number](#block_number)(self) -> [u32](../../std/primitive.u32.html)` `pub fn [timestamp](#timestamp)(self) -> [u64](../../std/primitive.u64.html)` `pub fn [this_address](#this_address)(self) -> [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html)` `pub fn [version](#version)(self) -> [Field](../../std/primitive.Field.html)` `pub fn [chain_id](#chain_id)(self) -> [Field](../../std/primitive.Field.html)` `pub unconstrained fn [raw_storage_read](#raw_storage_read)( self, storage_slot: [Field](../../std/primitive.Field.html), ) -> [[Field](../../std/primitive.Field.html); N]` `pub unconstrained fn [storage_read](#storage_read)(self, storage_slot: [Field](../../std/primitive.Field.html)) -> T where T: [Packable](../../protocol_types/traits/trait.Packable.html)` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/contract_self/contract_self_private/index.html # Module contract_self_private The `self` contract value for private execution contexts. ## Structs - [ContractSelfPrivate](struct.ContractSelfPrivate.html)Core interface for interacting with aztec-nr contract features in private execution contexts. - [PrivateUtilityCalls](struct.PrivateUtilityCalls.html)A struct that allows for ergonomic calling of utility functions from a private context. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/contract_self/contract_self_private/struct.ContractSelfPrivate.html # Struct ContractSelfPrivate ``` pub struct ContractSelfPrivate { pub address: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), pub storage: Storage, pub context: &mut [PrivateContext](../../../noir_aztec/context/struct.PrivateContext.html), pub call_self: CallSelf, pub enqueue_self: EnqueueSelf, pub call_self_static: CallSelfStatic, pub enqueue_self_static: EnqueueSelfStatic, pub internal: CallInternal, pub utility: [PrivateUtilityCalls](../../../noir_aztec/contract_self/contract_self_private/struct.PrivateUtilityCalls.html), } ``` Core interface for interacting with aztec-nr contract features in private execution contexts. This struct is automatically injected into every [`external`](../../../noir_aztec/macros/functions/fn.external.html) and [`internal`](../../../noir_aztec/macros/functions/fn.internal.html) contract function marked with `"private"` by the Aztec macro system and is accessible through the `self` variable. ## Usage in Contract Functions Once injected, you can use `self` to: - Access storage: `self.storage.balances.at(owner).read()` - Call contracts: `self.call(Token::at(address).transfer(recipient, amount))` - Emit events: `self.emit(event).deliver_to(recipient, delivery_mode)` - Get the contract address: `self.address` - Get the caller: `self.msg_sender()` - Access low-level Aztec.nr APIs through the context: `self.context` ## Example ``` #[external("private")] fn withdraw(amount: u128, recipient: AztecAddress) { // Get the caller of this function let sender = self.msg_sender(); // Access storage let token = self.storage.donation_token.get_note().get_address(); // Call contracts self.call(Token::at(token).transfer(recipient, amount)); } ``` ## Type Parameters - `Storage`: The contract's storage struct (defined with [`storage`](../../../noir_aztec/macros/storage/fn.storage.html), or `()` if the contract has no storage - `CallSelf`: Macro-generated type for calling contract's own non-view functions - `EnqueueSelf`: Macro-generated type for enqueuing calls to the contract's own non-view functions - `CallSelfStatic`: Macro-generated type for calling contract's own view functions - `EnqueueSelfStatic`: Macro-generated type for enqueuing calls to the contract's own view functions - `CallInternal`: Macro-generated type for calling internal functions - `CallSelfUtility`: Macro-generated type for calling the contract's own utility functions ## Fields `address: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html)` The address of this contract `storage: Storage` The contract's storage instance, representing the struct to which the [`storage`](../../../noir_aztec/macros/storage/fn.storage.html) macro was applied in your contract. If the contract has no storage, the type of this will be `()`. This storage instance is specialized for the current execution context (private) and provides access to the contract's state variables. #### Developer Note If you've arrived here while trying to access your contract's storage while the `Storage` generic type is set to unit type `()`, it means you haven't yet defined a Storage struct using the [`storage`](../../../noir_aztec/macros/storage/fn.storage.html) macro in your contract. For guidance on setting this up, please refer to our docs: [https://docs.aztec.network/developers/docs/guides/smart_contracts/storage](https://docs.aztec.network/developers/docs/guides/smart_contracts/storage) `context: &mut [PrivateContext](../../../noir_aztec/context/struct.PrivateContext.html)` The private execution context. `call_self: CallSelf` Provides type-safe methods for calling this contract's own non-view functions. Example API: ``` self.call_self.some_private_function(args) ``` `enqueue_self: EnqueueSelf` Provides type-safe methods for enqueuing calls to this contract's own non-view functions. Example API: ``` self.enqueue_self.some_public_function(args) ``` `call_self_static: CallSelfStatic` Provides type-safe methods for calling this contract's own view functions. Example API: ``` self.call_self_static.some_view_function(args) ``` `enqueue_self_static: EnqueueSelfStatic` Provides type-safe methods for enqueuing calls to the contract's own view functions. Example API: ``` self.enqueue_self_static.some_public_view_function(args) ``` `internal: CallInternal` Provides type-safe methods for calling internal functions. Example API: ``` self.internal.some_internal_function(args) ``` `utility: [PrivateUtilityCalls](../../../noir_aztec/contract_self/contract_self_private/struct.PrivateUtilityCalls.html)` A struct that allows for ergonomic calling of utility functions from this private context. Example API: ``` // Safety: result is unconstrained unsafe { self.utility.call(MyContract::at(address).my_utility_function(args)) } ``` ## Implementations ### `impl [ContractSelfPrivate](../../../noir_aztec/contract_self/contract_self_private/struct.ContractSelfPrivate.html)` `pub fn [new](#new)( context: &mut [PrivateContext](../../../noir_aztec/context/struct.PrivateContext.html), storage: Storage, call_self: CallSelf, enqueue_self: EnqueueSelf, call_self_static: CallSelfStatic, enqueue_self_static: EnqueueSelfStatic, internal: CallInternal, utility: [PrivateUtilityCalls](../../../noir_aztec/contract_self/contract_self_private/struct.PrivateUtilityCalls.html), ) -> Self` Creates a new `ContractSelfPrivate` instance for a private function. This constructor is called automatically by the macro system and should not be called directly. `pub fn [msg_sender](#msg_sender)(self) -> [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html)` The address of the contract address that made this function call. This is similar to Solidity's `msg.sender` value. #### Transaction Entrypoints As there are no EOAs (externally owned accounts) in Aztec, unlike on Ethereum, the first contract function executed in a transaction (i.e. transaction entrypoint) does not have a caller. This function panics when executed in such a context. If you need to handle these cases, use [`PrivateContext::maybe_msg_sender`](../../../noir_aztec/context/struct.PrivateContext.html#maybe_msg_sender). `pub fn [emit](#emit)(&mut self, event: Event) -> [EventMessage](../../../noir_aztec/event/struct.EventMessage.html) where Event: [EventInterface](../../../noir_aztec/event/event_interface/trait.EventInterface.html), Event: [Serialize](../../../serde/serialization/trait.Serialize.html)` Emits an event privately. Unlike public events, private events do not reveal their contents publicly. They instead create an [`EventMessage`](../../../noir_aztec/event/struct.EventMessage.html) containing the private event information, which MUST be delivered to a recipient via [`EventMessage::deliver_to`](../../../noir_aztec/event/struct.EventMessage.html#deliver_to) in order for them to learn about the event. Multiple recipients can have the same message be delivered to them. #### Example ``` #[event] struct Transfer { from: AztecAddress, to: AztecAddress, amount: u128 } #[external("private")] fn transfer(to: AztecAddress, amount: u128) { let from = self.msg_sender(); let message: EventMessage = self.emit(Transfer { from, to, amount }); message.deliver_to(from, MessageDelivery.OFFCHAIN); message.deliver_to(to, MessageDelivery.ONCHAIN_CONSTRAINED); } ``` #### Cost Private event emission always results in the creation of a nullifer, which acts as a commitment to the event and is used by third parties to verify its authenticity. See [`EventMessage::deliver_to`](../../../noir_aztec/event/struct.EventMessage.html#deliver_to) for the costs associated to delivery. #### Privacy The nullifier created when emitting a private event leaks nothing about the content of the event - it's a commitment that includes a random value, so even with full knowledge of the event preimage determining if an event was emitted or not requires brute-forcing the entire `Field` space. `pub fn [call](#call)(&mut self, call: [PrivateCall](../../../noir_aztec/context/calls/struct.PrivateCall.html)) -> T where T: [Deserialize](../../../serde/serialization/trait.Deserialize.html)` Makes a private contract call. #### Arguments - `call` - The object representing the private function to invoke. #### Returns - `T` - Whatever data the called function has returned. #### Example ``` self.call(Token::at(address).transfer_in_private(recipient, amount)); ``` This enables contracts to interact with each other while maintaining privacy. This "composability" of private contract functions is a key feature of the Aztec network. If a user's transaction includes multiple private function calls, then by the design of Aztec, the following information will remain private[1]: - The function selectors and contract addresses of all private function calls will remain private, so an observer of the public mempool will not be able to look at a tx and deduce which private functions have been executed. - The arguments and return values of all private function calls will remain private. - The person who initiated the tx will remain private. - The notes and nullifiers and private logs that are emitted by all private function calls will (if designed well) not leak any user secrets, nor leak which functions have been executed. [1] Caveats: Some of these privacy guarantees depend on how app developers design their smart contracts. Some actions can leak information, such as: - Calling an internal public function. - Calling a public function and not setting msg_sender to Option::none (see [https://github.com/AztecProtocol/aztec-packages/pull/16433](https://github.com/AztecProtocol/aztec-packages/pull/16433)) - Calling any public function will always leak details about the nature of the transaction, so devs should be careful in their contract designs. If it can be done in a private function, then that will give the best privacy. - Not padding the side-effects of a tx to some standardized, uniform size. The kernel circuits can take hints to pad side-effects, so a wallet should be able to request for a particular amount of padding. Wallets should ideally agree on some standard. - Padding should include: - Padding the lengths of note & nullifier arrays - Padding private logs with random fields, up to some standardized size. See also: [https://docs.aztec.network/developers/resources/considerations/privacy_considerations](https://docs.aztec.network/developers/resources/considerations/privacy_considerations) #### Advanced - The call is added to the private call stack and executed by kernel circuits after this function completes - The called function can modify its own contract's private state - Side effects from the called function are included in this transaction - The call inherits the current transaction's context and gas limits `pub fn [view](#view)(&mut self, call: [PrivateStaticCall](../../../noir_aztec/context/calls/struct.PrivateStaticCall.html)) -> T where T: [Deserialize](../../../serde/serialization/trait.Deserialize.html)` Makes a read-only private contract call. This is similar to Solidity's `staticcall`. The called function cannot modify state, emit L2->L1 messages, nor emit events. Any nested calls are constrained to also be static calls. #### Arguments - `call` - The object representing the read-only private function to invoke. #### Returns - `T` - Whatever data the called function has returned. #### Example ``` self.view(Token::at(address).balance_of_private(recipient)); ``` `pub fn [enqueue](#enqueue)(&mut self, call: [PublicCall](../../../noir_aztec/context/calls/struct.PublicCall.html))` Enqueues a public contract call function. Unlike private functions which execute immediately on the user's device, public function calls are "enqueued" and executed some time later by a block proposer. This means a public function cannot return any values back to a private function, because by the time the public function is being executed, the private function which called it has already completed execution. (In fact, the private function has been executed and proven, along with all other private function calls of the user's tx. A single proof of the tx has been submitted to the Aztec network, and some time later a proposer has picked the tx up from the mempool and begun executing all of the enqueued public functions). #### Privacy warning Enqueueing a public function call is an inherently leaky action. Many interesting applications will require some interaction with public state, but smart contract developers should try to use public function calls sparingly, and carefully. Internal public function calls are especially leaky, because they completely leak which private contract made the call. See also: [https://docs.aztec.network/developers/resources/considerations/privacy_considerations](https://docs.aztec.network/developers/resources/considerations/privacy_considerations) #### Arguments - `call` - The interface representing the public function to enqueue. `pub fn [enqueue_view](#enqueue_view)( &mut self, call: [PublicStaticCall](../../../noir_aztec/context/calls/struct.PublicStaticCall.html), )` Enqueues a read-only public contract call function. This is similar to Solidity's `staticcall`. The called function cannot modify state, emit L2->L1 messages, nor emit events. Any nested calls are constrained to also be static calls. #### Arguments - `call` - The object representing the read-only public function to enqueue. #### Example ``` self.enqueue_view(MyContract::at(address).assert_timestamp_less_than(timestamp)); ``` `pub fn [enqueue_incognito](#enqueue_incognito)(&mut self, call: [PublicCall](../../../noir_aztec/context/calls/struct.PublicCall.html))` Enqueues a privacy-preserving public contract call function. This is the same as [`ContractSelfPrivate::enqueue`](../../../noir_aztec/contract_self/contract_self_private/struct.ContractSelfPrivate.html#enqueue), except it hides this calling contract's address from the target public function (i.e. [`ContractSelfPrivate::msg_sender`](../../../noir_aztec/contract_self/contract_self_private/struct.ContractSelfPrivate.html#msg_sender) will panic). This means the origin of the call (msg_sender) will not be publicly visible to any blockchain observers, nor to the target public function. If the target public function reads `self.msg_sender()` the call will revert. NOTES: - Not all public functions will accept a msg_sender of "none". Many public functions will require that msg_sender is "some" and will revert otherwise. Therefore, if using `enqueue_incognito`, you must understand whether the function you're calling will accept a msg_sender of "none". Lots of public bookkeeping patterns rely on knowing which address made the call, so as to ascribe state against the caller's address. (There are patterns whereby bookkeeping could instead be done in private-land). - If you are enqueueing a call to an internal public function (i.e. a public function that will only accept calls from other functions of its own contract), then by definition a call to it cannot possibly be "incognito": the msg_sender must be its own address, and indeed the called public function will assert this. Tl;dr this is not usable for enqueued internal public calls. #### Arguments - `call` - The object representing the public function to enqueue. #### Example ``` self.enqueue_incognito(Token::at(address).increase_total_supply_by(amount)); ``` Advanced: - The kernel circuits will permit any private function to set the msg_sender field of any enqueued public function call to NULL_MSG_SENDER_CONTRACT_ADDRESS. - When the called public function calls `PublicContext::msg_sender()`, aztec-nr will translate NULL_MSG_SENDER_CONTRACT_ADDRESS into `Option::none` for familiarity to devs. `pub fn [enqueue_view_incognito](#enqueue_view_incognito)( &mut self, call: [PublicStaticCall](../../../noir_aztec/context/calls/struct.PublicStaticCall.html), )` Enqueues a privacy-preserving read-only public contract call function. As per `enqueue_view`, but hides this calling contract's address from the target public function. See `enqueue_incognito` for more details relating to hiding msg_sender. #### Arguments - `call` - The object representing the read-only public function to enqueue. #### Example ``` self.enqueue_view_incognito(MyContract::at(address).assert_timestamp_less_than(timestamp)); ``` `pub fn [set_as_teardown](#set_as_teardown)(&mut self, call: [PublicCall](../../../noir_aztec/context/calls/struct.PublicCall.html))` Enqueues a call to the public function defined by the `call` parameter, and designates it to be the teardown function for this tx. Only one teardown function call can be made by a tx. Niche function: Only wallet developers and paymaster contract developers (aka Fee-payment contracts) will need to make use of this function. Aztec supports a three-phase execution model: setup, app logic, teardown. The phases exist to enable a fee payer to take on the risk of paying a transaction fee, safe in the knowledge that their payment (in whatever token or method the user chooses) will succeed, regardless of whether the app logic will succeed. The "setup" phase ensures the fee payer has sufficient balance to pay the proposer their fees. The teardown phase is primarily intended to: calculate exactly how much the user owes, based on gas consumption, and refund the user any change. Note: in some cases, the cost of refunding the user (i.e. DA costs of tx side-effects) might exceed the refund amount. For app logic with fairly stable and predictable gas consumption, a material refund amount is unlikely. For app logic with unpredictable gas consumption, a refund might be important to the user (e.g. if a hefty function reverts very early). Wallet/FPC/Paymaster developers should be mindful of this. See `enqueue` for more information about enqueuing public function calls. #### Arguments - `call` - The object representing the public function to designate as teardown. `pub fn [set_as_teardown_incognito](#set_as_teardown_incognito)( &mut self, call: [PublicCall](../../../noir_aztec/context/calls/struct.PublicCall.html), )` Enqueues a call to the public function defined by the `call` parameter, and designates it to be the teardown function for this tx. Only one teardown function call can be made by a tx. As per `set_as_teardown`, but hides this calling contract's address from the target public function. See `enqueue_incognito` for more details relating to hiding msg_sender. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/contract_self/contract_self_private/struct.PrivateUtilityCalls.html # Struct PrivateUtilityCalls ``` pub struct PrivateUtilityCalls { pub call_self: CallSelf, } ``` A struct that allows for ergonomic calling of utility functions from a private context. Accessible via `self.utility` in private functions. Results are not part of any circuit proof; use them to inform logic, not as inputs to constrained assertions. ## Type Parameters - `CallSelf`: Macro-generated type for calling the contract's own utility functions (same type as `CallSelf` in [`ContractSelfUtility`]) ## Fields `call_self: CallSelf` Provides type-safe methods for calling this contract's own utility functions. Example API: ``` // Safety: result is unconstrained unsafe { self.utility.call_self.some_utility_function(args) } ``` ## Implementations ### `impl [PrivateUtilityCalls](../../../noir_aztec/contract_self/contract_self_private/struct.PrivateUtilityCalls.html)` `pub unconstrained fn [call](#call)( _self: Self, call: [UtilityCall](../../../noir_aztec/context/calls/struct.UtilityCall.html), ) -> T where T: [Deserialize](../../../serde/serialization/trait.Deserialize.html)` Makes a utility contract call from a private function. Note: only same-contract utility calls are currently supported. See TODO(F-29). #### Example ``` unsafe { self.utility.call(Token::at(self.address).get_balance_of(owner)) } ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/contract_self/contract_self_public/index.html # Module contract_self_public The `self` contract value for public execution contexts. ## Structs - [ContractSelfPublic](struct.ContractSelfPublic.html)Core interface for interacting with aztec-nr contract features in public execution contexts. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/contract_self/contract_self_public/struct.ContractSelfPublic.html # Struct ContractSelfPublic ``` pub struct ContractSelfPublic { pub address: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), pub storage: Storage, pub context: [PublicContext](../../../noir_aztec/context/struct.PublicContext.html), pub call_self: CallSelf, pub call_self_static: CallSelfStatic, pub internal: CallInternal, } ``` Core interface for interacting with aztec-nr contract features in public execution contexts. This struct is automatically injected into every [`external`](../../../noir_aztec/macros/functions/fn.external.html) and [`internal`](../../../noir_aztec/macros/functions/fn.internal.html) contract function marked with `"public"` by the Aztec macro system and is accessible through the `self` variable. ## Type Parameters - `Storage`: The contract's storage struct (defined with [`storage`](../../../noir_aztec/macros/storage/fn.storage.html), or `()` if the contract has no storage - `CallSelf`: Macro-generated type for calling contract's own non-view functions - `CallSelfStatic`: Macro-generated type for calling contract's own view functions - `CallInternal`: Macro-generated type for calling internal functions ## Fields `address: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html)` The address of this contract `storage: Storage` The contract's storage instance, representing the struct to which the [`storage`](../../../noir_aztec/macros/storage/fn.storage.html) macro was applied in your contract. If the contract has no storage, the type of this will be `()`. This storage instance is specialized for the current execution context (public) and provides access to the contract's state variables. #### Developer Note If you've arrived here while trying to access your contract's storage while the `Storage` generic type is set to unit type `()`, it means you haven't yet defined a Storage struct using the [`storage`](../../../noir_aztec/macros/storage/fn.storage.html) macro in your contract. For guidance on setting this up, please refer to our docs: [https://docs.aztec.network/developers/docs/guides/smart_contracts/storage](https://docs.aztec.network/developers/docs/guides/smart_contracts/storage) `context: [PublicContext](../../../noir_aztec/context/struct.PublicContext.html)` The public execution context. `call_self: CallSelf` Provides type-safe methods for calling this contract's own non-view functions. Example API: ``` self.call_self.some_public_function(args) ``` `call_self_static: CallSelfStatic` Provides type-safe methods for calling this contract's own view functions. Example API: ``` self.call_self_static.some_view_function(args) ``` `internal: CallInternal` Provides type-safe methods for calling internal functions. Example API: ``` self.internal.some_internal_function(args) ``` ## Implementations ### `impl [ContractSelfPublic](../../../noir_aztec/contract_self/contract_self_public/struct.ContractSelfPublic.html)` `pub fn [new](#new)( context: [PublicContext](../../../noir_aztec/context/struct.PublicContext.html), storage: Storage, call_self: CallSelf, call_self_static: CallSelfStatic, internal: CallInternal, ) -> Self` Creates a new `ContractSelfPublic` instance for a public function. This constructor is called automatically by the macro system and should not be called directly. `pub fn [msg_sender](#msg_sender)(self) -> [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html)` The address of the contract address that made this function call. This is similar to Solidity's `msg.sender` value. #### Incognito Calls Contracts can call public functions from private ones hiding their identity (see [`ContractSelfPrivate::enqueue_incognito`](../../../noir_aztec/contract_self/contract_self_private/struct.ContractSelfPrivate.html#enqueue_incognito)). This function reverts when executed in such a context. If you need to handle these cases, use [`PublicContext::maybe_msg_sender`](../../../noir_aztec/context/struct.PublicContext.html#maybe_msg_sender). `pub unconstrained fn [emit](#emit)(&mut self, event: Event) where Event: [EventInterface](../../../noir_aztec/event/event_interface/trait.EventInterface.html), Event: [Serialize](../../../serde/serialization/trait.Serialize.html)` Emits an event publicly. Public events are emitted as plaintext and are therefore visible to everyone. This is is the same as Solidity events on EVM chains. Unlike private events, they don't require delivery of an event message. #### Example ``` #[event] struct Update { value: Field } #[external("public")] fn publish_update(value: Field) { self.emit(Update { value }); } ``` #### Cost Public event emission is achieved by emitting public transaction logs. A total of `N+1` fields are emitted, where `N` is the serialization length of the event. `pub unconstrained fn [call](#call)( self, call: [PublicCall](../../../noir_aztec/context/calls/struct.PublicCall.html), ) -> T where T: [Deserialize](../../../serde/serialization/trait.Deserialize.html)` Makes a public contract call. Will revert if the called function reverts or runs out of gas. #### Arguments - `call` - The object representing the public function to invoke. #### Returns - `T` - Whatever data the called function has returned. #### Example ``` self.call(Token::at(address).transfer_in_public(recipient, amount)); ``` `pub unconstrained fn [view](#view)( self, call: [PublicStaticCall](../../../noir_aztec/context/calls/struct.PublicStaticCall.html), ) -> T where T: [Deserialize](../../../serde/serialization/trait.Deserialize.html)` Makes a public read-only contract call. This is similar to Solidity's `staticcall`. The called function cannot modify state or emit events. Any nested calls are constrained to also be static calls. Will revert if the called function reverts or runs out of gas. #### Arguments - `call` - The object representing the read-only public function to invoke. #### Returns - `T` - Whatever data the called function has returned. #### Example ``` self.view(Token::at(address).balance_of_public(recipient)); ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/contract_self/contract_self_utility/index.html # Module contract_self_utility The `self` contract value for utility execution contexts. ## Structs - [ContractSelfUtility](struct.ContractSelfUtility.html)Core interface for interacting with aztec-nr contract features in utility execution contexts. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/contract_self/contract_self_utility/struct.ContractSelfUtility.html # Struct ContractSelfUtility ``` pub struct ContractSelfUtility { pub address: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), pub storage: Storage, pub context: [UtilityContext](../../../noir_aztec/context/struct.UtilityContext.html), pub call_self: CallSelf, } ``` Core interface for interacting with aztec-nr contract features in utility execution contexts. This struct is automatically injected into every [`external`](../../../noir_aztec/macros/functions/fn.external.html) contract function marked with `"utility"` by the Aztec macro system and is accessible through the `self` variable. ## Type Parameters - `Storage`: The contract's storage struct (defined with [`storage`](../../../noir_aztec/macros/storage/fn.storage.html), or `()` if the contract has no storage - `CallSelf`: Macro-generated type for calling the contract's own utility functions (same type as `CallSelf` in [`PrivateUtilityCalls`]) ## Fields `address: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html)` The address of this contract `storage: Storage` The contract's storage instance, representing the struct to which the [`storage`](../../../noir_aztec/macros/storage/fn.storage.html) macro was applied in your contract. If the contract has no storage, the type of this will be `()`. This storage instance is specialized for the current execution context (utility) and provides access to the contract's state variables. #### Developer Note If you've arrived here while trying to access your contract's storage while the `Storage` generic type is set to unit type `()`, it means you haven't yet defined a Storage struct using the [`storage`](../../../noir_aztec/macros/storage/fn.storage.html) macro in your contract. For guidance on setting this up, please refer to our docs: [https://docs.aztec.network/developers/docs/guides/smart_contracts/storage](https://docs.aztec.network/developers/docs/guides/smart_contracts/storage) `context: [UtilityContext](../../../noir_aztec/context/struct.UtilityContext.html)` The utility execution context. `call_self: CallSelf` Provides type-safe methods for calling this contract's own utility functions. Example API: ``` self.call_self.some_utility_function(args) ``` ## Implementations ### `impl [ContractSelfUtility](../../../noir_aztec/contract_self/contract_self_utility/struct.ContractSelfUtility.html)` `pub fn [new](#new)(context: [UtilityContext](../../../noir_aztec/context/struct.UtilityContext.html), storage: Storage, call_self: CallSelf) -> Self` Creates a new `ContractSelfUtility` instance for a utility function. This constructor is called automatically by the macro system and should not be called directly. `pub unconstrained fn [call](#call)( _self: Self, call: [UtilityCall](../../../noir_aztec/context/calls/struct.UtilityCall.html), ) -> T where T: [Deserialize](../../../serde/serialization/trait.Deserialize.html)` Makes a utility contract call from another utility function. Note: only same-contract utility calls are currently supported. See TODO(F-29). #### Example ``` self.call(Token::at(address).get_balance_of(owner)); ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/contract_self/index.html # Module contract_self ## Re-exports - `pub use noir_aztec::contract_self::contract_self_private::[ContractSelfPrivate](../../noir_aztec/contract_self/contract_self_private/struct.ContractSelfPrivate.html);` - `pub use noir_aztec::contract_self::contract_self_private::[PrivateUtilityCalls](../../noir_aztec/contract_self/contract_self_private/struct.PrivateUtilityCalls.html);` - `pub use noir_aztec::contract_self::contract_self_public::[ContractSelfPublic](../../noir_aztec/contract_self/contract_self_public/struct.ContractSelfPublic.html);` - `pub use noir_aztec::contract_self::contract_self_utility::[ContractSelfUtility](../../noir_aztec/contract_self/contract_self_utility/struct.ContractSelfUtility.html);` ## Modules - [contract_self_private](contract_self_private/index.html)The `self` contract value for private execution contexts. - [contract_self_public](contract_self_public/index.html)The `self` contract value for public execution contexts. - [contract_self_utility](contract_self_utility/index.html)The `self` contract value for utility execution contexts. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/ephemeral/index.html # Module ephemeral ## Structs - [EphemeralArray](struct.EphemeralArray.html)A dynamically sized array that exists only during a single contract call frame. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/ephemeral/struct.EphemeralArray.html # Struct EphemeralArray ``` pub struct EphemeralArray { pub slot: [Field](../../std/primitive.Field.html), } ``` A dynamically sized array that exists only during a single contract call frame. Ephemeral arrays are backed by in-memory storage on the PXE side rather than a persistent database. Each contract call frame gets its own isolated slot space of ephemeral arrays. Child simulations cannot see the parent's ephemeral arrays, and vice versa. Each logical array operation (push, pop, get, etc.) is a single oracle call, making ephemeral arrays significantly cheaper than capsule arrays. ## Use Cases Ephemeral arrays are designed for passing data between PXE (TypeScript) and contracts (Noir) during simulation, for example, note validation requests or event validation responses. This data type is appropriate for data that is not supposed to be persisted. For data that needs to persist across simulations, contract calls, etc, use [`CapsuleArray`](../../noir_aztec/capsules/struct.CapsuleArray.html) instead. ## Fields `slot: [Field](../../std/primitive.Field.html)` ## Implementations ### `impl [EphemeralArray](../../noir_aztec/ephemeral/struct.EphemeralArray.html)` `pub unconstrained fn [at](#at)(slot: [Field](../../std/primitive.Field.html)) -> Self` Returns a handle to an ephemeral array at the given slot, which may already contain data (e.g. populated by an oracle). `pub unconstrained fn [len](#len)(self) -> [u32](../../std/primitive.u32.html)` Returns the number of elements stored in the array. `pub unconstrained fn [push](#push)(self, value: T) where T: [Serialize](../../serde/serialization/trait.Serialize.html)` Stores a value at the end of the array. `pub unconstrained fn [pop](#pop)(self) -> T where T: [Deserialize](../../serde/serialization/trait.Deserialize.html)` Removes and returns the last element. Panics if the array is empty. `pub unconstrained fn [get](#get)(self, index: [u32](../../std/primitive.u32.html)) -> T where T: [Deserialize](../../serde/serialization/trait.Deserialize.html)` Retrieves the value stored at `index`. Panics if the index is out of bounds. `pub unconstrained fn [set](#set)(self, index: [u32](../../std/primitive.u32.html), value: T) where T: [Serialize](../../serde/serialization/trait.Serialize.html)` Overwrites the value stored at `index`. Panics if the index is out of bounds. `pub unconstrained fn [remove](#remove)(self, index: [u32](../../std/primitive.u32.html))` Removes the element at `index`, shifting subsequent elements backward. Panics if out of bounds. `pub unconstrained fn [clear](#clear)(self) -> Self` Removes all elements from the array and returns self for chaining (e.g. `EphemeralArray::at(slot).clear()` to get a guaranteed-empty array at a given slot). `pub unconstrained fn [for_each](#for_each)(self, f: unconstrained fn[Env]([u32](../../std/primitive.u32.html), T)) where T: [Deserialize](../../serde/serialization/trait.Deserialize.html)` Calls a function on each element of the array. The function `f` is called once with each array value and its corresponding index. Iteration proceeds backwards so that it is safe to remove the current element (and only the current element) inside the callback. It is not safe to push new elements from inside the callback. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/event/event_emission/fn.emit_event_in_private.html # Function emit_event_in_private ``` pub fn emit_event_in_private( context: &mut [PrivateContext](../../../noir_aztec/context/struct.PrivateContext.html), event: Event, ) -> [EventMessage](../../../noir_aztec/event/struct.EventMessage.html) where Event: [EventInterface](../../../noir_aztec/event/event_interface/trait.EventInterface.html), Event: [Serialize](../../../serde/serialization/trait.Serialize.html) ``` Equivalent to `self.emit(event)`: see [`crate::contract_self::ContractSelfPrivate::emit`](../../../noir_aztec/contract_self/contract_self_private/struct.ContractSelfPrivate.html#emit). --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/event/event_emission/fn.emit_event_in_public.html # Function emit_event_in_public ``` pub unconstrained fn emit_event_in_public(context: [PublicContext](../../../noir_aztec/context/struct.PublicContext.html), event: Event) where Event: [EventInterface](../../../noir_aztec/event/event_interface/trait.EventInterface.html), Event: [Serialize](../../../serde/serialization/trait.Serialize.html) ``` Equivalent to `self.emit(event)`: see [`crate::contract_self::ContractSelfPublic::emit`](../../../noir_aztec/contract_self/contract_self_public/struct.ContractSelfPublic.html#emit). --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/event/event_emission/index.html # Module event_emission ## Structs - [NewEvent](struct.NewEvent.html)An event that was emitted in the current contract call. ## Functions - [emit_event_in_private](fn.emit_event_in_private.html)Equivalent to `self.emit(event)`: see [`crate::contract_self::ContractSelfPrivate::emit`](../../../noir_aztec/contract_self/contract_self_private/struct.ContractSelfPrivate.html#emit). - [emit_event_in_public](fn.emit_event_in_public.html)Equivalent to `self.emit(event)`: see [`crate::contract_self::ContractSelfPublic::emit`](../../../noir_aztec/contract_self/contract_self_public/struct.ContractSelfPublic.html#emit). --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/event/event_emission/struct.NewEvent.html # Struct NewEvent ``` pub struct NewEvent { /* private fields */ } ``` An event that was emitted in the current contract call. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/event/event_interface/fn.compute_private_event_commitment.html # Function compute_private_event_commitment ``` pub fn compute_private_event_commitment(event: Event, randomness: [Field](../../../std/primitive.Field.html)) -> [Field](../../../std/primitive.Field.html) where Event: [EventInterface](../../../noir_aztec/event/event_interface/trait.EventInterface.html), Event: [Serialize](../../../serde/serialization/trait.Serialize.html) ``` A private event's commitment is a value stored on-chain which is used to verify that the event was indeed emitted. It requires a `randomness` value that must be produced alongside the event in order to perform said validation. This random value prevents attacks in which someone guesses plausible events (e.g. 'Alice transfers to Bob an amount of 10'), since they will not be able to test for existence of their guessed events without brute-forcing the entire `Field` space by guessing `randomness` values. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/event/event_interface/fn.compute_private_serialized_event_commitment.html # Function compute_private_serialized_event_commitment ``` pub unconstrained fn compute_private_serialized_event_commitment( serialized_event: [BoundedVec](../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Field](../../../std/primitive.Field.html), 10>, randomness: [Field](../../../std/primitive.Field.html), event_type_id: [Field](../../../std/primitive.Field.html), ) -> [Field](../../../std/primitive.Field.html) ``` Unconstrained variant of [`compute_private_event_commitment`](../../../noir_aztec/event/event_interface/fn.compute_private_event_commitment.html) which takes the event in serialized form. This function is unconstrained as the mechanism it uses to compute the commitment would be very inefficient in a constrained environment (due to the hashing of a dynamically sized array). This is not an issue as it is typically invoked when processing event messages, which is an unconstrained operation. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/event/event_interface/index.html # Module event_interface ## Traits - [EventInterface](trait.EventInterface.html) ## Functions - [compute_private_event_commitment](fn.compute_private_event_commitment.html)A private event's commitment is a value stored on-chain which is used to verify that the event was indeed emitted. - [compute_private_serialized_event_commitment](fn.compute_private_serialized_event_commitment.html)Unconstrained variant of [`compute_private_event_commitment`](../../../noir_aztec/event/event_interface/fn.compute_private_event_commitment.html) which takes the event in serialized form. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/event/event_interface/trait.EventInterface.html # Trait EventInterface ``` pub trait EventInterface { // Required methods pub fn [get_event_type_id](#get_event_type_id)() -> [EventSelector](../../../noir_aztec/event/struct.EventSelector.html); } ``` ## Required methods `pub fn [get_event_type_id](#get_event_type_id)() -> [EventSelector](../../../noir_aztec/event/struct.EventSelector.html)` ## Implementors ### `impl [EventInterface](../../../noir_aztec/event/event_interface/trait.EventInterface.html) for MockEvent` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/event/index.html # Module event Event traits and utilities. ## Modules - [event_emission](event_emission/index.html) - [event_interface](event_interface/index.html) ## Structs - [EventMessage](struct.EventMessage.html)A message with information about an event that was emitted in the current contract call. This message MUST be delivered to a recipient in order to not lose the private event information. - [EventSelector](struct.EventSelector.html) --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/event/struct.EventMessage.html # Struct EventMessage ``` pub struct EventMessage { /* private fields */ } ``` A message with information about an event that was emitted in the current contract call. This message MUST be delivered to a recipient in order to not lose the private event information. Use [`EventMessage::deliver_to`](../../noir_aztec/event/struct.EventMessage.html#deliver_to) to select a delivery mechanism. ## Implementations ### `impl [EventMessage](../../noir_aztec/event/struct.EventMessage.html)` `pub fn [deliver_to](#deliver_to)(self, recipient: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html), delivery_mode: [u8](../../std/primitive.u8.html)) where Event: [EventInterface](../../noir_aztec/event/event_interface/trait.EventInterface.html), Event: [Serialize](../../serde/serialization/trait.Serialize.html)` Delivers the event message to a `recipient`, providing them access to the private event information. The same message can be delivered to multiple recipients, resulting in all of them learning about the event. Any recipient that receives the private event information will be able to prove its emission - events have no owner, and as such all recipients are treated equally. The message is first encrypted to the recipient's public key, ensuring no other actor can read it. The `delivery_mode` must be one of [`crate::messages::message_delivery::MessageDeliveryEnum`](../../noir_aztec/messages/message_delivery/struct.MessageDeliveryEnum.html), and will inform costs (both proving time and TX fees) as well as delivery guarantees. This value must be a compile-time constant. #### Invalid Recipients If `recipient` is an invalid address, then a random public key is selected and message delivery continues as normal. This prevents both 'king of the hill' attacks (where a sender would otherwise fail to deliver a message to an invalid recipient) and forced privacy leaks (where an invalid recipient results in a unique transaction fingerprint, e.g. one lacking the private logs that would correspond to message delivery). --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/event/struct.EventSelector.html # Struct EventSelector ``` pub struct EventSelector { /* private fields */ } ``` ## Implementations ### `impl [EventSelector](../../noir_aztec/event/struct.EventSelector.html)` `pub fn [from_u32](#from_u32)(value: [u32](../../std/primitive.u32.html)) -> Self` `pub fn [from_signature](#from_signature)(signature: [str](../../std/primitive.str.html)) -> Self` `pub fn [zero](#zero)() -> Self` ## Trait implementations ### `impl [Deserialize](../../serde/serialization/trait.Deserialize.html) for [EventSelector](../../noir_aztec/event/struct.EventSelector.html)` `pub fn deserialize(fields: [[Field](../../std/primitive.Field.html); 1]) -> Self` `pub fn stream_deserialize(reader: &mut [Reader](../../serde/reader/struct.Reader.html)) -> Self` ### `impl [Empty](../../protocol_types/traits/trait.Empty.html) for [EventSelector](../../noir_aztec/event/struct.EventSelector.html)` `pub fn empty() -> Self` `pub fn is_empty(self) -> [bool](../../std/primitive.bool.html)` `pub fn assert_empty(self, msg: [str](../../std/primitive.str.html))` ### `impl [Eq](../../std/cmp/trait.Eq.html) for [EventSelector](../../noir_aztec/event/struct.EventSelector.html)` `pub fn eq(_self: Self, _other: Self) -> [bool](../../std/primitive.bool.html)` ### `impl [FromField](../../protocol_types/traits/trait.FromField.html) for [EventSelector](../../noir_aztec/event/struct.EventSelector.html)` `pub fn from_field(field: [Field](../../std/primitive.Field.html)) -> Self` ### `impl [Serialize](../../serde/serialization/trait.Serialize.html) for [EventSelector](../../noir_aztec/event/struct.EventSelector.html)` `pub fn serialize(self) -> [[Field](../../std/primitive.Field.html); 1]` `pub fn stream_serialize(self, writer: &mut [Writer](../../serde/writer/struct.Writer.html))` ### `impl [ToField](../../protocol_types/traits/trait.ToField.html) for [EventSelector](../../noir_aztec/event/struct.EventSelector.html)` `pub fn to_field(self) -> [Field](../../std/primitive.Field.html)` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/hash/fn.compute_l1_to_l2_message_hash.html # Function compute_l1_to_l2_message_hash ``` pub fn compute_l1_to_l2_message_hash( sender: [EthAddress](../../protocol_types/address/eth_address/struct.EthAddress.html), chain_id: [Field](../../std/primitive.Field.html), recipient: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html), version: [Field](../../std/primitive.Field.html), content: [Field](../../std/primitive.Field.html), secret_hash: [Field](../../std/primitive.Field.html), leaf_index: [Field](../../std/primitive.Field.html), ) -> [Field](../../std/primitive.Field.html) ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/hash/fn.compute_l1_to_l2_message_nullifier.html # Function compute_l1_to_l2_message_nullifier ``` pub fn compute_l1_to_l2_message_nullifier(message_hash: [Field](../../std/primitive.Field.html), secret: [Field](../../std/primitive.Field.html)) -> [Field](../../std/primitive.Field.html) ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/hash/fn.compute_public_bytecode_commitment.html # Function compute_public_bytecode_commitment ``` pub fn compute_public_bytecode_commitment(packed_public_bytecode: [[Field](../../std/primitive.Field.html); 3000]) -> [Field](../../std/primitive.Field.html) ``` Computes the public bytecode commitment for a contract class. The commitment is `hash([(length | separator), ...bytecode])`. @param packed_bytecode - The packed bytecode of the contract class. 0th word is the length in bytes. packed_bytecode is mutable so that we can avoid copying the array to construct one starting with first_field instead of length. @returns The public bytecode commitment. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/hash/fn.compute_secret_hash.html # Function compute_secret_hash ``` pub fn compute_secret_hash(secret: [Field](../../std/primitive.Field.html)) -> [Field](../../std/primitive.Field.html) ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/hash/fn.compute_siloed_nullifier.html # Function compute_siloed_nullifier ``` pub fn compute_siloed_nullifier( contract_address: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html), nullifier: [Field](../../std/primitive.Field.html), ) -> [Field](../../std/primitive.Field.html) ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/hash/fn.hash_args.html # Function hash_args ``` pub fn hash_args(args: [[Field](../../std/primitive.Field.html); N]) -> [Field](../../std/primitive.Field.html) ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/hash/fn.hash_calldata_array.html # Function hash_calldata_array ``` pub fn hash_calldata_array(calldata: [[Field](../../std/primitive.Field.html); N]) -> [Field](../../std/primitive.Field.html) ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/hash/index.html # Module hash Aztec hash functions. ## Functions - [compute_l1_to_l2_message_hash](fn.compute_l1_to_l2_message_hash.html) - [compute_l1_to_l2_message_nullifier](fn.compute_l1_to_l2_message_nullifier.html) - [compute_public_bytecode_commitment](fn.compute_public_bytecode_commitment.html)Computes the public bytecode commitment for a contract class. The commitment is `hash([(length | separator), ...bytecode])`. - [compute_secret_hash](fn.compute_secret_hash.html) - [compute_siloed_nullifier](fn.compute_siloed_nullifier.html) - [hash_args](fn.hash_args.html) - [hash_calldata_array](fn.hash_calldata_array.html) --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/history/deployment/fn.assert_contract_bytecode_was_not_published_by.html # Function assert_contract_bytecode_was_not_published_by ``` pub fn assert_contract_bytecode_was_not_published_by( block_header: [BlockHeader](../../../protocol_types/abis/block_header/struct.BlockHeader.html), contract_address: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), ) ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/history/deployment/fn.assert_contract_bytecode_was_published_by.html # Function assert_contract_bytecode_was_published_by ``` pub fn assert_contract_bytecode_was_published_by( block_header: [BlockHeader](../../../protocol_types/abis/block_header/struct.BlockHeader.html), contract_address: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), ) ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/history/deployment/fn.assert_contract_was_initialized_by.html # Function assert_contract_was_initialized_by ``` pub fn assert_contract_was_initialized_by( block_header: [BlockHeader](../../../protocol_types/abis/block_header/struct.BlockHeader.html), contract_address: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), init_hash: [Field](../../../std/primitive.Field.html), ) ``` Asserts that a contract was initialized by the given block. `init_hash` is the contract's initialization hash, obtainable via [`get_contract_instance`](../../../noir_aztec/oracle/get_contract_instance/fn.get_contract_instance.html). --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/history/deployment/fn.assert_contract_was_not_initialized_by.html # Function assert_contract_was_not_initialized_by ``` pub fn assert_contract_was_not_initialized_by( block_header: [BlockHeader](../../../protocol_types/abis/block_header/struct.BlockHeader.html), contract_address: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), init_hash: [Field](../../../std/primitive.Field.html), ) ``` Asserts that a contract was not initialized by the given block. `init_hash` is the contract's initialization hash, obtainable via [`get_contract_instance`](../../../noir_aztec/oracle/get_contract_instance/fn.get_contract_instance.html). --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/history/deployment/index.html # Module deployment Contract deployments. ## Functions - [assert_contract_bytecode_was_not_published_by](fn.assert_contract_bytecode_was_not_published_by.html) - [assert_contract_bytecode_was_published_by](fn.assert_contract_bytecode_was_published_by.html) - [assert_contract_was_initialized_by](fn.assert_contract_was_initialized_by.html)Asserts that a contract was initialized by the given block. - [assert_contract_was_not_initialized_by](fn.assert_contract_was_not_initialized_by.html)Asserts that a contract was not initialized by the given block. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/history/index.html # Module history Proofs of Aztec history. ## Data Availability Functions in these modules assert statements about the past state of the Aztec network. While this is possible in theory, actual network nodes might not have the information required to produce proofs at relatively old blocks. In particular, many nodes prune old state and do not keep records of the different state trees (note hashes, nullifiers, public storage, etc.) at blocks older than a couple hours, which are required in order to produce the sibling paths these functions need. An archive node is therefore required when using any of these functions on non-recent blocks. ## Modules - [deployment](deployment/index.html)Contract deployments. - [note](note/index.html)Note existence and non-nullification. - [nullifier](nullifier/index.html)Nullifier existence and non-existence. - [storage](storage/index.html)Historical storage accesses. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/history/note/fn.assert_note_existed_by.html # Function assert_note_existed_by ``` pub fn assert_note_existed_by( block_header: [BlockHeader](../../../protocol_types/abis/block_header/struct.BlockHeader.html), hinted_note: [HintedNote](../../../noir_aztec/note/struct.HintedNote.html), ) -> [ConfirmedNote](../../../noir_aztec/note/struct.ConfirmedNote.html) where Note: [NoteHash](../../../noir_aztec/note/note_interface/trait.NoteHash.html) ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/history/note/fn.assert_note_was_not_nullified_by.html # Function assert_note_was_not_nullified_by ``` pub fn assert_note_was_not_nullified_by( block_header: [BlockHeader](../../../protocol_types/abis/block_header/struct.BlockHeader.html), confirmed_note: [ConfirmedNote](../../../noir_aztec/note/struct.ConfirmedNote.html), context: &mut [PrivateContext](../../../noir_aztec/context/struct.PrivateContext.html), ) where Note: [NoteHash](../../../noir_aztec/note/note_interface/trait.NoteHash.html) ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/history/note/fn.assert_note_was_nullified_by.html # Function assert_note_was_nullified_by ``` pub fn assert_note_was_nullified_by( block_header: [BlockHeader](../../../protocol_types/abis/block_header/struct.BlockHeader.html), confirmed_note: [ConfirmedNote](../../../noir_aztec/note/struct.ConfirmedNote.html), context: &mut [PrivateContext](../../../noir_aztec/context/struct.PrivateContext.html), ) where Note: [NoteHash](../../../noir_aztec/note/note_interface/trait.NoteHash.html) ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/history/note/fn.assert_note_was_valid_by.html # Function assert_note_was_valid_by ``` pub fn assert_note_was_valid_by( block_header: [BlockHeader](../../../protocol_types/abis/block_header/struct.BlockHeader.html), hinted_note: [HintedNote](../../../noir_aztec/note/struct.HintedNote.html), context: &mut [PrivateContext](../../../noir_aztec/context/struct.PrivateContext.html), ) where Note: [NoteHash](../../../noir_aztec/note/note_interface/trait.NoteHash.html) ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/history/note/index.html # Module note Note existence and non-nullification. ## Functions - [assert_note_existed_by](fn.assert_note_existed_by.html) - [assert_note_was_not_nullified_by](fn.assert_note_was_not_nullified_by.html) - [assert_note_was_nullified_by](fn.assert_note_was_nullified_by.html) - [assert_note_was_valid_by](fn.assert_note_was_valid_by.html) --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/history/nullifier/fn.assert_nullifier_did_not_exist_by.html # Function assert_nullifier_did_not_exist_by ``` pub fn assert_nullifier_did_not_exist_by( block_header: [BlockHeader](../../../protocol_types/abis/block_header/struct.BlockHeader.html), siloed_nullifier: [Field](../../../std/primitive.Field.html), ) ``` Asserts that a nullifier did not exist by the time a block was mined. This function takes a siloed nullifier, i.e. the value that is actually stored in the tree. Use [`crate::protocol::hash::compute_siloed_nullifier`](../../../protocol_types/hash/fn.compute_siloed_nullifier.html) to convert an inner nullifier (what [`PrivateContext::push_nullifier`](../../../noir_aztec/context/struct.PrivateContext.html#push_nullifier) takes) into a siloed one. In order to prove that a nullifier did exist by `block_header`, use [`assert_nullifier_existed_by`](../../../noir_aztec/history/nullifier/fn.assert_nullifier_existed_by.html). ## Nullifier Non-Existence Proving nullifier non-existence is always nuanced, as it is not possible to privately prove that a nullifier does not exist by the time a transaction is executed. What this function does instead is assert that once all transactions from `block_header` were executed, the nullifier was not in the tree. If you must prove that a nullifier does not exist by the time a transaction is executed, there only two ways to do this: by actually emitting the nullifier via [`PrivateContext::push_nullifier`](../../../noir_aztec/context/struct.PrivateContext.html#push_nullifier) (which can of course can be done once), or by calling a public contract function that calls [`PublicContext::nullifier_exists_unsafe`](../../../noir_aztec/context/struct.PublicContext.html#nullifier_exists_unsafe) (which leaks that this nullifier is being checked): ``` #[external("public")] #[only_self] fn _assert_nullifier_does_not_exist(unsileod_nullifier: Field, contract_address: AztecAddress) { assert(!self.context.nullifier_exists_unsafe(unsiloed_nullifier, contract_address)); } ``` ## Cost This function performs a single merkle tree inclusion proof, which is ~4k gates. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/history/nullifier/fn.assert_nullifier_existed_by.html # Function assert_nullifier_existed_by ``` pub fn assert_nullifier_existed_by(block_header: [BlockHeader](../../../protocol_types/abis/block_header/struct.BlockHeader.html), siloed_nullifier: [Field](../../../std/primitive.Field.html)) ``` Asserts that a nullifier existed by the time a block was mined. This function takes a siloed nullifier, i.e. the value that is actually stored in the tree. Use [`crate::protocol::hash::compute_siloed_nullifier`](../../../protocol_types/hash/fn.compute_siloed_nullifier.html) to convert an inner nullifier (what [`PrivateContext::push_nullifier`](../../../noir_aztec/context/struct.PrivateContext.html#push_nullifier) takes) into a siloed one. Note that this does not mean that the nullifier was created at `block_header`, only that it was present in the tree once all transactions from `block_header` were executed. In order to prove that a nullifier did not exist by `block_header`, use [`assert_nullifier_did_not_exist_by`](../../../noir_aztec/history/nullifier/fn.assert_nullifier_did_not_exist_by.html). ## Cost This function performs a single merkle tree inclusion proof, which is ~4k gates. If you don't need to assert existence at a specific past block, consider using [`PrivateContext::assert_nullifier_exists`](../../../noir_aztec/context/struct.PrivateContext.html#assert_nullifier_exists) instead, which is typically cheaper. Note that there are semantic differences though, as that function also considers pending nullifiers. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/history/nullifier/index.html # Module nullifier Nullifier existence and non-existence. ## Functions - [assert_nullifier_did_not_exist_by](fn.assert_nullifier_did_not_exist_by.html)Asserts that a nullifier did not exist by the time a block was mined. - [assert_nullifier_existed_by](fn.assert_nullifier_existed_by.html)Asserts that a nullifier existed by the time a block was mined. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/history/storage/fn.public_storage_historical_read.html # Function public_storage_historical_read ``` pub fn public_storage_historical_read( block_header: [BlockHeader](../../../protocol_types/abis/block_header/struct.BlockHeader.html), storage_slot: [Field](../../../std/primitive.Field.html), contract_address: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), ) -> [Field](../../../std/primitive.Field.html) ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/history/storage/index.html # Module storage Historical storage accesses. ## Functions - [public_storage_historical_read](fn.public_storage_historical_read.html) --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/index.html # Crate noir_aztec The aztec-nr framework for writing Aztec smart contracts. This is the detailed API reference for aztec-nr. It assumes that the reader is a developer familiar with Aztec contracts. ## Beginners If you don't know what Aztec is, head to the [Aztec documentation](https://docs.aztec.network). If you don't know what aztec-nr is or how to install it, head to the [aztec-nr documentation](https://docs.aztec.network/developers/docs/aztec-nr). ## How to Use This Documentation aztec-nr is split into multiple [modules](index.html#modules), each of which deals with some high-level concept. If you already know which module you're interested in, just navigate to it directly. Here are some frequently used modules: - [`macros`](../noir_aztec/macros/index.html): the attributes applied to contract functions, such as [`[#external]`](../noir_aztec/macros/functions/fn.external.html) and [`[#initializer]`](../noir_aztec/macros/functions/fn.initializer.html), as well as definition of contract types such as [`[#storage]`](../noir_aztec/macros/storage/fn.storage.html) and [`[#event]`](../noir_aztec/macros/events/fn.event.html). - [`state_vars`](../noir_aztec/state_vars/index.html): types for onchain storage of contract state, such as [`PrivateMutable`](../noir_aztec/state_vars/struct.PrivateMutable.html), [`PublicMutable`](../noir_aztec/state_vars/struct.PublicMutable.html) and [`Map`](../noir_aztec/state_vars/struct.Map.html). - [`test`](../noir_aztec/test/index.html): for writing Aztec tests in Noir using [`TestEnvironment`](../noir_aztec/test/helpers/test_environment/struct.TestEnvironment.html) and [mocks](../noir_aztec/test/mocks/index.html). ## Modules - [authwit](authwit/index.html)Authorization. - [capsules](capsules/index.html) - [context](context/index.html)Private, public and utility execution contexts. - [contract_self](contract_self/index.html) - [ephemeral](ephemeral/index.html) - [event](event/index.html)Event traits and utilities. - [hash](hash/index.html)Aztec hash functions. - [history](history/index.html)Proofs of Aztec history. - [keys](keys/index.html)Privacy keys (npk, ivsk, etc.). - [macros](macros/index.html)Definition of Aztec contract functions, storage, notes and events. - [messages](messages/index.html)Message encoding, encryption, delivery and processing. - [note](note/index.html)Note traits and utilities - [nullifier](nullifier/index.html)Nullifier-related utilities. - [oracle](oracle/index.html)Standard PXE oracles. - [protocol](protocol/index.html)Types and constants related to the Aztec protocol. - [publish_contract_instance](publish_contract_instance/index.html)Public contract deployment. - [state_vars](state_vars/index.html)Storage for contract state. - [test](test/index.html)The [TestEnvironment](../noir_aztec/test/helpers/test_environment/struct.TestEnvironment.html) utility and mock types. - [utils](utils/index.html)Miscellaneous utilities. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/keys/constants/global.INCOMING_INDEX.html # Global INCOMING_INDEX ``` pub global INCOMING_INDEX: [Field](../../../std/primitive.Field.html); ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/keys/constants/global.NULLIFIER_INDEX.html # Global NULLIFIER_INDEX ``` pub global NULLIFIER_INDEX: [Field](../../../std/primitive.Field.html); ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/keys/constants/global.OUTGOING_INDEX.html # Global OUTGOING_INDEX ``` pub global OUTGOING_INDEX: [Field](../../../std/primitive.Field.html); ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/keys/constants/global.TAGGING_INDEX.html # Global TAGGING_INDEX ``` pub global TAGGING_INDEX: [Field](../../../std/primitive.Field.html); ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/keys/constants/global.public_key_domain_separators.html # Global public_key_domain_separators ``` pub global public_key_domain_separators: [[Field](../../../std/primitive.Field.html); 4]; ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/keys/constants/index.html # Module constants ## Globals - [INCOMING_INDEX](global.INCOMING_INDEX.html) - [NULLIFIER_INDEX](global.NULLIFIER_INDEX.html) - [NUM_KEY_TYPES](global.NUM_KEY_TYPES.html) - [OUTGOING_INDEX](global.OUTGOING_INDEX.html) - [public_key_domain_separators](global.public_key_domain_separators.html) - [TAGGING_INDEX](global.TAGGING_INDEX.html) --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/keys/ecdh_shared_secret/fn.derive_ecdh_shared_secret.html # Function derive_ecdh_shared_secret ``` pub fn derive_ecdh_shared_secret(secret: [EmbeddedCurveScalar](../../../protocol_types/scalar/struct.Scalar.html), public_key: [Point](../../../protocol_types/point/struct.Point.html)) -> [Point](../../../protocol_types/point/struct.Point.html) ``` Computes a standard ECDH shared secret: secret * public_key = shared_secret. The input secret is known only to one party. The output shared secret can be derived given knowledge of `public_key`'s key-pair and the public ephemeral secret, using this same function (with reversed inputs). E.g.: Epk = esk * G // ephemeral key-pair Pk = sk * G // recipient key-pair Shared secret S = esk * Pk = sk * Epk See also: [https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman](https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman) --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/keys/ecdh_shared_secret/index.html # Module ecdh_shared_secret ## Functions - [derive_ecdh_shared_secret](fn.derive_ecdh_shared_secret.html)Computes a standard ECDH shared secret: secret * public_key = shared_secret. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/keys/ephemeral/fn.generate_ephemeral_key_pair.html # Function generate_ephemeral_key_pair ``` pub fn generate_ephemeral_key_pair() -> ([EmbeddedCurveScalar](../../../protocol_types/scalar/struct.Scalar.html), [Point](../../../protocol_types/point/struct.Point.html)) ``` Generates a random ephemeral key pair. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/keys/ephemeral/fn.generate_positive_ephemeral_key_pair.html # Function generate_positive_ephemeral_key_pair ``` pub fn generate_positive_ephemeral_key_pair() -> ([EmbeddedCurveScalar](../../../protocol_types/scalar/struct.Scalar.html), [Point](../../../protocol_types/point/struct.Point.html)) ``` Generates a random ephemeral key pair with a positive y-coordinate. Unlike [`generate_ephemeral_key_pair`](../../../noir_aztec/keys/ephemeral/fn.generate_ephemeral_key_pair.html), the y-coordinate of the public key is guaranteed to be a positive value (i.e. [`crate::utils::point::get_sign_of_point`](../../../noir_aztec/utils/point/fn.get_sign_of_point.html) will return `true`). This is useful as it means it is possible to just broadcast the x-coordinate as a single `Field` and then reconstruct the original public key using [`crate::utils::point::point_from_x_coord_and_sign`](../../../noir_aztec/utils/point/fn.point_from_x_coord_and_sign.html) with `sign: true`. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/keys/ephemeral/index.html # Module ephemeral ## Functions - [generate_ephemeral_key_pair](fn.generate_ephemeral_key_pair.html)Generates a random ephemeral key pair. - [generate_positive_ephemeral_key_pair](fn.generate_positive_ephemeral_key_pair.html)Generates a random ephemeral key pair with a positive y-coordinate. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/keys/getters/fn.get_nhk_app.html # Function get_nhk_app ``` pub unconstrained fn get_nhk_app(npk_m_hash: [Field](../../../std/primitive.Field.html)) -> [Field](../../../std/primitive.Field.html) ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/keys/getters/fn.get_ovsk_app.html # Function get_ovsk_app ``` pub unconstrained fn get_ovsk_app(ovpk_m_hash: [Field](../../../std/primitive.Field.html)) -> [Field](../../../std/primitive.Field.html) ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/keys/getters/fn.get_public_keys.html # Function get_public_keys ``` pub fn get_public_keys(account: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html)) -> [PublicKeys](../../../protocol_types/public_keys/struct.PublicKeys.html) ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/keys/getters/fn.try_get_public_keys.html # Function try_get_public_keys ``` pub unconstrained fn try_get_public_keys(account: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html)) -> [Option](../../../std/option/struct.Option.html)<[PublicKeys](../../../protocol_types/public_keys/struct.PublicKeys.html)> ``` Returns all public keys for a given account, or `None` if the public keys are not registered in the PXE. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/keys/getters/index.html # Module getters ## Functions - [get_nhk_app](fn.get_nhk_app.html) - [get_ovsk_app](fn.get_ovsk_app.html) - [get_public_keys](fn.get_public_keys.html) - [try_get_public_keys](fn.try_get_public_keys.html)Returns all public keys for a given account, or `None` if the public keys are not registered in the PXE. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/keys/index.html # Module keys Privacy keys (npk, ivsk, etc.). ## Modules - [constants](constants/index.html) - [ecdh_shared_secret](ecdh_shared_secret/index.html) - [ephemeral](ephemeral/index.html) - [getters](getters/index.html) --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/authorization/fn.authorization.html # Function authorization ``` pub comptime fn authorization(s: [TypeDefinition](../../../std/primitive.TypeDefinition.html)) -> [Quoted](../../../std/primitive.Quoted.html) ``` An Authorization is a struct that represents an action a user can allow others to do on their behalf. By definition, an Authorization must be human-readable and contain enough information to be interpreted on its own. Authorizations are: - Emitted as offchain effects to convey the piece of data a contract needs to be signed in order to perform the action - Hashed in a specific way to produce an inner_hash that can later be signed and checked via the authwit mechanism. This allows a contract developer to convey to a user in a human-readable way what they are asked to sign, while keeping the account contract interface simple (it just has to check if a hash was signed). It should always be possible to recompute the inner_hash from the Authorization alone, so the user/wallet can verify the action they are signing. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/authorization/fn.generate_authorization_interface_and_get_selector.html # Function generate_authorization_interface_and_get_selector ``` pub comptime fn generate_authorization_interface_and_get_selector( s: [TypeDefinition](../../../std/primitive.TypeDefinition.html), ) -> ([Quoted](../../../std/primitive.Quoted.html), [Field](../../../std/primitive.Field.html)) ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/authorization/index.html # Module authorization ## Functions - [authorization](fn.authorization.html)An Authorization is a struct that represents an action a user can allow others to do on their behalf. By definition, an Authorization must be human-readable and contain enough information to be interpreted on its own. Authorizations are: - [generate_authorization_interface_and_get_selector](fn.generate_authorization_interface_and_get_selector.html) --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/dispatch/fn.generate_public_dispatch.html # Function generate_public_dispatch ``` pub comptime fn generate_public_dispatch( m: [Module](../../../std/primitive.Module.html), generate_emit_public_init_nullifier: [bool](../../../std/primitive.bool.html), ) -> [Quoted](../../../std/primitive.Quoted.html) ``` Generates a `public_dispatch` function for an Aztec contract module `m`. The generated function dispatches public calls based on selector to the appropriate contract function. If `generate_emit_public_init_nullifier` is true, it also handles dispatch to the macro-generated `__emit_public_init_nullifier` function. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/dispatch/index.html # Module dispatch ## Functions - [generate_public_dispatch](fn.generate_public_dispatch.html)Generates a `public_dispatch` function for an Aztec contract module `m`. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/events/fn.event.html # Function event ``` pub comptime fn event(s: [TypeDefinition](../../../std/primitive.TypeDefinition.html)) -> [Quoted](../../../std/primitive.Quoted.html) ``` Generates the core event functionality for a struct, including the [`EventInterface`](../../../noir_aztec/event/event_interface/trait.EventInterface.html) implementation (which provides the event type id) and a [`Serialize`](../../../serde/serialization/trait.Serialize.html) implementation if one is not already provided. ## Requirements Events can be emitted both privately and publicly. Public emission imposes no requirements, but for an event to be emittable privately its serialization length must not exceeed [`MAX_EVENT_SERIALIZED_LEN`](../../../noir_aztec/messages/logs/event/global.MAX_EVENT_SERIALIZED_LEN.html). --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/events/global.EVENT_SELECTORS.html # Global EVENT_SELECTORS ``` pub mut comptime global EVENT_SELECTORS: CHashMap<[Field](../../../std/primitive.Field.html), [Quoted](../../../std/primitive.Quoted.html)>; ``` A map from event selector to event name indicating whether the event selector has already been seen during the contract compilation - prevents event selector collisions. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/events/index.html # Module events ## Functions - [event](fn.event.html)Generates the core event functionality for a struct, including the [`EventInterface`](../../../noir_aztec/event/event_interface/trait.EventInterface.html) implementation (which provides the event type id) and a [`Serialize`](../../../serde/serialization/trait.Serialize.html) implementation if one is not already provided. ## Globals - [EVENT_SELECTORS](global.EVENT_SELECTORS.html)A map from event selector to event name indicating whether the event selector has already been seen during the contract compilation - prevents event selector collisions. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/fn.aztec.html # Function aztec ``` pub comptime fn aztec(m: [Module](../../std/primitive.Module.html), args: [[AztecConfig](../../noir_aztec/macros/struct.AztecConfig.html)]) -> [Quoted](../../std/primitive.Quoted.html) ``` Enables aztec-nr features on a `contract`. All aztec-nr contracts should have this macro invoked on them, as it is the one that processes all contract functions, notes, storage, generates interfaces for external calls, and creates the message processing boilerplate. ## Examples Most contracts can simply invoke the macro with no parameters, resulting in default aztec-nr behavior: ``` #[aztec] contract MyContract { ... } ``` Advanced contracts can use [`AztecConfig`](../../noir_aztec/macros/struct.AztecConfig.html) to customize parts of its behavior, such as message processing. ``` #[aztec(aztec::macros::AztecConfig::new().custom_message_handler(my_handler))] contract MyAdvancedContract { ... } ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/functions/fn.allow_phase_change.html # Function allow_phase_change ``` pub comptime fn allow_phase_change(f: [FunctionDefinition](../../../std/primitive.FunctionDefinition.html)) ``` An `allow_phase_change` function will allow transitioning from the non-revertible to the revertible phase during its execution. This is an advanced feature that is typically only required for account contract entrypoints that handle transaction fee payment. ## Note Retrieval When notes are read e.g. via [`crate::note::note_getter::get_notes`](../../../noir_aztec/note/note_getter/fn.get_notes.html), a [`ConfirmedNote`](../../../noir_aztec/note/struct.ConfirmedNote.html) value is returned which includes note metadata, notably whether the note was created in a previous transaction (a settled note) or in the current one (a pending note), either in the current or previous phase. Because `allow_phase_change` functions can change phases, this metadata can be left stale, e.g. it is possible to read a pending current phase note and then change phases, resulting in the note being [`PENDING_PREVIOUS_PHASE`]() instead. Such a note would be rejected by the kernels if it was tried to be nullified, as the metadata is incorrect. It is therefore important to not allow [`ConfirmedNote`](../../../noir_aztec/note/struct.ConfirmedNote.html), [`HintedNote`](../../../noir_aztec/note/struct.HintedNote.html) or [`NoteMetadata`](../../../noir_aztec/note/note_metadata/struct.NoteMetadata.html) values to cross the phase barrier caused by [`crate::context::PrivateContext::end_setup`](../../../noir_aztec/context/struct.PrivateContext.html#end_setup). --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/functions/fn.authorize_once.html # Function authorize_once ``` pub comptime fn authorize_once( f: [FunctionDefinition](../../../std/primitive.FunctionDefinition.html), from_arg_name: [CtString](../../../std/primitive.CtString.html), nonce_arg_name: [CtString](../../../std/primitive.CtString.html), ) ``` Restricts access to an [`external`](../../../noir_aztec/macros/functions/fn.external.html) private or public function so that it can only be called by an authorized account. Receives the name of a `from` `AztecAddress` variable which will be the default authorized account, and the name of a `nonce` `Field` variable which is used by `from` to grant one-time-only access to other accounts. ## Usage The `from` account can always call an [`authorize_once`](../../../noir_aztec/macros/functions/fn.authorize_once.html) function by passing a value of 0 as the `nonce`. Any other caller requires explicit permission granted by the `from` account, which 1) will be tied to a specific `nonce` value that must be passed by the caller, and which can only be used once, and 2) will restrict all other function params to be exactly the ones that have been authorized by `from`. ## Cost Private functions perform a private authwit check by calling the standard account contract `verify_private_authwit` function on `from` with the hash resulting of all function params and the nonce. A nullifier is then emitted, preventing the same permission from being used again. Note that this requires that the caller has access to `from`'s contract class ID and salted initialization hash, as it would otherwise not be possible to call the `verify_private_authwit` function. Public functions call the `consume` function on the `AuthRegistry` contract, which requires that either a) `from` first calls the `set_authorized` public function, or b) that a private authwit check by `from` is passed for the registry's `set_authorized_private` function, allowing in turn the caller to privately call `set_authorized_private` in the same transaction in which the [`authorize_once`](../../../noir_aztec/macros/functions/fn.authorize_once.html) function is invoked. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/functions/fn.external.html # Function external ``` pub comptime fn external(f: [FunctionDefinition](../../../std/primitive.FunctionDefinition.html), f_type: [CtString](../../../std/primitive.CtString.html)) ``` An external function is callable from outside the contract. There are three types of external functions: - private: executed client-side and preserve privacy - public: executed by the sequencer and do not preserve privacy (like on the EVM) - utility: helpers that are never executed on-chain, typically used for state queries [`external`](../../../noir_aztec/macros/functions/fn.external.html) functions are the Aztec equivalent of a Solidity `external` function, though their behavior differs slightly for public, private and utiliy functions. [`external`](../../../noir_aztec/macros/functions/fn.external.html) functions can also be made [`initializer`](../../../noir_aztec/macros/functions/fn.initializer.html) (for contract intialization), [`view`](../../../noir_aztec/macros/functions/fn.view.html) (for read-only behavior), [`authorize_once`](../../../noir_aztec/macros/functions/fn.authorize_once.html) (for basic access control) and [`only_self`](../../../noir_aztec/macros/functions/fn.only_self.html) (to prevent other contracts from calling them). --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/functions/fn.initializer.html # Function initializer ``` pub comptime fn initializer(f: [FunctionDefinition](../../../std/primitive.FunctionDefinition.html)) ``` An initializer function is where a contract initializes its state. Initializer functions are similar to constructors: - can only be called once - [`external`](../../../noir_aztec/macros/functions/fn.external.html) non-initializer functions cannot be called until one of the initializers has been called The only exception are [`noinitcheck`](../../../noir_aztec/macros/functions/fn.noinitcheck.html) functions, which can be called even if none of the initializers has been called. ## Initialization Commitment All contract instances have their address include a commitment to one of their initializer functions, along with parameters and calling address. Any of the following will therefore fail: - calling the wrong initializer function - calling the initializer function with incorrect parameters - calling the initializer function from the incorrect address It is possible however to allow for any account to call the specified initializer by setting the intended caller to the zero address. These are called 'universal deployments'. ## Multiple Initializers A contract can have multiple initializer functions, but it is not possible to call multiple initializers on the same instance: all initializers become disabled once any of them executes. Each individual instance can call any of the initializers. Initializers can be either private or public. If a contract needs to initialize both private and public state, then it should have an [`external`](../../../noir_aztec/macros/functions/fn.external.html) private function marked as [`initializer`](../../../noir_aztec/macros/functions/fn.initializer.html) which then enqueues a call to an [`external`](../../../noir_aztec/macros/functions/fn.external.html) public function not marked as [`initializer`](../../../noir_aztec/macros/functions/fn.initializer.html) and instead marked as [`only_self`](../../../noir_aztec/macros/functions/fn.only_self.html) (so that it can only be called in this manner). ## Lack of Initializers If a contract has no initializer function, initialization is then not required and all functions can be called at any time. Contracts that do have initializers can also make some of their functions available prior to initialization by marking them with the `#[noinitcheck]` attribute - though any contract state initialization will of course not have taken place. ## How It Works Initializers emit nullifiers to mark the contract as initialized. Two separate nullifiers are used (a private initialization nullifier and a public initialization nullifier) because private nullifiers are committed before public execution begins: if a single nullifier were used, public functions enqueued by the initializer would see it as existing before the public initialization code had a chance to run. The private initialization nullifier is computed from the contract address and the contract's `init_hash`. This means that address knowledge alone is insufficient to check whether a contract has been initialized, preventing a privacy leak for fully private contracts. - Private initializers emit the private initialization nullifier. For contracts that also have external public functions, they auto-enqueue a call to an auto-generated public function that emits the public initialization nullifier during public execution. This function name is reserved and cannot be used by contract developers. - Public initializers emit both nullifiers directly. - Private external functions check the private initialization nullifier. - Public external functions check the public initialization nullifier. For private non-initializer functions, the cost of this check is equivalent to a call to [`PrivateContext::assert_nullifier_exists`](../../../noir_aztec/context/struct.PrivateContext.html#assert_nullifier_exists). For public ones, it is equivalent to a call to [`PublicContext::nullifier_exists_unsafe`](../../../noir_aztec/context/struct.PublicContext.html#nullifier_exists_unsafe). The [`noinitcheck`](../../../noir_aztec/macros/functions/fn.noinitcheck.html) attribute can be used to skip these checks. [`only_self`](../../../noir_aztec/macros/functions/fn.only_self.html) functions also implicitly skip them. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/functions/fn.internal.html # Function internal ``` pub comptime fn internal(f: [FunctionDefinition](../../../std/primitive.FunctionDefinition.html), f_type: [CtString](../../../std/primitive.CtString.html)) ``` An internal function is only callable from inside the contract. [`internal`](../../../noir_aztec/macros/functions/fn.internal.html) functions are the Aztec equivalent of a Solidity `internal` function, though their behavior differs slightly for public, private and utiliy functions. Note that these are different from [`only_self`](../../../noir_aztec/macros/functions/fn.only_self.html) in that those are [`external`](../../../noir_aztec/macros/functions/fn.external.html) and so are externally called (via contract calls). --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/functions/fn.noinitcheck.html # Function noinitcheck ``` pub comptime fn noinitcheck(f: [FunctionDefinition](../../../std/primitive.FunctionDefinition.html)) ``` A no-init-check [`external`](../../../noir_aztec/macros/functions/fn.external.html) function can be called even if none of the [`initializer`](../../../noir_aztec/macros/functions/fn.initializer.html) functions have been called yet. Contracts that have no [`initializer`](../../../noir_aztec/macros/functions/fn.initializer.html) functions do not require this attribute: all of their functions behave as if they implicitly had it (i.e. they don't check for initialization nullifiers). ## Use Case This is an optimization as it skips the initialization check, possibly reducing proving time for private functions and L2 gas for public ones. It is dangerous attribute however, as the contract will not be able to rely on its storage having been initialized. Valid use cases include those in which the contract indirectly infers initialization, e.g. by reading one of its state variables and testing for a non-default value. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/functions/fn.only_self.html # Function only_self ``` pub comptime fn only_self(f: [FunctionDefinition](../../../std/primitive.FunctionDefinition.html)) ``` Restricts an [`external`](../../../noir_aztec/macros/functions/fn.external.html) function so that it can only be called by the contract itself. [`external`](../../../noir_aztec/macros/functions/fn.external.html) functions can normally be called by any address - it is up to contracts to set up access control checks e.g. to admin functions. [`only_self`](../../../noir_aztec/macros/functions/fn.only_self.html) similarly makes it so that the only authorized address is the account contract, i.e. the function requires reentrancy. These are different from [`internal`](../../../noir_aztec/macros/functions/fn.internal.html) functions in that those are internally called, i.e. they don't result in contract calls. ## Use Cases This attribute can be applied to both private or public [`external`](../../../noir_aztec/macros/functions/fn.external.html) functions, so there are multiple scenarios to consider. ### Private Private functions can only be externally called by other private functions. In this case, [`only_self`](../../../noir_aztec/macros/functions/fn.only_self.html) can be used to recursively call circuits, achieving proving time performance improvements for low-load scenarios. For example, consider a token transfer for some amount: the number of notes that need to be read and nullified is not known at compile time. Selecting a large maximum number of notes to read per transfer would result in a large circuit with excess capacity in transfers of few notes, while a small maximum would outright prevent many notes from being used at once. A better approach is to create a private [`only_self`](../../../noir_aztec/macros/functions/fn.only_self.html) external function in which notes are read and nullified, recursively calling itself if the sum of the values does not add up to some target amount. This makes the circuit adapt itself to the number of notes required, resulting in either few or many recursive invocations and therefore proving time roughly proportional to the number of notes. The recursive function must be [`only_self`](../../../noir_aztec/macros/functions/fn.only_self.html) because we want to prevent any other contract from calling it - it is only an internal mechanism. ### Public Public functions can only be externally called by both private and public functions. Public to public [`only_self`](../../../noir_aztec/macros/functions/fn.only_self.html) calls are rare and not very useful (it'd be equivalent to an `external` Solidity function with `require(msg.sender == address(this))`). Private to public calls do enable useful design patterns though. A private function that needs to perform some public check (like some public state assertion) or follow-up public action (like some public state mutation) can do so by enqueuing a call to an [`external`](../../../noir_aztec/macros/functions/fn.external.html) [`only_self`](../../../noir_aztec/macros/functions/fn.only_self.html) public function. The [`only_self`](../../../noir_aztec/macros/functions/fn.only_self.html) attribute will prevent external callers from invoking the function, making it be a purely internal mechanism. A classic example is a private mint function in a token, which would require an enqueued public call in which the total supply is incremented. ## Initialization Checks `only_self` functions implicitly skip initialization checks (as if they had [`noinitcheck`](../../../noir_aztec/macros/functions/fn.noinitcheck.html)). We want `only_self` functions to be callable during initialization, so we can't have them check the initialization nullifier since it would fail. This is safe because `only_self` functions can be called only by the contract the function is in, meaning execution must start with another external function in the same contract. Eventually the call stack reaches the `only_self` function, but let's focus on that external entry point: - If it already performed an initialization check, then we are safe. - If it skipped the initialization check (via [`noinitcheck`](../../../noir_aztec/macros/functions/fn.noinitcheck.html)), then the contract developer is explicitly choosing to not check for initialization, and so will our `only_self` function. That's a design choice by the developer. If we didn't skip the initialization check on `only_self`, the developer would just add `noinitcheck` to it anyway. - If it was the initializer, note that initialization nullifiers are emitted at the end of initialization: the private initialization nullifier after all private execution, and the public one after all public execution. So in terms of initialization checking, everything behaves as if the contract hasn't been initialized yet, and the same two points above still apply. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/functions/fn.view.html # Function view ``` pub comptime fn view(f: [FunctionDefinition](../../../std/primitive.FunctionDefinition.html)) ``` View functions cannot modify state in any way, including performing contract calls that would in turn modify state. This makes them easy to reason about: they are simply 'pure' functions that return a value, and carry e.g. no reentrancy risk. ## Use Cases Only public and private functions can be [`view`](../../../noir_aztec/macros/functions/fn.view.html). Private [`view`](../../../noir_aztec/macros/functions/fn.view.html) functions are typically not very useful as they cannot emit nullifiers, which is often required when reading private state (e.g. from a [`crate::state_vars::private_mutable::PrivateMutable`](../../../noir_aztec/state_vars/struct.PrivateMutable.html) or [`crate::state_vars::private_set::PrivateSet`](../../../noir_aztec/state_vars/struct.PrivateSet.html) state variable). They do however have their use cases, such as performing a [`crate::state_vars::delayed_public_mutable::DelayedPublicMutable`](../../../noir_aztec/state_vars/struct.DelayedPublicMutable.html) read. Public view functions on the other hand are very common, since reading public storage does not require any state modifications. These are essentially equivalent to a Solidity `view` function. ## Guarantees [`view`](../../../noir_aztec/macros/functions/fn.view.html) functions can only be called in a static execution context, which is typically achieved by calling the [`crate::contract_self::ContractSelfPublic::view`](../../../noir_aztec/contract_self/contract_self_public/struct.ContractSelfPublic.html#view) method on `self`. No compile time checks are performed on whether a function can be made [`view`](../../../noir_aztec/macros/functions/fn.view.html). If a function marked as view attempts to modify state, that will result in runtime failures. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/functions/index.html # Module functions ## Modules - [initialization_utils](initialization_utils/index.html) ## Functions - [allow_phase_change](fn.allow_phase_change.html)An `allow_phase_change` function will allow transitioning from the non-revertible to the revertible phase during its execution. - [authorize_once](fn.authorize_once.html)Restricts access to an [`external`](../../../noir_aztec/macros/functions/fn.external.html) private or public function so that it can only be called by an authorized account. - [external](fn.external.html)An external function is callable from outside the contract. - [initializer](fn.initializer.html)An initializer function is where a contract initializes its state. - [internal](fn.internal.html)An internal function is only callable from inside the contract. - [noinitcheck](fn.noinitcheck.html)A no-init-check [`external`](../../../noir_aztec/macros/functions/fn.external.html) function can be called even if none of the [`initializer`](../../../noir_aztec/macros/functions/fn.initializer.html) functions have been called yet. - [only_self](fn.only_self.html)Restricts an [`external`](../../../noir_aztec/macros/functions/fn.external.html) function so that it can only be called by the contract itself. - [view](fn.view.html)View functions cannot modify state in any way, including performing contract calls that would in turn modify state. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/functions/initialization_utils/fn.assert_initialization_matches_address_preimage_private.html # Function assert_initialization_matches_address_preimage_private ``` pub fn assert_initialization_matches_address_preimage_private(context: [PrivateContext](../../../../noir_aztec/context/struct.PrivateContext.html)) ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/functions/initialization_utils/fn.assert_initialization_matches_address_preimage_public.html # Function assert_initialization_matches_address_preimage_public ``` pub fn assert_initialization_matches_address_preimage_public(context: [PublicContext](../../../../noir_aztec/context/struct.PublicContext.html)) ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/functions/initialization_utils/fn.assert_is_initialized_private.html # Function assert_is_initialized_private ``` pub fn assert_is_initialized_private(context: &mut [PrivateContext](../../../../noir_aztec/context/struct.PrivateContext.html)) ``` Asserts that the contract has been initialized, from private's perspective. Checks that the private initialization nullifier exists. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/functions/initialization_utils/fn.assert_is_initialized_public.html # Function assert_is_initialized_public ``` pub unconstrained fn assert_is_initialized_public(context: [PublicContext](../../../../noir_aztec/context/struct.PublicContext.html)) ``` Asserts that the contract has been initialized, from public's perspective. Checks that the public initialization nullifier exists. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/functions/initialization_utils/fn.assert_is_initialized_utility.html # Function assert_is_initialized_utility ``` pub unconstrained fn assert_is_initialized_utility(context: [UtilityContext](../../../../noir_aztec/context/struct.UtilityContext.html)) ``` Asserts that the contract has been initialized, from a utility function's perspective. Only checks the private initialization nullifier in the settled nullifier tree. Since both nullifiers are emitted in the same transaction, the private nullifier's presence in settled state guarantees the public one is also settled. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/functions/initialization_utils/fn.compute_initialization_hash.html # Function compute_initialization_hash ``` pub fn compute_initialization_hash( init_selector: [FunctionSelector](../../../../protocol_types/abis/function_selector/struct.FunctionSelector.html), init_args_hash: [Field](../../../../std/primitive.Field.html), ) -> [Field](../../../../std/primitive.Field.html) ``` This function is not only used in macros but it's also used by external people to check that an instance has been initialized with the correct constructor arguments. Don't hide this unless you implement factory functionality. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/functions/initialization_utils/fn.compute_private_initialization_nullifier.html # Function compute_private_initialization_nullifier ``` pub fn compute_private_initialization_nullifier( address: [AztecAddress](../../../../protocol_types/address/aztec_address/struct.AztecAddress.html), init_hash: [Field](../../../../std/primitive.Field.html), ) -> [Field](../../../../std/primitive.Field.html) ``` Computes the private initialization nullifier for a contract. Including `init_hash` ensures that an observer who knows only the contract address cannot reconstruct this value and scan the nullifier tree to determine initialization status. `init_hash` is only known to parties that hold the contract instance. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/functions/initialization_utils/fn.mark_as_initialized_from_private_initializer.html # Function mark_as_initialized_from_private_initializer ``` pub fn mark_as_initialized_from_private_initializer( context: &mut [PrivateContext](../../../../noir_aztec/context/struct.PrivateContext.html), emit_public_init_nullifier: [bool](../../../../std/primitive.bool.html), ) ``` Emits the private initialization nullifier and, if relevant, enqueues the emission of the public one. If the contract has public functions that perform initialization checks (i.e. that don't have `#[noinitcheck]`), this also enqueues a call to the auto-generated `__emit_public_init_nullifier` function so the public initialization nullifier is emitted in public. Called by private [`initializer`](../../../../noir_aztec/macros/functions/fn.initializer.html) macros. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/functions/initialization_utils/fn.mark_as_initialized_from_public_initializer.html # Function mark_as_initialized_from_public_initializer ``` pub fn mark_as_initialized_from_public_initializer(context: [PublicContext](../../../../noir_aztec/context/struct.PublicContext.html)) ``` Emits both initialization nullifiers (private and public). Called by public [`initializer`](../../../../noir_aztec/macros/functions/fn.initializer.html) macros, since public initializers must set both so that both private and public functions see the contract as initialized. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/functions/initialization_utils/fn.mark_as_initialized_public.html # Function mark_as_initialized_public ``` pub fn mark_as_initialized_public(context: [PublicContext](../../../../noir_aztec/context/struct.PublicContext.html)) ``` Emits (only) the public initialization nullifier. This function is called by the aztec-nr auto-generated external public contract function (enqueued by private [`initializer`](../../../../noir_aztec/macros/functions/fn.initializer.html) functions), and also by [`mark_as_initialized_from_public_initializer`](../../../../noir_aztec/macros/functions/initialization_utils/fn.mark_as_initialized_from_public_initializer.html) for public initializers. ## Warning This should not be called manually. Incorrect use can leave the contract in a broken initialization state (e.g. emitting the public nullifier without the private one). The macro-generated code handles this automatically. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/functions/initialization_utils/index.html # Module initialization_utils ## Functions - [assert_initialization_matches_address_preimage_private](fn.assert_initialization_matches_address_preimage_private.html) - [assert_initialization_matches_address_preimage_public](fn.assert_initialization_matches_address_preimage_public.html) - [assert_is_initialized_private](fn.assert_is_initialized_private.html)Asserts that the contract has been initialized, from private's perspective. - [assert_is_initialized_public](fn.assert_is_initialized_public.html)Asserts that the contract has been initialized, from public's perspective. - [assert_is_initialized_utility](fn.assert_is_initialized_utility.html)Asserts that the contract has been initialized, from a utility function's perspective. - [compute_initialization_hash](fn.compute_initialization_hash.html)This function is not only used in macros but it's also used by external people to check that an instance has been initialized with the correct constructor arguments. Don't hide this unless you implement factory functionality. - [compute_private_initialization_nullifier](fn.compute_private_initialization_nullifier.html)Computes the private initialization nullifier for a contract. - [mark_as_initialized_from_private_initializer](fn.mark_as_initialized_from_private_initializer.html)Emits the private initialization nullifier and, if relevant, enqueues the emission of the public one. - [mark_as_initialized_from_public_initializer](fn.mark_as_initialized_from_public_initializer.html)Emits both initialization nullifiers (private and public). - [mark_as_initialized_public](fn.mark_as_initialized_public.html)Emits (only) the public initialization nullifier. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/index.html # Module macros Definition of Aztec contract functions, storage, notes and events. ## Modules - [authorization](authorization/index.html) - [dispatch](dispatch/index.html) - [events](events/index.html) - [functions](functions/index.html) - [internals_functions_generation](internals_functions_generation/index.html)The functionality in this module is triggered by the `#[aztec]` macro. It generates new functions, prefixed with `__aztec_nr_internals___`, from the ones marked with `#[external(...)]` and `#[internal(...)]` attributes. The original functions are then modified to be uncallable. This prevents developers from inadvertently calling a function directly, instead of performing a proper contract call. - [notes](notes/index.html) - [storage](storage/index.html) - [utils](utils/index.html) ## Structs - [AztecConfig](struct.AztecConfig.html)Configuration for the [`aztec`](../../noir_aztec/macros/fn.aztec.html) macro. ## Functions - [aztec](fn.aztec.html)Enables aztec-nr features on a `contract`. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/internals_functions_generation/abi_attributes/fn.abi_initializer.html # Function abi_initializer ``` pub comptime fn abi_initializer(_f: [FunctionDefinition](../../../../std/primitive.FunctionDefinition.html)) ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/internals_functions_generation/abi_attributes/fn.abi_only_self.html # Function abi_only_self ``` pub comptime fn abi_only_self(_f: [FunctionDefinition](../../../../std/primitive.FunctionDefinition.html)) ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/internals_functions_generation/abi_attributes/fn.abi_private.html # Function abi_private ``` pub comptime fn abi_private(_f: [FunctionDefinition](../../../../std/primitive.FunctionDefinition.html)) ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/internals_functions_generation/abi_attributes/fn.abi_public.html # Function abi_public ``` pub comptime fn abi_public(_f: [FunctionDefinition](../../../../std/primitive.FunctionDefinition.html)) ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/internals_functions_generation/abi_attributes/fn.abi_utility.html # Function abi_utility ``` pub comptime fn abi_utility(_f: [FunctionDefinition](../../../../std/primitive.FunctionDefinition.html)) ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/internals_functions_generation/abi_attributes/fn.abi_view.html # Function abi_view ``` pub comptime fn abi_view(_f: [FunctionDefinition](../../../../std/primitive.FunctionDefinition.html)) ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/internals_functions_generation/abi_attributes/index.html # Module abi_attributes ABI attributes that are applied by the Aztec.nr macros to the generated functions with the `__aztec_nr_internals___` prefix in the name. The attributes are prefixed with `abi_` as they are used only for ABI purposes and artifact generation. ## Functions - [abi_initializer](fn.abi_initializer.html) - [abi_only_self](fn.abi_only_self.html) - [abi_private](fn.abi_private.html) - [abi_public](fn.abi_public.html) - [abi_utility](fn.abi_utility.html) - [abi_view](fn.abi_view.html) --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/internals_functions_generation/index.html # Module internals_functions_generation The functionality in this module is triggered by the `#[aztec]` macro. It generates new functions, prefixed with `__aztec_nr_internals___`, from the ones marked with `#[external(...)]` and `#[internal(...)]` attributes. The original functions are then modified to be uncallable. This prevents developers from inadvertently calling a function directly, instead of performing a proper contract call. ## Modules - [abi_attributes](abi_attributes/index.html)ABI attributes that are applied by the Aztec.nr macros to the generated functions with the `__aztec_nr_internals___` prefix in the name. The attributes are prefixed with `abi_` as they are used only for ABI purposes and artifact generation. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/notes/fn.custom_note.html # Function custom_note ``` pub comptime fn custom_note(s: [TypeDefinition](../../../std/primitive.TypeDefinition.html)) -> [Quoted](../../../std/primitive.Quoted.html) ``` Generates code for a custom note implementation that requires specialized note hash or nullifier computation. ## Generated Code - NoteTypeProperties: Defines the structure and properties of note fields - NoteType trait implementation: Provides the note type ID ## Requirements The note struct must: - Implement the `Packable` trait - Not exceed `MAX_NOTE_PACKED_LEN` when packed ## Registration Registers the note in the global `NOTES` BoundedVec to enable note processing functionality. ## Use Cases Use this macro when implementing a note that needs custom: - Note hash computation logic - Nullifier computation logic The macro omits generating default NoteHash trait implementation, allowing you to provide your own. ## Example ``` #[custom_note] struct CustomNote { value: Field, metadata: Field } impl NoteHash for CustomNote { // Custom note hash computation... fn compute_note_hash(...) -> Field { ... } // Custom nullifier computation... fn compute_nullifier(...) -> Field { ... } fn compute_nullifier_unconstrained(...) -> Field { ... } } ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/notes/fn.note.html # Function note ``` pub comptime fn note(s: [TypeDefinition](../../../std/primitive.TypeDefinition.html)) -> [Quoted](../../../std/primitive.Quoted.html) ``` Generates the core note functionality for a struct: - NoteTypeProperties: Defines the structure and properties of note fields - NoteType trait implementation: Provides the note type ID - NoteHash trait implementation: Handles note hash and nullifier computation ## Requirements The note struct must: - Implement the `Packable` trait - Not exceed `MAX_NOTE_PACKED_LEN` when packed ## Registration Registers the note in the global `NOTES` BoundedVec to enable note processing functionality. ## Generated Code For detailed documentation on the generated implementations, see: - `generate_note_properties()` - `generate_note_type_impl()` - `generate_note_hash_trait_impl()` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/notes/global.NOTES.html # Global NOTES ``` pub mut comptime global NOTES: [BoundedVec](../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Type](../../../std/primitive.Type.html), 128>; ``` A BoundedVec containing all the note types within this contract. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/notes/index.html # Module notes ## Functions - [custom_note](fn.custom_note.html)Generates code for a custom note implementation that requires specialized note hash or nullifier computation. - [note](fn.note.html)Generates the core note functionality for a struct: ## Globals - [NOTES](global.NOTES.html)A BoundedVec containing all the note types within this contract. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/storage/fn.storage.html # Function storage ``` pub comptime fn storage(s: [TypeDefinition](../../../std/primitive.TypeDefinition.html)) -> [Quoted](../../../std/primitive.Quoted.html) ``` Declares the contract's storage. This function: - marks the contract as having storage, so that `macros::utils::module_has_storage` will return true, - marks the struct `s` as the one describing the storage layout of a contract, - generates an `impl` block for the storage struct with an `init` function (call to `init` is then injected at the beginning of every `#[external(...)]` and `#[internal]` contract function and the storage is then available as `self.storage`), - creates a `StorageLayout` struct that is is exposed via the `abi(storage)` macro in the contract artifact. Only a single struct in the entire contract should have this macro (or `storage_no_init`) applied to it, and the struct has to be called 'Storage'. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/storage/fn.storage_no_init.html # Function storage_no_init ``` pub comptime fn storage_no_init(s: [TypeDefinition](../../../std/primitive.TypeDefinition.html)) ``` Same as `storage`, except the user is in charge of providing an implementation of the `init` constructor function with signature `fn init(context: Context) -> Self`, which allows for manual control of storage slot allocation. Similarly, no `StorageLayout` struct will be created. The contract's storage is accessed via the `storage` variable, which will will automatically be made available in all functions as an instance of the struct this macro was applied to. Only a single struct in the entire contract can have this macro (or storage_no_init) applied to it, and the struct has to be called 'Storage'. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/storage/global.STORAGE_LAYOUT_NAME.html # Global STORAGE_LAYOUT_NAME ``` pub mut comptime global STORAGE_LAYOUT_NAME: CHashMap<[Module](../../../std/primitive.Module.html), [Quoted](../../../std/primitive.Quoted.html)>; ``` Stores a map from a module to the name of the struct that describes its storage layout. This is then used when generating a `storage_layout()` getter on the contract struct. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/storage/index.html # Module storage ## Functions - [storage](fn.storage.html)Declares the contract's storage. - [storage_no_init](fn.storage_no_init.html)Same as `storage`, except the user is in charge of providing an implementation of the `init` constructor function with signature `fn init(context: Context) -> Self`, which allows for manual control of storage slot allocation. Similarly, no `StorageLayout` struct will be created. ## Globals - [STORAGE_LAYOUT_NAME](global.STORAGE_LAYOUT_NAME.html)Stores a map from a module to the name of the struct that describes its storage layout. This is then used when generating a `storage_layout()` getter on the contract struct. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/struct.AztecConfig.html # Struct AztecConfig ``` pub struct AztecConfig { /* private fields */ } ``` Configuration for the [`aztec`](../../noir_aztec/macros/fn.aztec.html) macro. This type lets users override different parts of the default aztec-nr contract behavior, such as message handling. These are advanced features that require careful understanding of the behavior of these systems. ## Examples ``` #[aztec(aztec::macros::AztecConfig::new().custom_message_handler(my_handler))] contract MyContract { ... } ``` ## Implementations ### `impl [AztecConfig](../../noir_aztec/macros/struct.AztecConfig.html)` `pub comptime fn [new](#new)() -> Self` Creates a new `AztecConfig` with default values. Calling `new` is equivalent to invoking the [`aztec`](../../noir_aztec/macros/fn.aztec.html) macro with no parameters. The different methods (e.g. [`AztecConfig::custom_message_handler`](../../noir_aztec/macros/struct.AztecConfig.html#custom_message_handler)) can then be used to change the default behavior. `pub comptime fn [custom_message_handler](#custom_message_handler)( _self: Self, handler: [CustomMessageHandler](../../noir_aztec/messages/discovery/type.CustomMessageHandler.html)<()>, ) -> Self` Sets a handler for custom messages. This enables contracts to process non-standard messages (i.e. any with a message type that is not in [`crate::messages::msg_type`](../../noir_aztec/messages/msg_type/index.html)). `handler` must be a `#[contract_library_method]` function that conforms to the [`crate::messages::discovery::CustomMessageHandler`](../../noir_aztec/messages/discovery/type.CustomMessageHandler.html) type signature. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/utils/fn.derive_serialize_if_not_implemented.html # Function derive_serialize_if_not_implemented ``` pub comptime fn derive_serialize_if_not_implemented(s: [TypeDefinition](../../../std/primitive.TypeDefinition.html)) -> [Quoted](../../../std/primitive.Quoted.html) ``` Generates a quote that implements `Serialize` for a given struct `s`. If the struct already implements `Serialize`, we return an empty quote. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/macros/utils/index.html # Module utils ## Functions - [derive_serialize_if_not_implemented](fn.derive_serialize_if_not_implemented.html)Generates a quote that implements `Serialize` for a given struct `s`. If the struct already implements `Serialize`, we return an empty quote. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/discovery/fn.do_sync_state.html # Function do_sync_state ``` pub unconstrained fn do_sync_state( contract_address: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), compute_note_hash: [ComputeNoteHash](../../../noir_aztec/messages/discovery/type.ComputeNoteHash.html), compute_note_nullifier: [ComputeNoteNullifier](../../../noir_aztec/messages/discovery/type.ComputeNoteNullifier.html), process_custom_message: [Option](../../../std/option/struct.Option.html)<[CustomMessageHandler](../../../noir_aztec/messages/discovery/type.CustomMessageHandler.html)>, offchain_inbox_sync: [Option](../../../std/option/struct.Option.html)>, scope: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), ) ``` Synchronizes the contract's private state with the network. As blocks are mined, it is possible for a contract's private state to change (e.g. with new notes being created), but because these changes are private they will be invisible to most actors. This is the function that processes new transactions in order to discover new notes, events, and other kinds of private state changes. The private state will be synchronized up to the block that will be used for private transactions (i.e. the anchor block. This will typically be close to the tip of the chain. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/discovery/index.html # Module discovery ## Modules - [private_notes](private_notes/index.html) - [process_message](process_message/index.html) ## Structs - [NoteHashAndNullifier](struct.NoteHashAndNullifier.html) ## Type aliases - [ComputeNoteHash](type.ComputeNoteHash.html)A contract's way of computing note hashes. - [ComputeNoteHashAndNullifier](type.ComputeNoteHashAndNullifier.html)Deprecated: use [`ComputeNoteHash`](../../../noir_aztec/messages/discovery/type.ComputeNoteHash.html) and [`ComputeNoteNullifier`](../../../noir_aztec/messages/discovery/type.ComputeNoteNullifier.html) instead. - [ComputeNoteNullifier](type.ComputeNoteNullifier.html)A contract's way of computing note nullifiers. - [CustomMessageHandler](type.CustomMessageHandler.html)A handler for custom messages. ## Functions - [do_sync_state](fn.do_sync_state.html)Synchronizes the contract's private state with the network. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/discovery/private_notes/fn.attempt_note_discovery.html # Function attempt_note_discovery ``` pub unconstrained fn attempt_note_discovery( contract_address: [AztecAddress](../../../../protocol_types/address/aztec_address/struct.AztecAddress.html), tx_hash: [Field](../../../../std/primitive.Field.html), unique_note_hashes_in_tx: [BoundedVec](../../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Field](../../../../std/primitive.Field.html), 64>, first_nullifier_in_tx: [Field](../../../../std/primitive.Field.html), compute_note_hash: [ComputeNoteHash](../../../../noir_aztec/messages/discovery/type.ComputeNoteHash.html), compute_note_nullifier: [ComputeNoteNullifier](../../../../noir_aztec/messages/discovery/type.ComputeNoteNullifier.html), owner: [AztecAddress](../../../../protocol_types/address/aztec_address/struct.AztecAddress.html), storage_slot: [Field](../../../../std/primitive.Field.html), randomness: [Field](../../../../std/primitive.Field.html), note_type_id: [Field](../../../../std/primitive.Field.html), packed_note: [BoundedVec](../../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Field](../../../../std/primitive.Field.html), 8>, ) ``` Attempts discovery of a note given information about its contents and the transaction in which it is suspected the note was created. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/discovery/private_notes/fn.process_private_note_msg.html # Function process_private_note_msg ``` pub unconstrained fn process_private_note_msg( contract_address: [AztecAddress](../../../../protocol_types/address/aztec_address/struct.AztecAddress.html), tx_hash: [Field](../../../../std/primitive.Field.html), unique_note_hashes_in_tx: [BoundedVec](../../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Field](../../../../std/primitive.Field.html), 64>, first_nullifier_in_tx: [Field](../../../../std/primitive.Field.html), compute_note_hash: [ComputeNoteHash](../../../../noir_aztec/messages/discovery/type.ComputeNoteHash.html), compute_note_nullifier: [ComputeNoteNullifier](../../../../noir_aztec/messages/discovery/type.ComputeNoteNullifier.html), msg_metadata: [u64](../../../../std/primitive.u64.html), msg_content: [BoundedVec](../../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Field](../../../../std/primitive.Field.html), 11>, ) ``` Processes a private note message, attempting to discover and enqueue any notes it contains. For each note recovered from the message whose computed note hash matches a unique note hash in the transaction, a [`NoteValidationRequest`](../../../../noir_aztec/messages/processing/struct.NoteValidationRequest.html) is pushed onto the ephemeral array at `NOTE_VALIDATION_REQUESTS_ARRAY_BASE_SLOT` via [`enqueue_note_for_validation`](../../../../noir_aztec/messages/processing/fn.enqueue_note_for_validation.html). PXE later drains this array during `validate_and_store_enqueued_notes_and_events`, after which the notes are retrievable via `get_notes`. Messages that fail to decode, or whose computed note hash matches nothing in the transaction, are discarded (with a debug or warning log respectively) and produce no validation requests. Decode failures are not treated as errors since messages may originate from malicious senders and we don't want them to be able to brick message processing. ## Use Cases This function is invoked automatically by aztec-nr when handling messages with the built-in private note type id, so contracts do not normally need to call it directly. It is exposed for use by custom message handlers (see [`CustomMessageHandler`](../../../../noir_aztec/messages/discovery/type.CustomMessageHandler.html)) that might want to use the standard note message processing pipeline while extending it with custom logic. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/discovery/private_notes/index.html # Module private_notes ## Functions - [process_private_note_msg](fn.process_private_note_msg.html)Processes a private note message, attempting to discover and enqueue any notes it contains. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/discovery/process_message/fn.process_message_ciphertext.html # Function process_message_ciphertext ``` pub unconstrained fn process_message_ciphertext( contract_address: [AztecAddress](../../../../protocol_types/address/aztec_address/struct.AztecAddress.html), compute_note_hash: [ComputeNoteHash](../../../../noir_aztec/messages/discovery/type.ComputeNoteHash.html), compute_note_nullifier: [ComputeNoteNullifier](../../../../noir_aztec/messages/discovery/type.ComputeNoteNullifier.html), process_custom_message: [Option](../../../../std/option/struct.Option.html)<[CustomMessageHandler](../../../../noir_aztec/messages/discovery/type.CustomMessageHandler.html)>, message_ciphertext: [BoundedVec](../../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Field](../../../../std/primitive.Field.html), 15>, message_context: [MessageContext](../../../../noir_aztec/messages/processing/struct.MessageContext.html), recipient: [AztecAddress](../../../../protocol_types/address/aztec_address/struct.AztecAddress.html), ) ``` Processes a message that can contain notes, partial notes, or events. Notes result in nonce discovery being performed prior to delivery, which requires knowledge of the transaction hash in which the notes would've been created (typically the same transaction in which the log was emitted), along with the list of unique note hashes in said transaction and the `compute_note_hash` and `compute_note_nullifier` functions. Once discovered, the notes are enqueued for validation. Partial notes result in a pending partial note entry being stored in a PXE capsule, which will later be retrieved to search for the note's completion public log. Events are processed by computing an event commitment from the serialized event data and its randomness field, then enqueueing the event data and commitment for validation. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/discovery/process_message/index.html # Module process_message ## Functions - [process_message_ciphertext](fn.process_message_ciphertext.html)Processes a message that can contain notes, partial notes, or events. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/discovery/struct.NoteHashAndNullifier.html # Struct NoteHashAndNullifier ``` pub struct NoteHashAndNullifier { pub note_hash: [Field](../../../std/primitive.Field.html), pub inner_nullifier: [Option](../../../std/option/struct.Option.html)<[Field](../../../std/primitive.Field.html)>, } ``` ## Fields `note_hash: [Field](../../../std/primitive.Field.html)` The result of [`crate::note::note_interface::NoteHash::compute_note_hash`](../../../noir_aztec/note/note_interface/trait.NoteHash.html#compute_note_hash). `inner_nullifier: [Option](../../../std/option/struct.Option.html)<[Field](../../../std/primitive.Field.html)>` The result of [`crate::note::note_interface::NoteHash::compute_nullifier_unconstrained`](../../../noir_aztec/note/note_interface/trait.NoteHash.html#compute_nullifier_unconstrained). This value is unconstrained, as all of message discovery is unconstrained. It is `None` if the nullifier cannot be computed (e.g. because the nullifier hiding key is not available). --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/discovery/type.ComputeNoteHash.html # Type alias ComputeNoteHash ``` pub type ComputeNoteHash = unconstrained fn([BoundedVec](../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Field](../../../std/primitive.Field.html), 8>, [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), [Field](../../../std/primitive.Field.html), [Field](../../../std/primitive.Field.html), [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), [Field](../../../std/primitive.Field.html)) -> [Option](../../../std/option/struct.Option.html)<[Field](../../../std/primitive.Field.html)>; ``` A contract's way of computing note hashes. Each contract in the network is free to compute their note's hash as they see fit - the hash function itself is not enshrined or standardized. Some aztec-nr functions however do need to know the details of this computation (e.g. when finding new notes), which is what this type represents. This function takes a note's packed content, storage slot, note type ID, address of the emitting contract and randomness, and attempts to compute its inner note hash (not siloed by address nor uniqued by nonce). ## Transient Notes This function is meant to always be used on settled notes, i.e. those that have been inserted into the trees and for which the nonce is known. It is never invoked in the context of a transient note, as those are not involved in message processing. ## Automatic Implementation The [`[#aztec]`](../../../noir_aztec/macros/fn.aztec.html) macro automatically creates a correct implementation of this function for each contract by inspecting all note types in use and the storage layout. This injected function is a `#[contract_library_method]` called `_compute_note_hash`, and it looks something like this: ``` |packed_note, owner, storage_slot, note_type_id, _contract_address, randomness| { if note_type_id == MyNoteType::get_id() { if packed_note.len() != MY_NOTE_TYPE_SERIALIZATION_LENGTH { Option::none() } else { let note = MyNoteType::unpack(aztec::utils::array::subarray(packed_note.storage(), 0)); Option::some(note.compute_note_hash(owner, storage_slot, randomness)) } } else if note_type_id == MyOtherNoteType::get_id() { ... // Similar to above but calling MyOtherNoteType::unpack } else { Option::none() // Unknown note type ID }; } ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/discovery/type.ComputeNoteHashAndNullifier.html # Type alias ComputeNoteHashAndNullifier ``` pub type ComputeNoteHashAndNullifier = unconstrained fn[Env]([BoundedVec](../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Field](../../../std/primitive.Field.html), 8>, [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), [Field](../../../std/primitive.Field.html), [Field](../../../std/primitive.Field.html), [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), [Field](../../../std/primitive.Field.html), [Field](../../../std/primitive.Field.html)) -> [Option](../../../std/option/struct.Option.html)<[NoteHashAndNullifier](../../../noir_aztec/messages/discovery/struct.NoteHashAndNullifier.html)>; ``` Deprecated: use [`ComputeNoteHash`](../../../noir_aztec/messages/discovery/type.ComputeNoteHash.html) and [`ComputeNoteNullifier`](../../../noir_aztec/messages/discovery/type.ComputeNoteNullifier.html) instead. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/discovery/type.ComputeNoteNullifier.html # Type alias ComputeNoteNullifier ``` pub type ComputeNoteNullifier = unconstrained fn([Field](../../../std/primitive.Field.html), [BoundedVec](../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Field](../../../std/primitive.Field.html), 8>, [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), [Field](../../../std/primitive.Field.html), [Field](../../../std/primitive.Field.html), [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), [Field](../../../std/primitive.Field.html)) -> [Option](../../../std/option/struct.Option.html)<[Field](../../../std/primitive.Field.html)>; ``` A contract's way of computing note nullifiers. Like [`ComputeNoteHash`](../../../noir_aztec/messages/discovery/type.ComputeNoteHash.html), each contract is free to derive nullifiers as they see fit. This function takes the unique note hash (used as the note hash for nullification for settled notes), plus the note's packed content and metadata, and attempts to compute the inner nullifier (not siloed by address). ## Automatic Implementation The [`[#aztec]`](../../../noir_aztec/macros/fn.aztec.html) macro automatically creates a correct implementation of this function for each contract called `_compute_note_nullifier`. It dispatches on `note_type_id` similarly to [`ComputeNoteHash`](../../../noir_aztec/messages/discovery/type.ComputeNoteHash.html), then calls the note's [`compute_nullifier_unconstrained`](../../../noir_aztec/note/note_interface/trait.NoteHash.html#compute_nullifier_unconstrained) method. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/discovery/type.CustomMessageHandler.html # Type alias CustomMessageHandler ``` pub type CustomMessageHandler = unconstrained fn[Env]([AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), [u64](../../../std/primitive.u64.html), [u64](../../../std/primitive.u64.html), [BoundedVec](../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Field](../../../std/primitive.Field.html), 11>, [MessageContext](../../../noir_aztec/messages/processing/struct.MessageContext.html), [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html)); ``` A handler for custom messages. Contracts that emit custom messages (i.e. any with a message type that is not in [`crate::messages::msg_type`](../../../noir_aztec/messages/msg_type/index.html)) need to use [`crate::macros::AztecConfig::custom_message_handler`](../../../noir_aztec/macros/struct.AztecConfig.html#custom_message_handler) with a function of this type in order to process them. They will otherwise be silently ignored. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/encoding/fn.decode_message.html # Function decode_message ``` pub unconstrained fn decode_message( message: [BoundedVec](../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Field](../../../std/primitive.Field.html), 12>, ) -> [Option](../../../std/option/struct.Option.html)<([u64](../../../std/primitive.u64.html), [u64](../../../std/primitive.u64.html), [BoundedVec](../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Field](../../../std/primitive.Field.html), 11>)> ``` Decodes a standard aztec-nr message, i.e. one created via `encode_message`, returning the original encoded values. Returns `None` if the message is empty or has invalid (>128 bit) expanded metadata. Note that `encode_message` returns a fixed size array while this function takes a `BoundedVec`: this is because prior to decoding the message type is unknown, and consequentially not known at compile time. If working with fixed-size messages, consider using `BoundedVec::from_array` to convert them. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/encoding/fn.encode_message.html # Function encode_message ``` pub fn encode_message( msg_type: [u64](../../../std/primitive.u64.html), msg_metadata: [u64](../../../std/primitive.u64.html), msg_content: [[Field](../../../std/primitive.Field.html); N], ) -> [[Field](../../../std/primitive.Field.html); N + 1] ``` Encodes a message following aztec-nr's standard message encoding. This message can later be decoded with `decode_message` to retrieve the original values. - The `msg_type` is an identifier that groups types of messages that are all processed the same way, e.g. private notes or events. Possible values are defined in `aztec::messages::msg_type`. - The `msg_metadata` and `msg_content` are the values stored in the message, whose meaning depends on the `msg_type`. The only special thing about `msg_metadata` that separates it from `msg_content` is that it is a u64 instead of a full Field (due to details of how messages are encoded), allowing applications that can fit values into this smaller variable to achieve higher data efficiency. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/encoding/global.EPH_PK_X_SIZE_IN_FIELDS.html # Global EPH_PK_X_SIZE_IN_FIELDS ``` pub global EPH_PK_X_SIZE_IN_FIELDS: [u32](../../../std/primitive.u32.html); ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/encoding/global.MAX_MESSAGE_CONTENT_LEN.html # Global MAX_MESSAGE_CONTENT_LEN ``` pub global MAX_MESSAGE_CONTENT_LEN: [u32](../../../std/primitive.u32.html); ``` The maximum length of a message's content, i.e. not including the expanded message metadata. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/encoding/global.MESSAGE_CIPHERTEXT_LEN.html # Global MESSAGE_CIPHERTEXT_LEN ``` pub global MESSAGE_CIPHERTEXT_LEN: [u32](../../../std/primitive.u32.html); ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/encoding/global.MESSAGE_EXPANDED_METADATA_LEN.html # Global MESSAGE_EXPANDED_METADATA_LEN ``` pub global MESSAGE_EXPANDED_METADATA_LEN: [u32](../../../std/primitive.u32.html); ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/encoding/global.MESSAGE_PLAINTEXT_LEN.html # Global MESSAGE_PLAINTEXT_LEN ``` pub global MESSAGE_PLAINTEXT_LEN: [u32](../../../std/primitive.u32.html); ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/encoding/index.html # Module encoding ## Functions - [decode_message](fn.decode_message.html)Decodes a standard aztec-nr message, i.e. one created via `encode_message`, returning the original encoded values. - [encode_message](fn.encode_message.html)Encodes a message following aztec-nr's standard message encoding. This message can later be decoded with `decode_message` to retrieve the original values. ## Globals - [EPH_PK_X_SIZE_IN_FIELDS](global.EPH_PK_X_SIZE_IN_FIELDS.html) - [MAX_MESSAGE_CONTENT_LEN](global.MAX_MESSAGE_CONTENT_LEN.html)The maximum length of a message's content, i.e. not including the expanded message metadata. - [MESSAGE_CIPHERTEXT_LEN](global.MESSAGE_CIPHERTEXT_LEN.html) - [MESSAGE_EXPANDED_METADATA_LEN](global.MESSAGE_EXPANDED_METADATA_LEN.html) - [MESSAGE_PLAINTEXT_LEN](global.MESSAGE_PLAINTEXT_LEN.html) --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/encryption/aes128/fn.derive_aes_symmetric_key_and_iv_from_shared_secret.html # Function derive_aes_symmetric_key_and_iv_from_shared_secret ``` pub fn derive_aes_symmetric_key_and_iv_from_shared_secret( s_app: [Field](../../../../std/primitive.Field.html), ) -> [([[u8](../../../../std/primitive.u8.html); 16], [[u8](../../../../std/primitive.u8.html); 16]); N] ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/encryption/aes128/index.html # Module aes128 ## Structs - [AES128](struct.AES128.html) ## Functions - [derive_aes_symmetric_key_and_iv_from_shared_secret](fn.derive_aes_symmetric_key_and_iv_from_shared_secret.html) --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/encryption/aes128/struct.AES128.html # Struct AES128 ``` pub struct AES128 {} ``` ## Trait implementations ### `impl [MessageEncryption](../../../../noir_aztec/messages/encryption/message_encryption/trait.MessageEncryption.html) for [AES128](../../../../noir_aztec/messages/encryption/aes128/struct.AES128.html)` `pub fn encrypt( plaintext: [[Field](../../../../std/primitive.Field.html); PlaintextLen], recipient: [AztecAddress](../../../../protocol_types/address/aztec_address/struct.AztecAddress.html), contract_address: [AztecAddress](../../../../protocol_types/address/aztec_address/struct.AztecAddress.html), ) -> [[Field](../../../../std/primitive.Field.html); 15]` AES128-CBC encryption for Aztec protocol messages. #### Overview The plaintext is an array of up to `MESSAGE_PLAINTEXT_LEN` (12) fields. The output is always exactly `MESSAGE_CIPHERTEXT_LEN` (15) fields, regardless of plaintext size. All output fields except the ephemeral public key are uniformly random `Field` values to any observer without knowledge of the shared secret, making all encrypted messages indistinguishable by size or content. #### PKCS#7 Padding AES operates on 16-byte blocks, so the plaintext must be padded to a multiple of 16. PKCS#7 padding always adds at least 1 byte (so the receiver can always detect and strip it), which means: - 1 B plaintext -> 15 B padding -> 16 B total - 15 B plaintext -> 1 B padding -> 16 B total - 16 B plaintext -> 16 B padding -> 32 B total (full extra block) In general: if the plaintext is already a multiple of 16, a full 16-byte padding block is appended. #### Encryption Steps 1. Body encryption. The plaintext fields are serialized to bytes (32 bytes per field) and AES-128-CBC encrypted. Since 32 is a multiple of 16, PKCS#7 always adds a full 16-byte padding block (see above): ``` +---------------------------------------------+ | body ct | | PlaintextLen*32 + 16 B | +-------------------------------+--------------+ | encrypted plaintext fields | PKCS#7 (16B) | | (serialized at 32 B each) | | +-------------------------------+--------------+ ``` 2. Header encryption. The byte length of `body_ct` is stored as a 2-byte big-endian integer. This 2-byte header plaintext is then AES-encrypted; PKCS#7 pads the remaining 14 bytes to fill one 16-byte AES block, producing a 16-byte header ciphertext: ``` +---------------------------+ | header ct | | 16 B | +--------+------------------+ | body ct| PKCS#7 (14B) | | length | | | (2 B) | | +--------+------------------+ ``` #### Wire Format Messages are transmitted as fields, not bytes. A field is ~254 bits and can safely store 31 whole bytes, so we need to pack our byte data into 31-byte chunks. This packing drives the wire format. Step 1 -- Assemble bytes. The ciphertexts are laid out in a byte array, padded with zero bytes to a multiple of 31 so it divides evenly into fields: ``` +------------+-------------------------+---------+ | header ct | body ct | byte pad| | 16 B | PlaintextLen*32 + 16 B | (zeros) | +------------+-------------------------+---------+ |<-------- padded to a multiple of 31 B -------->| ``` Step 2 -- Pack and mask. The byte array is split into 31-byte chunks, each stored in one field. A Poseidon2-derived mask (see `derive_shared_secret_field_mask`) is added to each so that the resulting fields appear as uniformly random `Field` values to any observer without knowledge of the shared secret, hiding the fact that the underlying ciphertext consists of 128-bit AES blocks. Step 3 -- Assemble ciphertext. The ephemeral public key x-coordinate is prepended and random field padding is appended to fill to 15 fields: ``` +----------+-------------------------+-------------------+ | eph_pk.x | masked message fields | random field pad | | | (packed 31 B per field) | (fills to 15) | +----------+-------------------------+-------------------+ |<---------- MESSAGE_CIPHERTEXT_LEN = 15 fields -------->| ``` #### Key Derivation The raw ECDH shared secret point is first app-siloed into a scalar `s_app` by hashing with the contract address (see [`compute_app_siloed_shared_secret`]()). Two (key, IV) pairs are then derived from `s_app` via indexed Poseidon2 hashing: one pair for the body ciphertext and one for the header ciphertext. `pub unconstrained fn decrypt( ciphertext: [BoundedVec](../../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Field](../../../../std/primitive.Field.html), 15>, recipient: [AztecAddress](../../../../protocol_types/address/aztec_address/struct.AztecAddress.html), contract_address: [AztecAddress](../../../../protocol_types/address/aztec_address/struct.AztecAddress.html), ) -> [Option](../../../../std/option/struct.Option.html)<[BoundedVec](../../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Field](../../../../std/primitive.Field.html), 12>>` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/encryption/index.html # Module encryption ## Modules - [aes128](aes128/index.html) - [message_encryption](message_encryption/index.html) - [poseidon2](poseidon2/index.html) --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/encryption/message_encryption/index.html # Module message_encryption ## Traits - [MessageEncryption](trait.MessageEncryption.html)Trait for encrypting and decrypting messages in the Aztec protocol. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/encryption/message_encryption/trait.MessageEncryption.html # Trait MessageEncryption ``` pub trait MessageEncryption { // Required methods pub fn [encrypt](#encrypt)( plaintext: [[Field](../../../../std/primitive.Field.html); PlaintextLen], recipient: [AztecAddress](../../../../protocol_types/address/aztec_address/struct.AztecAddress.html), contract_address: [AztecAddress](../../../../protocol_types/address/aztec_address/struct.AztecAddress.html), ) -> [[Field](../../../../std/primitive.Field.html); 15]; pub unconstrained fn [decrypt](#decrypt)( ciphertext: [BoundedVec](../../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Field](../../../../std/primitive.Field.html), 15>, recipient: [AztecAddress](../../../../protocol_types/address/aztec_address/struct.AztecAddress.html), contract_address: [AztecAddress](../../../../protocol_types/address/aztec_address/struct.AztecAddress.html), ) -> [Option](../../../../std/option/struct.Option.html)<[BoundedVec](../../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Field](../../../../std/primitive.Field.html), 12>>; } ``` Trait for encrypting and decrypting messages in the Aztec protocol. This trait defines the interface for encrypting plaintext data into messages that are delivered either onchain (via logs) or offchain, as well as decrypting those messages back into their original plaintext. ## Type Parameters - `PLAINTEXT_LEN`: Length of the plaintext array in fields - `MESSAGE_CIPHERTEXT_LEN`: Fixed length of encrypted message (defined globally) - `MESSAGE_PLAINTEXT_LEN`: Maximum size of decrypted plaintext (defined globally) ## Note on privacy sets To preserve privacy, [`MessageEncryption::encrypt`](../../../../noir_aztec/messages/encryption/message_encryption/trait.MessageEncryption.html#encrypt) returns a fixed-length array ensuring all log types are indistinguishable onchain. Implementations of this trait must handle padding the encrypted log to match this standardized length. ## Required methods `pub fn [encrypt](#encrypt)( plaintext: [[Field](../../../../std/primitive.Field.html); PlaintextLen], recipient: [AztecAddress](../../../../protocol_types/address/aztec_address/struct.AztecAddress.html), contract_address: [AztecAddress](../../../../protocol_types/address/aztec_address/struct.AztecAddress.html), ) -> [[Field](../../../../std/primitive.Field.html); 15]` Encrypts a plaintext message to `recipient`. The returned message ciphertext can be passed to [`MessageEncryption::decrypt`](../../../../noir_aztec/messages/encryption/message_encryption/trait.MessageEncryption.html#decrypt) in order to obtain the original `plaintext`. ### Privacy Knowledge of the returned ciphertext provides no information to third parties - `recipient`'s encryption keys (specifically their ivsk and pre-address) are required in order to decrypt. Additionally, this function adds random padding in order to always produce equal length message ciphertexts regardless of the input, hiding its length. These properties make it secure to distribute the ciphertext publicly, e.g. on blockchain logs (assuming the encryption function is itself secure). `pub unconstrained fn [decrypt](#decrypt)( ciphertext: [BoundedVec](../../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Field](../../../../std/primitive.Field.html), 15>, recipient: [AztecAddress](../../../../protocol_types/address/aztec_address/struct.AztecAddress.html), contract_address: [AztecAddress](../../../../protocol_types/address/aztec_address/struct.AztecAddress.html), ) -> [Option](../../../../std/option/struct.Option.html)<[BoundedVec](../../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Field](../../../../std/primitive.Field.html), 12>>` Decrypts a message ciphertext into its original plaintext. Typically invoked with ciphertext obtained from calling [`MessageEncryption::encrypt`](../../../../noir_aztec/messages/encryption/message_encryption/trait.MessageEncryption.html#encrypt) with `recipient`. Note that this function is unconstrained: decryption typically happens when processing messages in utility functions. Not all ciphertexts are valid - among other things, the ephemeral public key included in it may not correspond to a point on the curve. In all such cases, [`Option::none`](../../../../std/option/struct.Option.html#none) is returned instead. ## Implementors ### `impl [MessageEncryption](../../../../noir_aztec/messages/encryption/message_encryption/trait.MessageEncryption.html) for [AES128](../../../../noir_aztec/messages/encryption/aes128/struct.AES128.html)` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/encryption/poseidon2/fn.poseidon2_decrypt.html # Function poseidon2_decrypt ``` pub fn poseidon2_decrypt( ciphertext: [[Field](../../../../std/primitive.Field.html); L + 2 / 3 * 3 + 1], shared_secret: [Point](../../../../protocol_types/point/struct.Point.html), encryption_nonce: [Field](../../../../std/primitive.Field.html), ) -> [Option](../../../../std/option/struct.Option.html)<[[Field](../../../../std/primitive.Field.html); L]> ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/encryption/poseidon2/fn.poseidon2_encrypt.html # Function poseidon2_encrypt ``` pub fn poseidon2_encrypt( msg: [[Field](../../../../std/primitive.Field.html); L], shared_secret: [Point](../../../../protocol_types/point/struct.Point.html), encryption_nonce: [Field](../../../../std/primitive.Field.html), ) -> [[Field](../../../../std/primitive.Field.html); L + 2 / 3 * 3 + 1] ``` Poseidon2 Encryption. ~160 constraints to encrypt 8 fields. Use this hash if you favour proving speed over long-term privacy for your users. WARNING: Poseidon2 as an encryption scheme isn't considered as secure as more battle-tested encryption schemes, e.g. AES128. This is because: - it's relatively new; - it isn't used much in the wild, so there's less incentive for hackers or bounty hunters to try to break it; - it doesn't provide post-quantum privacy. If you want to protect your users' privacy decades into the future, it might be prudent to choose a more 'traditional' encryption scheme. If your app is "lower stakes", and your users will only care about their privacy in the near future or immediate future, then this encryption scheme might be for you! See the paper: [https://drive.google.com/file/d/1EVrP3DzoGbmzkRmYnyEDcIQcXVU7GlOd/view](https://drive.google.com/file/d/1EVrP3DzoGbmzkRmYnyEDcIQcXVU7GlOd/view) Note: The return length is: L padded to the next multiple of 3, plus 1 for a message auth code of s[1]. @param encryption_nonce is only needed if your use case needs to protect against replay attacks. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/encryption/poseidon2/index.html # Module poseidon2 ## Functions - [poseidon2_decrypt](fn.poseidon2_decrypt.html) - [poseidon2_encrypt](fn.poseidon2_encrypt.html)Poseidon2 Encryption. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/index.html # Module messages Message encoding, encryption, delivery and processing. ## Modules - [discovery](discovery/index.html) - [encoding](encoding/index.html) - [encryption](encryption/index.html) - [logs](logs/index.html) - [message_delivery](message_delivery/index.html) - [msg_type](msg_type/index.html) - [offchain_messages](offchain_messages/index.html) - [processing](processing/index.html) --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/logs/event/fn.encode_private_event_message.html # Function encode_private_event_message ``` pub fn encode_private_event_message( event: Event, randomness: [Field](../../../../std/primitive.Field.html), ) -> [[Field](../../../../std/primitive.Field.html); ::N + 2] where Event: [EventInterface](../../../../noir_aztec/event/event_interface/trait.EventInterface.html), Event: [Serialize](../../../../serde/serialization/trait.Serialize.html) ``` Creates the plaintext for a private event message (i.e. one of type [`PRIVATE_EVENT_MSG_TYPE_ID`](../../../../noir_aztec/messages/msg_type/global.PRIVATE_EVENT_MSG_TYPE_ID.html)). This plaintext is meant to be decoded via [`decode_private_event_message`]. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/logs/event/global.MAX_EVENT_SERIALIZED_LEN.html # Global MAX_EVENT_SERIALIZED_LEN ``` pub global MAX_EVENT_SERIALIZED_LEN: [u32](../../../../std/primitive.u32.html); ``` The maximum length of the packed representation of an event's contents. This is limited by private log size, encryption overhead and extra fields in the message (e.g. message type id, randomness, etc.). --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/logs/event/index.html # Module event ## Functions - [encode_private_event_message](fn.encode_private_event_message.html)Creates the plaintext for a private event message (i.e. one of type [`PRIVATE_EVENT_MSG_TYPE_ID`](../../../../noir_aztec/messages/msg_type/global.PRIVATE_EVENT_MSG_TYPE_ID.html)). ## Globals - [MAX_EVENT_SERIALIZED_LEN](global.MAX_EVENT_SERIALIZED_LEN.html)The maximum length of the packed representation of an event's contents. This is limited by private log size, encryption overhead and extra fields in the message (e.g. message type id, randomness, etc.). --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/logs/index.html # Module logs ## Modules - [event](event/index.html) - [note](note/index.html) - [partial_note](partial_note/index.html) --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/logs/note/fn.encode_private_note_message.html # Function encode_private_note_message ``` pub fn encode_private_note_message( note: Note, owner: [AztecAddress](../../../../protocol_types/address/aztec_address/struct.AztecAddress.html), storage_slot: [Field](../../../../std/primitive.Field.html), randomness: [Field](../../../../std/primitive.Field.html), ) -> [[Field](../../../../std/primitive.Field.html); ::N + 4] where Note: [NoteType](../../../../noir_aztec/note/note_interface/trait.NoteType.html), Note: [Packable](../../../../protocol_types/traits/trait.Packable.html) ``` Creates the plaintext for a private note message (i.e. one of type [`PRIVATE_NOTE_MSG_TYPE_ID`](../../../../noir_aztec/messages/msg_type/global.PRIVATE_NOTE_MSG_TYPE_ID.html)). This plaintext is meant to be decoded via [`decode_private_note_message`]. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/logs/note/fn.encode_private_note_message_with_msg_type_id.html # Function encode_private_note_message_with_msg_type_id ``` pub fn encode_private_note_message_with_msg_type_id( msg_type_id: [u64](../../../../std/primitive.u64.html), note: Note, owner: [AztecAddress](../../../../protocol_types/address/aztec_address/struct.AztecAddress.html), storage_slot: [Field](../../../../std/primitive.Field.html), randomness: [Field](../../../../std/primitive.Field.html), ) -> [[Field](../../../../std/primitive.Field.html); ::N + 4] where Note: [NoteType](../../../../noir_aztec/note/note_interface/trait.NoteType.html), Note: [Packable](../../../../protocol_types/traits/trait.Packable.html) ``` Creates the plaintext for a private note message with the given `msg_type_id`. Shared encoder used by [`encode_private_note_message`](../../../../noir_aztec/messages/logs/note/fn.encode_private_note_message.html) and by custom message handlers that use the same format as standard private note message but perform additional validation logic. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/logs/note/global.MAX_NOTE_PACKED_LEN.html # Global MAX_NOTE_PACKED_LEN ``` pub global MAX_NOTE_PACKED_LEN: [u32](../../../../std/primitive.u32.html); ``` The maximum length of the packed representation of a note's contents. This is limited by private log size, encryption overhead and extra fields in the message (e.g. message type id, storage slot, randomness, etc.). --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/logs/note/index.html # Module note ## Functions - [encode_private_note_message](fn.encode_private_note_message.html)Creates the plaintext for a private note message (i.e. one of type [`PRIVATE_NOTE_MSG_TYPE_ID`](../../../../noir_aztec/messages/msg_type/global.PRIVATE_NOTE_MSG_TYPE_ID.html)). - [encode_private_note_message_with_msg_type_id](fn.encode_private_note_message_with_msg_type_id.html)Creates the plaintext for a private note message with the given `msg_type_id`. ## Globals - [MAX_NOTE_PACKED_LEN](global.MAX_NOTE_PACKED_LEN.html)The maximum length of the packed representation of a note's contents. This is limited by private log size, encryption overhead and extra fields in the message (e.g. message type id, storage slot, randomness, etc.). --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/logs/partial_note/fn.encode_partial_note_private_message.html # Function encode_partial_note_private_message ``` pub fn encode_partial_note_private_message( partial_note_private_content: PartialNotePrivateContent, owner: [AztecAddress](../../../../protocol_types/address/aztec_address/struct.AztecAddress.html), randomness: [Field](../../../../std/primitive.Field.html), note_completion_log_tag: [Field](../../../../std/primitive.Field.html), ) -> [[Field](../../../../std/primitive.Field.html); ::N + 4] where PartialNotePrivateContent: [NoteType](../../../../noir_aztec/note/note_interface/trait.NoteType.html), PartialNotePrivateContent: [Packable](../../../../protocol_types/traits/trait.Packable.html) ``` Creates the plaintext for a partial note private message (i.e. one of type [`PARTIAL_NOTE_PRIVATE_MSG_TYPE_ID`](../../../../noir_aztec/messages/msg_type/global.PARTIAL_NOTE_PRIVATE_MSG_TYPE_ID.html)). This plaintext is meant to be decoded via [`decode_partial_note_private_message`]. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/logs/partial_note/global.MAX_PARTIAL_NOTE_PRIVATE_PACKED_LEN.html # Global MAX_PARTIAL_NOTE_PRIVATE_PACKED_LEN ``` pub global MAX_PARTIAL_NOTE_PRIVATE_PACKED_LEN: [u32](../../../../std/primitive.u32.html); ``` Partial notes have a maximum packed length of their private fields bound by extra content in their private message (e.g. the storage slot, note completion log tag, etc.). --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/logs/partial_note/index.html # Module partial_note ## Functions - [encode_partial_note_private_message](fn.encode_partial_note_private_message.html)Creates the plaintext for a partial note private message (i.e. one of type [`PARTIAL_NOTE_PRIVATE_MSG_TYPE_ID`](../../../../noir_aztec/messages/msg_type/global.PARTIAL_NOTE_PRIVATE_MSG_TYPE_ID.html)). ## Globals - [MAX_PARTIAL_NOTE_PRIVATE_PACKED_LEN](global.MAX_PARTIAL_NOTE_PRIVATE_PACKED_LEN.html)Partial notes have a maximum packed length of their private fields bound by extra content in their private message (e.g. the storage slot, note completion log tag, etc.). --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/message_delivery/fn.do_private_message_delivery.html # Function do_private_message_delivery ``` pub fn do_private_message_delivery( context: &mut [PrivateContext](../../../noir_aztec/context/struct.PrivateContext.html), encode_into_message_plaintext: fn[Env]() -> [[Field](../../../std/primitive.Field.html); MESSAGE_PLAINTEXT_LEN], maybe_note_hash_counter: [Option](../../../std/option/struct.Option.html)<[u32](../../../std/primitive.u32.html)>, recipient: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), delivery_mode: [u8](../../../std/primitive.u8.html), ) ``` Performs private delivery of a message to `recipient` according to `delivery_mode`. The message is encoded into plaintext and then encrypted for `recipient`. This function takes a function that returns the plaintext instead of taking the plaintext directly in order to not waste constraints encoding the message in scenarios where the plaintext will be encrypted with unconstrained encryption. `maybe_note_hash_counter` is only relevant for on-chain delivery modes (i.e. via protocol logs): if a newly created note hash's side effect counter is passed, then the log will be squashed alongside the note should its nullifier be emitted in the current transaction. This is typically only used for note messages: since the note will not actually be created, there is no point in delivering the message. `delivery_mode` must be one of [`MessageDeliveryEnum`](../../../noir_aztec/messages/message_delivery/struct.MessageDeliveryEnum.html). ## Privacy The emitted log always has the same length regardless of `MESSAGE_PLAINTEXT_LEN`, because all message ciphertexts also have the same length. This prevents accidental privacy leakage via the log length. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/message_delivery/global.MessageDelivery.html # Global MessageDelivery ``` pub global MessageDelivery: [MessageDeliveryEnum](../../../noir_aztec/messages/message_delivery/struct.MessageDeliveryEnum.html); ``` Specifies how to deliver a message to a recipient. All messages are delivered encrypted to their recipient's public address key, so no other account will be able to read their contents. This enum instead configures which guarantees exist regarding delivery. There are two aspects to delivery guarantees: - the medium on which the message is sent (off-chain or on-chain) - whether the contract constrains the message to be constructed correctly For scenarios where the sender is incentivized to deliver the message correctly, use [`MessageDeliveryEnum::OFFCHAIN`](../../../noir_aztec/messages/message_delivery/struct.MessageDeliveryEnum.html#structfield.OFFCHAIN) (the cheapest delivery option, but requiring that sender and recipient can communicate off-chain) or [`MessageDeliveryEnum::ONCHAIN_UNCONSTRAINED`](../../../noir_aztec/messages/message_delivery/struct.MessageDeliveryEnum.html#structfield.ONCHAIN_UNCONSTRAINED). If the sender cannot be trusted to send the message to the recipient, use [`MessageDeliveryEnum::ONCHAIN_CONSTRAINED`](../../../noir_aztec/messages/message_delivery/struct.MessageDeliveryEnum.html#structfield.ONCHAIN_CONSTRAINED). --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/message_delivery/index.html # Module message_delivery ## Structs - [MessageDeliveryEnum](struct.MessageDeliveryEnum.html)Placeholder struct until Noir adds `enum` support. ## Functions - [do_private_message_delivery](fn.do_private_message_delivery.html)Performs private delivery of a message to `recipient` according to `delivery_mode`. ## Globals - [MessageDelivery](global.MessageDelivery.html)Specifies how to deliver a message to a recipient. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/message_delivery/struct.MessageDeliveryEnum.html # Struct MessageDeliveryEnum ``` pub struct MessageDeliveryEnum { pub OFFCHAIN: [u8](../../../std/primitive.u8.html), pub ONCHAIN_UNCONSTRAINED: [u8](../../../std/primitive.u8.html), pub ONCHAIN_CONSTRAINED: [u8](../../../std/primitive.u8.html), } ``` Placeholder struct until Noir adds `enum` support. See [`MessageDelivery`](../../../noir_aztec/messages/message_delivery/global.MessageDelivery.html) instead. ## Fields `OFFCHAIN: [u8](../../../std/primitive.u8.html)` Delivers the message fully off-chain, with no guarantees whatsoever. #### Use Cases This delivery method is suitable when the sender is required to send the message to the recipient because of some external reason, and where the sender is able to directly contact the recipient off-chain. In these cases, it might be unnecessary to force the sender to spend proving time guaranteeing message correctness, or to pay transaction fees in order to use the chain as a medium. For example, if performing a payment in exchange for some good or service, the recipient will only accept the payment once they receive note and event messages, allowing them to observe the balance increase. The sender has no reason not to deliver the message correctly to the recipient, and in all likelihood has a way to send it to them. Similarly, in games and other applications that might rely on some server processing state, players might be required to update the server with their current state. Finally, any messages for which the recipient is a local account (e.g.: the message for the change note in a token transfer) work well with this delivery option, since the sender would only be harming themselves by not delivering correctly. #### Guarantees The sender of the message is free to both not deliver the message to the recipient at all (since no delivery occurs on-chain), and to alter the message contents (possibly resulting in an undecryptable message, or one with incorrect content). An undecryptable or otherwise invalid note or event message will however simply be ignored by the recipient, who can always validate the existence of the note or event on-chain. Because the message is not stored on-chain, it is the sender's (and eventually recipient's) responsability to back it up and make sure it is not lost. #### Costs Because no data is emitted on-chain, this delivery option is the cheapest one in terms of transaction fees: these are zero. Additionally, no circuit gates are introduced when the message is encrypted, since its provenance cannot be authenticated anyway. Therefore, off-chain messages do not affect proving time at all. #### Privacy No information is revelead on-chain about sender, recipient, or the message contents. The message itself reveals no information about the sender or recipient, and requires knowledge of the recipient's private address keys in order to obtain the plaintext. `ONCHAIN_UNCONSTRAINED: [u8](../../../std/primitive.u8.html)` Delivers the message on-chain, but with no guarantees on the content. #### Use Cases This delivery method is suitable when the sender is required to send the message to the recipient because of some external reason, but might not have a way to contact them off-chain, or does not wish to bear the responsability of keeping backups. In these cases, it might be unnecessary to force the sender to spend proving time guaranteeing message correctness. For example, when depositing funds into an escrow or sale contract the sender may not have an off-chain channel through which they could send the recipient a message. But since the recipient will not acknowledge receipt and proceed with the exchange unless they obtain the message, the sender has no reason not to deliver the message correctly. #### Guarantees The message will be stored on-chain in a private log, as part of the transaction's effects, and will be retrievable in the future without requiring any backups. However, the sender is free to alter the message contents (possibly resulting in an undecryptable message, or one with incorrect content), including making it so that the recipient cannot find it. An undecryptable or otherwise invalid note or event message will however simply be ignored by the recipient, who can always validate the existence of the note or event on-chain. These guarantees make this delivery mechanism be quite similar to [`MessageDeliveryEnum::OFFCHAIN`](../../../noir_aztec/messages/message_delivery/struct.MessageDeliveryEnum.html#structfield.OFFCHAIN), except the sender does not need to establish an off-chain communication channel with the recipient, and neither party needs to worry about backups. #### Costs Because the encrypted message is emitted on-chain as transaction private logs, this delivery option results in transaction fees associated with DA gas. The length of the original message is irrelevant to this cost, since all private logs are padded to the same length with random data to enhance privacy. However, no circuit gates are introduced when the message is encrypted. Therefore, on-chain unconstrained messages do not affect proving time at all. #### Privacy No information is revealed on-chain about sender, recipient, or the message contents. The message itself reveals no information about the sender or recipient, and requires knowledge of the recipient's private address keys in order to obtain the plaintext. Delivering the message does produce on-chain information in the form of private logs, so transactions that deliver many messages this way might be identifiable by the large number of logs. Identifying that a log corresponds to a message between a given sender and recipient requires, among other things, knowledge of both of their addresses and either the sender's or recipient's private address key. `ONCHAIN_CONSTRAINED: [u8](../../../std/primitive.u8.html)` Delivers the message on-chain, guaranteeing the recipient will receive the correct content. WARNING: this delivery mode is [currently NOT fully constrained](https://github.com/AztecProtocol/aztec-packages/issues/14565). The log's tag is unconstrained, meaning a malicious sender could manipulate it to prevent the recipient from finding the message. #### Use Cases This delivery method is suitable for all use cases, since it always works as expected. It is however the most costly method, and there are multiple scenarios where alternatives such as [`MessageDeliveryEnum::OFFCHAIN`](../../../noir_aztec/messages/message_delivery/struct.MessageDeliveryEnum.html#structfield.OFFCHAIN) or [`MessageDeliveryEnum::ONCHAIN_UNCONSTRAINED`](../../../noir_aztec/messages/message_delivery/struct.MessageDeliveryEnum.html#structfield.ONCHAIN_UNCONSTRAINED) will suffice. If the sender cannot be relied on to correctly send the message to the recipient (e.g. because they have no incentive to do so, such as when paying a fee to a protocol, creating the change note after spending a third party's tokens, or updating the configuration of a shared system like a multisig) then this is the only suitable delivery option. #### Guarantees The message will be stored on-chain in a private log, as part of the transaction's effects, and will be retrievable in the future without requiring any backups. The ciphertext will be decryptable by the recipient using their address private key and the ephemeral public key that accompanies the message. The log will be tagged in such a way that the recipient will be able to efficiently find it after querying for handshakes. #### Costs Because the encrypted message is emitted on-chain as transaction private logs, this delivery option results in transaction fees associated with DA gas. The length of the original message is irrelevant to this cost, since all private logs are padded to the same length with random data to enhance privacy. Additionally, the constraining of the log's tag results in additional DA usage and hence transaction fees due to the emission of nullifiers. Proving time is also increased as circuit gates are introduced to guarantee both the correct encryption of the message, and selection of log tag. #### Privacy No information is revelead on-chain about sender, recipient, or the message contents. The message itself reveals no information about the sender or recipient, and requires knowledge of the recipient's private address keys in order to obtain the plaintext. Delivering the message does produce on-chain information in the form of private logs and nullifiers, so transactions that deliver many messages this way might be identifiable by these markers. Identifying that a log corresponds to a message between a given sender and recipient requires, among other things, knowledge of both of their addresses and either the sender's or recipient's private address key. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/msg_type/fn.custom_msg_type_id.html # Function custom_msg_type_id ``` pub comptime fn custom_msg_type_id(local_id: [u64](../../../std/primitive.u64.html)) -> [u64](../../../std/primitive.u64.html) ``` Creates a custom message type ID from a local index. Custom message handlers must use message type IDs >= `MIN_CUSTOM_MSG_TYPE_ID`. This function offsets the provided `local_id` by that minimum, making it clear at the call site that a custom (non-built-in) message type is being defined. ## Examples ``` global MY_MSG_TYPE_ID: u64 = custom_msg_type_id(0); global MY_OTHER_MSG_TYPE_ID: u64 = custom_msg_type_id(1); ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/msg_type/global.MIN_CUSTOM_MSG_TYPE_ID.html # Global MIN_CUSTOM_MSG_TYPE_ID ``` pub global MIN_CUSTOM_MSG_TYPE_ID: [u64](../../../std/primitive.u64.html); ``` The minimum message type ID that custom message handlers may use. IDs below this value are reserved for current and future aztec.nr built-in message types (notes, partial notes, events, etc). If a message is received with a type ID below this threshold that aztec.nr doesn't recognize as its own, processing will fail with an error instructing the developer to use a higher ID. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/msg_type/global.PARTIAL_NOTE_PRIVATE_MSG_TYPE_ID.html # Global PARTIAL_NOTE_PRIVATE_MSG_TYPE_ID ``` pub global PARTIAL_NOTE_PRIVATE_MSG_TYPE_ID: [u64](../../../std/primitive.u64.html); ``` A message containing the private information of a partial note, i.e. one that has both private and public fields. This message contains all information necessary in order to find the public note information once it is created, and then prove existence of the note. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/msg_type/global.PRIVATE_EVENT_MSG_TYPE_ID.html # Global PRIVATE_EVENT_MSG_TYPE_ID ``` pub global PRIVATE_EVENT_MSG_TYPE_ID: [u64](../../../std/primitive.u64.html); ``` A message containing the information about a private event, i.e. one that has been emitted privately. This message contains all information necessary in order to prove existence of the event. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/msg_type/global.PRIVATE_NOTE_MSG_TYPE_ID.html # Global PRIVATE_NOTE_MSG_TYPE_ID ``` pub global PRIVATE_NOTE_MSG_TYPE_ID: [u64](../../../std/primitive.u64.html); ``` A message containing the information about a private note, i.e. one that has been created fully privately with no public fields. This message contains all information necessary in order to prove existence of the note. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/msg_type/index.html # Module msg_type ## Functions - [custom_msg_type_id](fn.custom_msg_type_id.html)Creates a custom message type ID from a local index. ## Globals - [MIN_CUSTOM_MSG_TYPE_ID](global.MIN_CUSTOM_MSG_TYPE_ID.html)The minimum message type ID that custom message handlers may use. - [PARTIAL_NOTE_PRIVATE_MSG_TYPE_ID](global.PARTIAL_NOTE_PRIVATE_MSG_TYPE_ID.html)A message containing the private information of a partial note, i.e. one that has both private and public fields. - [PRIVATE_EVENT_MSG_TYPE_ID](global.PRIVATE_EVENT_MSG_TYPE_ID.html)A message containing the information about a private event, i.e. one that has been emitted privately. - [PRIVATE_NOTE_MSG_TYPE_ID](global.PRIVATE_NOTE_MSG_TYPE_ID.html)A message containing the information about a private note, i.e. one that has been created fully privately with no public fields. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/offchain_messages/fn.deliver_offchain_message.html # Function deliver_offchain_message ``` pub fn deliver_offchain_message(ciphertext: [[Field](../../../std/primitive.Field.html); 15], recipient: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html)) ``` Emits a message that will be delivered offchain rather than through the data availability layer. Sends data through an alternative app-specific channel without incurring data availability (DA) costs. After receiving the message, the recipient is expected to call the `process_message` function implemented on the contract that originally emitted the message. ## Example use case A typical use case would be a payment app where the notes and events do not need to be delivered via DA because the payment is considered successful by the recipient once he receives the notes and events offchain. Hence having the guaranteed delivery via DA is not necessary. ## When not to use This function should not be used when an onchain guarantee of successful delivery is required. This is the case when a smart contract (rather than a person) needs to make decisions based on the message. For example, consider a contract that escrows a privately-stored NFT (i.e. an NFT represented by a note) and releases it to a buyer only after receiving a payment in a specific token. Without onchain delivery, the buyer could potentially obtain the NFT without sending the payment token message (the note hash preimage) to the seller, rugging the seller. To clarify the above, while the malicious buyer's payment token would still be deducted from their balance, they would obtain the NFT while the seller would be unable to spend the payment token, keeping the payment token note in limbo. ## Arguments - `message` - The message to emit. - `recipient` - The address of the recipient. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/offchain_messages/global.OFFCHAIN_MESSAGE_IDENTIFIER.html # Global OFFCHAIN_MESSAGE_IDENTIFIER ``` pub global OFFCHAIN_MESSAGE_IDENTIFIER: [Field](../../../std/primitive.Field.html); ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/offchain_messages/index.html # Module offchain_messages ## Functions - [deliver_offchain_message](fn.deliver_offchain_message.html)Emits a message that will be delivered offchain rather than through the data availability layer. ## Globals - [OFFCHAIN_MESSAGE_IDENTIFIER](global.OFFCHAIN_MESSAGE_IDENTIFIER.html) --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/processing/fn.enqueue_event_for_validation.html # Function enqueue_event_for_validation ``` pub unconstrained fn enqueue_event_for_validation( contract_address: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), event_type_id: [EventSelector](../../../noir_aztec/event/struct.EventSelector.html), randomness: [Field](../../../std/primitive.Field.html), serialized_event: [BoundedVec](../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Field](../../../std/primitive.Field.html), 10>, event_commitment: [Field](../../../std/primitive.Field.html), tx_hash: [Field](../../../std/primitive.Field.html), ) ``` Enqueues an event for validation and storage by PXE. This is the primary way for custom message handlers (registered via [`crate::macros::AztecConfig::custom_message_handler`](../../../noir_aztec/macros/struct.AztecConfig.html#custom_message_handler)) to deliver reassembled events back to PXE after processing application-specific message formats. In order for the event validation and insertion to occur, `validate_and_store_enqueued_notes_and_events` must be later called. For optimal performance, accumulate as many event validation requests as possible and then validate them all at the end (which results in PXE minimizing the number of network round-trips). Note that `validate_and_store_enqueued_notes_and_events` is called by Aztec.nr after processing messages, so custom message processors do not need to be concerned with this. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/processing/fn.enqueue_note_for_validation.html # Function enqueue_note_for_validation ``` pub unconstrained fn enqueue_note_for_validation( contract_address: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), owner: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), storage_slot: [Field](../../../std/primitive.Field.html), randomness: [Field](../../../std/primitive.Field.html), note_nonce: [Field](../../../std/primitive.Field.html), packed_note: [BoundedVec](../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Field](../../../std/primitive.Field.html), 8>, note_hash: [Field](../../../std/primitive.Field.html), nullifier: [Field](../../../std/primitive.Field.html), tx_hash: [Field](../../../std/primitive.Field.html), ) ``` Enqueues a note for validation and storage by PXE. Once validated, the note becomes retrievable via the `get_notes` oracle. The note will be scoped to `contract_address`, meaning other contracts will not be able to access it unless authorized. In order for the note validation and insertion to occur, `validate_and_store_enqueued_notes_and_events` must be later called. For optimal performance, accumulate as many note validation requests as possible and then validate them all at the end (which results in PXE minimizing the number of network round-trips). The `packed_note` is what `getNotes` will later return. PXE indexes notes by `storage_slot`, so this value is typically used to filter notes that correspond to different state variables. `note_hash` and `nullifier` are the inner hashes, i.e. the raw hashes returned by `NoteHash::compute_note_hash` and `NoteHash::compute_nullifier`. PXE will verify that the siloed unique note hash was inserted into the tree at `tx_hash`, and will store the nullifier to later check for nullification. `owner` is the address used in note hash and nullifier computation, often requiring knowledge of their nullifier secret key. `scope` is the account to which the note message was delivered (i.e. the address the message was encrypted to). This determines which PXE account can see the note - other accounts will not be able to access it (e.g. other accounts will not be able to see one another's token balance notes, even in the same PXE) unless authorized. In most cases `recipient` equals `owner`, but they can differ in scenarios like delegated discovery. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/processing/fn.validate_and_store_enqueued_notes_and_events.html # Function validate_and_store_enqueued_notes_and_events ``` pub unconstrained fn validate_and_store_enqueued_notes_and_events(scope: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html)) ``` Validates and stores all enqueued notes and events. Processes all requests enqueued via [`enqueue_note_for_validation`](../../../noir_aztec/messages/processing/fn.enqueue_note_for_validation.html) and [`enqueue_event_for_validation`](../../../noir_aztec/messages/processing/fn.enqueue_event_for_validation.html), inserting them into the note database and event store respectively, making them queryable via `get_notes` oracle and our TS API (PXE::getPrivateEvents). --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/processing/index.html # Module processing ## Modules - [offchain](offchain/index.html) ## Structs - [MessageContext](struct.MessageContext.html)Additional information needed to process a message. - [NoteValidationRequest](struct.NoteValidationRequest.html)Intermediate struct used to perform batch note validation by PXE. The `aztec_utl_validateAndStoreEnqueuedNotesAndEvents` oracle expects for values of this type to be stored in a `EphemeralArray`. - [OffchainMessageWithContext](struct.OffchainMessageWithContext.html)An offchain-delivered message with resolved context, ready for processing during sync. ## Functions - [enqueue_event_for_validation](fn.enqueue_event_for_validation.html)Enqueues an event for validation and storage by PXE. - [enqueue_note_for_validation](fn.enqueue_note_for_validation.html)Enqueues a note for validation and storage by PXE. - [validate_and_store_enqueued_notes_and_events](fn.validate_and_store_enqueued_notes_and_events.html)Validates and stores all enqueued notes and events. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/processing/offchain/fn.receive.html # Function receive ``` pub unconstrained fn receive( contract_address: [AztecAddress](../../../../protocol_types/address/aztec_address/struct.AztecAddress.html), messages: [BoundedVec](../../../../std/collections/bounded_vec/struct.BoundedVec.html)<[OffchainMessage](../../../../noir_aztec/messages/processing/offchain/struct.OffchainMessage.html), 16>, ) ``` Delivers offchain messages to the given contract's offchain inbox for subsequent processing. Offchain messages are transaction effects that are not broadcasted via onchain logs. Instead, the sender shares the message to the recipient through an external channel (e.g. a URL accessible by the recipient). The recipient then calls this function to hand the messages to the contract so they can be processed through the same mechanisms as onchain messages. Each message is routed to the inbox scoped to its `recipient` field, so messages for different accounts are automatically isolated. Messages are processed when their originating transaction is found onchain (providing the context needed to validate resulting notes and events). Messages are kept in the inbox until they expire. The effective expiration is `anchor_block_timestamp + MAX_MSG_TTL`. Processing order is not guaranteed. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/processing/offchain/fn.sync_inbox.html # Function sync_inbox ``` pub unconstrained fn sync_inbox( contract_address: [AztecAddress](../../../../protocol_types/address/aztec_address/struct.AztecAddress.html), scope: [AztecAddress](../../../../protocol_types/address/aztec_address/struct.AztecAddress.html), ) -> [EphemeralArray](../../../../noir_aztec/ephemeral/struct.EphemeralArray.html)<[OffchainMessageWithContext](../../../../noir_aztec/messages/processing/struct.OffchainMessageWithContext.html)> ``` Returns offchain-delivered messages to process during sync. Messages remain in the inbox and are reprocessed on each sync until their originating transaction is no longer at risk of being dropped by a reorg. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/processing/offchain/global.MAX_OFFCHAIN_MESSAGES_PER_RECEIVE_CALL.html # Global MAX_OFFCHAIN_MESSAGES_PER_RECEIVE_CALL ``` pub global MAX_OFFCHAIN_MESSAGES_PER_RECEIVE_CALL: [u32](../../../../std/primitive.u32.html); ``` Maximum number of offchain messages accepted by `offchain_receive` in a single call. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/processing/offchain/index.html # Module offchain ## Structs - [OffchainMessage](struct.OffchainMessage.html)A message delivered via the `offchain_receive` utility function. ## Functions - [receive](fn.receive.html)Delivers offchain messages to the given contract's offchain inbox for subsequent processing. - [sync_inbox](fn.sync_inbox.html)Returns offchain-delivered messages to process during sync. ## Globals - [MAX_OFFCHAIN_MESSAGES_PER_RECEIVE_CALL](global.MAX_OFFCHAIN_MESSAGES_PER_RECEIVE_CALL.html)Maximum number of offchain messages accepted by `offchain_receive` in a single call. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/processing/offchain/struct.OffchainMessage.html # Struct OffchainMessage ``` pub struct OffchainMessage { pub ciphertext: [BoundedVec](../../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Field](../../../../std/primitive.Field.html), 15>, pub recipient: [AztecAddress](../../../../protocol_types/address/aztec_address/struct.AztecAddress.html), pub tx_hash: [Option](../../../../std/option/struct.Option.html)<[Field](../../../../std/primitive.Field.html)>, pub anchor_block_timestamp: [u64](../../../../std/primitive.u64.html), } ``` A message delivered via the `offchain_receive` utility function. ## Fields `ciphertext: [BoundedVec](../../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Field](../../../../std/primitive.Field.html), 15>` The encrypted message payload. `recipient: [AztecAddress](../../../../protocol_types/address/aztec_address/struct.AztecAddress.html)` The intended recipient of the message. `tx_hash: [Option](../../../../std/option/struct.Option.html)<[Field](../../../../std/primitive.Field.html)>` The hash of the transaction that produced this message. `Option::none` indicates a tx-less message. `anchor_block_timestamp: [u64](../../../../std/primitive.u64.html)` Anchor block timestamp at message emission. ## Trait implementations ### `impl [Deserialize](../../../../serde/serialization/trait.Deserialize.html) for [OffchainMessage](../../../../noir_aztec/messages/processing/offchain/struct.OffchainMessage.html)` `pub fn deserialize(fields: [[Field](../../../../std/primitive.Field.html); 20]) -> Self` `pub fn stream_deserialize(reader: &mut [Reader](../../../../serde/reader/struct.Reader.html)) -> Self` ### `impl [Serialize](../../../../serde/serialization/trait.Serialize.html) for [OffchainMessage](../../../../noir_aztec/messages/processing/offchain/struct.OffchainMessage.html)` `pub fn serialize(self) -> [[Field](../../../../std/primitive.Field.html); 20]` `pub fn stream_serialize(self, writer: &mut [Writer](../../../../serde/writer/struct.Writer.html))` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/processing/struct.MessageContext.html # Struct MessageContext ``` pub struct MessageContext { pub tx_hash: [Field](../../../std/primitive.Field.html), pub unique_note_hashes_in_tx: [BoundedVec](../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Field](../../../std/primitive.Field.html), 64>, pub first_nullifier_in_tx: [Field](../../../std/primitive.Field.html), } ``` Additional information needed to process a message. All messages exist in the context of a transaction, and information about that transaction is typically required in order to perform validation, store results, etc. For example, messages containing notes require knowledge of note hashes and the first nullifier in order to find the note's nonce. ## Fields `tx_hash: [Field](../../../std/primitive.Field.html)` `unique_note_hashes_in_tx: [BoundedVec](../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Field](../../../std/primitive.Field.html), 64>` `first_nullifier_in_tx: [Field](../../../std/primitive.Field.html)` ## Trait implementations ### `impl [Deserialize](../../../serde/serialization/trait.Deserialize.html) for [MessageContext](../../../noir_aztec/messages/processing/struct.MessageContext.html)` `pub fn deserialize(fields: [[Field](../../../std/primitive.Field.html); 67]) -> Self` `pub fn stream_deserialize(reader: &mut [Reader](../../../serde/reader/struct.Reader.html)) -> Self` ### `impl [Eq](../../../std/cmp/trait.Eq.html) for [MessageContext](../../../noir_aztec/messages/processing/struct.MessageContext.html)` `pub fn eq(_self: Self, _other: Self) -> [bool](../../../std/primitive.bool.html)` ### `impl [Serialize](../../../serde/serialization/trait.Serialize.html) for [MessageContext](../../../noir_aztec/messages/processing/struct.MessageContext.html)` `pub fn serialize(self) -> [[Field](../../../std/primitive.Field.html); 67]` `pub fn stream_serialize(self, writer: &mut [Writer](../../../serde/writer/struct.Writer.html))` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/processing/struct.NoteValidationRequest.html # Struct NoteValidationRequest ``` pub struct NoteValidationRequest { pub contract_address: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), pub owner: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), pub storage_slot: [Field](../../../std/primitive.Field.html), pub randomness: [Field](../../../std/primitive.Field.html), pub note_nonce: [Field](../../../std/primitive.Field.html), pub packed_note: [BoundedVec](../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Field](../../../std/primitive.Field.html), 8>, pub note_hash: [Field](../../../std/primitive.Field.html), pub nullifier: [Field](../../../std/primitive.Field.html), pub tx_hash: [Field](../../../std/primitive.Field.html), } ``` Intermediate struct used to perform batch note validation by PXE. The `aztec_utl_validateAndStoreEnqueuedNotesAndEvents` oracle expects for values of this type to be stored in a `EphemeralArray`. ## Fields `contract_address: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html)` `owner: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html)` `storage_slot: [Field](../../../std/primitive.Field.html)` `randomness: [Field](../../../std/primitive.Field.html)` `note_nonce: [Field](../../../std/primitive.Field.html)` `packed_note: [BoundedVec](../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Field](../../../std/primitive.Field.html), 8>` `note_hash: [Field](../../../std/primitive.Field.html)` `nullifier: [Field](../../../std/primitive.Field.html)` `tx_hash: [Field](../../../std/primitive.Field.html)` ## Trait implementations ### `impl [Deserialize](../../../serde/serialization/trait.Deserialize.html) for [NoteValidationRequest](../../../noir_aztec/messages/processing/struct.NoteValidationRequest.html)` `pub fn deserialize(fields: [[Field](../../../std/primitive.Field.html); 17]) -> Self` `pub fn stream_deserialize(reader: &mut [Reader](../../../serde/reader/struct.Reader.html)) -> Self` ### `impl [Serialize](../../../serde/serialization/trait.Serialize.html) for [NoteValidationRequest](../../../noir_aztec/messages/processing/struct.NoteValidationRequest.html)` `pub fn serialize(self) -> [[Field](../../../std/primitive.Field.html); 17]` `pub fn stream_serialize(self, writer: &mut [Writer](../../../serde/writer/struct.Writer.html))` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/messages/processing/struct.OffchainMessageWithContext.html # Struct OffchainMessageWithContext ``` pub struct OffchainMessageWithContext { pub message_ciphertext: [BoundedVec](../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Field](../../../std/primitive.Field.html), 15>, pub message_context: [MessageContext](../../../noir_aztec/messages/processing/struct.MessageContext.html), } ``` An offchain-delivered message with resolved context, ready for processing during sync. ## Fields `message_ciphertext: [BoundedVec](../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Field](../../../std/primitive.Field.html), 15>` `message_context: [MessageContext](../../../noir_aztec/messages/processing/struct.MessageContext.html)` ## Trait implementations ### `impl [Deserialize](../../../serde/serialization/trait.Deserialize.html) for [OffchainMessageWithContext](../../../noir_aztec/messages/processing/struct.OffchainMessageWithContext.html)` `pub fn deserialize(fields: [[Field](../../../std/primitive.Field.html); 83]) -> Self` `pub fn stream_deserialize(reader: &mut [Reader](../../../serde/reader/struct.Reader.html)) -> Self` ### `impl [Serialize](../../../serde/serialization/trait.Serialize.html) for [OffchainMessageWithContext](../../../noir_aztec/messages/processing/struct.OffchainMessageWithContext.html)` `pub fn serialize(self) -> [[Field](../../../std/primitive.Field.html); 83]` `pub fn stream_serialize(self, writer: &mut [Writer](../../../serde/writer/struct.Writer.html))` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/constants/global.MAX_NOTES_PER_PAGE.html # Global MAX_NOTES_PER_PAGE ``` pub global MAX_NOTES_PER_PAGE: [u32](../../../std/primitive.u32.html); ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/index.html # Module note Note traits and utilities ## Siloing ## Modules - [constants](constants/index.html) - [lifecycle](lifecycle/index.html) - [note_getter](note_getter/index.html) - [note_getter_options](note_getter_options/index.html) - [note_interface](note_interface/index.html) - [note_metadata](note_metadata/index.html) - [note_viewer_options](note_viewer_options/index.html) - [utils](utils/index.html) ## Structs - [ConfirmedNote](struct.ConfirmedNote.html)A note that has been confirmed to exist. - [HintedNote](struct.HintedNote.html)A hint for a note that might exist. - [MaybeNoteMessage](struct.MaybeNoteMessage.html)Same as [`NoteMessage`](../../noir_aztec/note/struct.NoteMessage.html), except this type also handles the possibility where the note may not have been actually created depending on runtime conditions (e.g. a token transfer change note is not created if there is no change). - [NoteMessage](struct.NoteMessage.html)A message with information about a note that was created in the current contract call. This message MUST be delivered to a recipient in order to not lose the private note information. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/lifecycle/fn.create_note.html # Function create_note ``` pub fn create_note( context: &mut [PrivateContext](../../../noir_aztec/context/struct.PrivateContext.html), owner: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), storage_slot: [Field](../../../std/primitive.Field.html), note: Note, ) -> [NoteMessage](../../../noir_aztec/note/struct.NoteMessage.html) where Note: [NoteType](../../../noir_aztec/note/note_interface/trait.NoteType.html), Note: [NoteHash](../../../noir_aztec/note/note_interface/trait.NoteHash.html), Note: [Packable](../../../protocol_types/traits/trait.Packable.html) ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/lifecycle/fn.destroy_note.html # Function destroy_note ``` pub fn destroy_note( context: &mut [PrivateContext](../../../noir_aztec/context/struct.PrivateContext.html), confirmed_note: [ConfirmedNote](../../../noir_aztec/note/struct.ConfirmedNote.html), ) where Note: [NoteHash](../../../noir_aztec/note/note_interface/trait.NoteHash.html) ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/lifecycle/index.html # Module lifecycle ## Structs - [NewNote](struct.NewNote.html)A note that was created in the current contract call. ## Functions - [create_note](fn.create_note.html) - [destroy_note](fn.destroy_note.html) --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/lifecycle/struct.NewNote.html # Struct NewNote ``` pub struct NewNote { pub note: Note, pub owner: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), pub storage_slot: [Field](../../../std/primitive.Field.html), pub randomness: [Field](../../../std/primitive.Field.html), pub note_hash_counter: [u32](../../../std/primitive.u32.html), } ``` A note that was created in the current contract call. This struct holds a freshly created note along with the side-effect counter that the kernel uses to order note creations within a transaction. It is produced by [`create_note`](../../../noir_aztec/note/lifecycle/fn.create_note.html) and is typically wrapped in a [`NoteMessage`](../../../noir_aztec/note/struct.NoteMessage.html), which is responsible for delivering the note's information to its recipient so that it is not lost. Unlike [`ConfirmedNote`](../../../noir_aztec/note/struct.ConfirmedNote.html), which represents a note whose existence has been proven (either by reading it from PXE or by checking historical state), a `NewNote` represents a note whose creation is still pending in the current transaction's side-effect stream. Its note hash has been pushed into the [`PrivateContext`](../../../noir_aztec/context/struct.PrivateContext.html) but has not yet been siloed nor inserted into the note hash tree. ## Fields `note: Note` `owner: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html)` `storage_slot: [Field](../../../std/primitive.Field.html)` `randomness: [Field](../../../std/primitive.Field.html)` `note_hash_counter: [u32](../../../std/primitive.u32.html)` ## Implementations ### `impl [NewNote](../../../noir_aztec/note/lifecycle/struct.NewNote.html)` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/note_getter/fn.get_note.html # Function get_note ``` pub fn get_note( context: &mut [PrivateContext](../../../noir_aztec/context/struct.PrivateContext.html), owner: [Option](../../../std/option/struct.Option.html)<[AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html)>, storage_slot: [Field](../../../std/primitive.Field.html), ) -> [ConfirmedNote](../../../noir_aztec/note/struct.ConfirmedNote.html) where Note: [NoteType](../../../noir_aztec/note/note_interface/trait.NoteType.html), Note: [NoteHash](../../../noir_aztec/note/note_interface/trait.NoteHash.html), Note: [Packable](../../../protocol_types/traits/trait.Packable.html) ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/note_getter/fn.get_notes.html # Function get_notes ``` pub fn get_notes( context: &mut [PrivateContext](../../../noir_aztec/context/struct.PrivateContext.html), storage_slot: [Field](../../../std/primitive.Field.html), options: [NoteGetterOptions](../../../noir_aztec/note/note_getter_options/struct.NoteGetterOptions.html), ) -> [BoundedVec](../../../std/collections/bounded_vec/struct.BoundedVec.html)<[ConfirmedNote](../../../noir_aztec/note/struct.ConfirmedNote.html), 16> where Note: [NoteType](../../../noir_aztec/note/note_interface/trait.NoteType.html), Note: [NoteHash](../../../noir_aztec/note/note_interface/trait.NoteHash.html), Note: [Eq](../../../std/cmp/trait.Eq.html), Note: [Packable](../../../protocol_types/traits/trait.Packable.html) ``` Returns a BoundedVec of notes that have been proven to have been created by this contract, either in the current or past transactions (i.e. pending or settled notes). A second BoundedVec contains the note hashes used for the read requests, which can save constraints when computing the note's nullifiers. WARNING: recall that notes are never destroyed! Note existence therefore does not imply that the note is current or valid - this typically requires also emitting the note's nullifier to prove that it had not been emitted before. Because of this, calling this function directly from end-user applications should be discouraged, and safe abstractions such as aztec-nr's state variables should be used instead. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/note_getter/fn.view_note.html # Function view_note ``` pub unconstrained fn view_note( owner: [Option](../../../std/option/struct.Option.html)<[AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html)>, storage_slot: [Field](../../../std/primitive.Field.html), ) -> [HintedNote](../../../noir_aztec/note/struct.HintedNote.html) where Note: [NoteType](../../../noir_aztec/note/note_interface/trait.NoteType.html), Note: [Packable](../../../protocol_types/traits/trait.Packable.html) ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/note_getter/fn.view_notes.html # Function view_notes ``` pub unconstrained fn view_notes( storage_slot: [Field](../../../std/primitive.Field.html), options: [NoteViewerOptions](../../../noir_aztec/note/note_viewer_options/struct.NoteViewerOptions.html), ) -> [BoundedVec](../../../std/collections/bounded_vec/struct.BoundedVec.html) where Note: [NoteType](../../../noir_aztec/note/note_interface/trait.NoteType.html), Note: [Packable](../../../protocol_types/traits/trait.Packable.html), Note: [Eq](../../../std/cmp/trait.Eq.html) ``` Unconstrained variant of `get_notes`, meant to be used in unconstrained execution contexts. Notably only the note content is returned, and not any of the information used when proving its existence (e.g. note nonce, note hash, etc.). --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/note_getter/index.html # Module note_getter ## Re-exports - `pub use noir_aztec::note::constants::[MAX_NOTES_PER_PAGE](../../../noir_aztec/note/constants/global.MAX_NOTES_PER_PAGE.html);` ## Functions - [get_note](fn.get_note.html) - [get_notes](fn.get_notes.html)Returns a BoundedVec of notes that have been proven to have been created by this contract, either in the current or past transactions (i.e. pending or settled notes). A second BoundedVec contains the note hashes used for the read requests, which can save constraints when computing the note's nullifiers. - [view_note](fn.view_note.html) - [view_notes](fn.view_notes.html)Unconstrained variant of `get_notes`, meant to be used in unconstrained execution contexts. Notably only the note content is returned, and not any of the information used when proving its existence (e.g. note nonce, note hash, etc.). --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/note_getter_options/global.NoteStatus.html # Global NoteStatus ``` pub global NoteStatus: [NoteStatusEnum](../../../noir_aztec/note/note_getter_options/struct.NoteStatusEnum.html); ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/note_getter_options/global.SortOrder.html # Global SortOrder ``` pub global SortOrder: [SortOrderEnum](../../../noir_aztec/note/note_getter_options/struct.SortOrderEnum.html); ``` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/note_getter_options/index.html # Module note_getter_options ## Structs - [NoteGetterOptions](struct.NoteGetterOptions.html) - [NoteStatusEnum](struct.NoteStatusEnum.html) - [PropertySelector](struct.PropertySelector.html) - [Select](struct.Select.html) - [Sort](struct.Sort.html) - [SortOrderEnum](struct.SortOrderEnum.html) ## Globals - [NoteStatus](global.NoteStatus.html) - [SortOrder](global.SortOrder.html) --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/note_getter_options/struct.NoteGetterOptions.html # Struct NoteGetterOptions ``` pub struct NoteGetterOptions { pub selects: [BoundedVec](../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Option](../../../std/option/struct.Option.html)<[Select](../../../noir_aztec/note/note_getter_options/struct.Select.html)>, N>, pub sorts: [BoundedVec](../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Option](../../../std/option/struct.Option.html)<[Sort](../../../noir_aztec/note/note_getter_options/struct.Sort.html)>, N>, pub limit: [u32](../../../std/primitive.u32.html), pub offset: [u32](../../../std/primitive.u32.html), pub owner: [Option](../../../std/option/struct.Option.html)<[AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html)>, pub preprocessor: fn([[Option](../../../std/option/struct.Option.html)<[HintedNote](../../../noir_aztec/note/struct.HintedNote.html)>; 16], PreprocessorArgs) -> [[Option](../../../std/option/struct.Option.html)<[HintedNote](../../../noir_aztec/note/struct.HintedNote.html)>; 16], pub preprocessor_args: PreprocessorArgs, pub filter: fn([[Option](../../../std/option/struct.Option.html)<[HintedNote](../../../noir_aztec/note/struct.HintedNote.html)>; 16], FilterArgs) -> [[Option](../../../std/option/struct.Option.html)<[HintedNote](../../../noir_aztec/note/struct.HintedNote.html)>; 16], pub filter_args: FilterArgs, pub status: [u8](../../../std/primitive.u8.html), } ``` ## Fields `selects: [BoundedVec](../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Option](../../../std/option/struct.Option.html)<[Select](../../../noir_aztec/note/note_getter_options/struct.Select.html)>, N>` `sorts: [BoundedVec](../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Option](../../../std/option/struct.Option.html)<[Sort](../../../noir_aztec/note/note_getter_options/struct.Sort.html)>, N>` `limit: [u32](../../../std/primitive.u32.html)` `offset: [u32](../../../std/primitive.u32.html)` `owner: [Option](../../../std/option/struct.Option.html)<[AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html)>` `preprocessor: fn([[Option](../../../std/option/struct.Option.html)<[HintedNote](../../../noir_aztec/note/struct.HintedNote.html)>; 16], PreprocessorArgs) -> [[Option](../../../std/option/struct.Option.html)<[HintedNote](../../../noir_aztec/note/struct.HintedNote.html)>; 16]` `preprocessor_args: PreprocessorArgs` `filter: fn([[Option](../../../std/option/struct.Option.html)<[HintedNote](../../../noir_aztec/note/struct.HintedNote.html)>; 16], FilterArgs) -> [[Option](../../../std/option/struct.Option.html)<[HintedNote](../../../noir_aztec/note/struct.HintedNote.html)>; 16]` `filter_args: FilterArgs` `status: [u8](../../../std/primitive.u8.html)` ## Implementations ### `impl [NoteGetterOptions](../../../noir_aztec/note/note_getter_options/struct.NoteGetterOptions.html)` `pub fn [select](#select)( &mut self, property_selector: [PropertySelector](../../../noir_aztec/note/note_getter_options/struct.PropertySelector.html), comparator: [u8](../../../std/primitive.u8.html), value: T, ) -> Self where T: [ToField](../../../protocol_types/traits/trait.ToField.html)` `pub fn [sort](#sort)(&mut self, property_selector: [PropertySelector](../../../noir_aztec/note/note_getter_options/struct.PropertySelector.html), order: [u8](../../../std/primitive.u8.html)) -> Self` `pub fn [set_limit](#set_limit)(&mut self, limit: [u32](../../../std/primitive.u32.html)) -> Self` `pub fn [set_offset](#set_offset)(&mut self, offset: [u32](../../../std/primitive.u32.html)) -> Self` `pub fn [set_status](#set_status)(&mut self, status: [u8](../../../std/primitive.u8.html)) -> Self` `pub fn [set_owner](#set_owner)(&mut self, owner: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html)) -> Self` ### `impl [NoteGetterOptions](../../../noir_aztec/note/note_getter_options/struct.NoteGetterOptions.html)` `pub fn [new](#new)() -> Self where Note: [NoteType](../../../noir_aztec/note/note_interface/trait.NoteType.html), Note: [Packable](../../../protocol_types/traits/trait.Packable.html)` ### `impl [NoteGetterOptions](../../../noir_aztec/note/note_getter_options/struct.NoteGetterOptions.html)` `pub fn [with_preprocessor](#with_preprocessor)( preprocessor: fn([[Option](../../../std/option/struct.Option.html)<[HintedNote](../../../noir_aztec/note/struct.HintedNote.html)>; 16], PreprocessorArgs) -> [[Option](../../../std/option/struct.Option.html)<[HintedNote](../../../noir_aztec/note/struct.HintedNote.html)>; 16], preprocessor_args: PreprocessorArgs, ) -> Self where Note: [NoteType](../../../noir_aztec/note/note_interface/trait.NoteType.html), Note: [Packable](../../../protocol_types/traits/trait.Packable.html)` ### `impl [NoteGetterOptions](../../../noir_aztec/note/note_getter_options/struct.NoteGetterOptions.html)` `pub fn [with_filter](#with_filter)( filter: fn([[Option](../../../std/option/struct.Option.html)<[HintedNote](../../../noir_aztec/note/struct.HintedNote.html)>; 16], FilterArgs) -> [[Option](../../../std/option/struct.Option.html)<[HintedNote](../../../noir_aztec/note/struct.HintedNote.html)>; 16], filter_args: FilterArgs, ) -> Self where Note: [NoteType](../../../noir_aztec/note/note_interface/trait.NoteType.html), Note: [Packable](../../../protocol_types/traits/trait.Packable.html)` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/note_getter_options/struct.NoteStatusEnum.html # Struct NoteStatusEnum ``` pub struct NoteStatusEnum { pub ACTIVE: [u8](../../../std/primitive.u8.html), pub ACTIVE_OR_NULLIFIED: [u8](../../../std/primitive.u8.html), } ``` ## Fields `ACTIVE: [u8](../../../std/primitive.u8.html)` `ACTIVE_OR_NULLIFIED: [u8](../../../std/primitive.u8.html)` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/note_getter_options/struct.PropertySelector.html # Struct PropertySelector ``` pub struct PropertySelector { pub index: [u8](../../../std/primitive.u8.html), pub offset: [u8](../../../std/primitive.u8.html), pub length: [u8](../../../std/primitive.u8.html), } ``` ## Fields `index: [u8](../../../std/primitive.u8.html)` `offset: [u8](../../../std/primitive.u8.html)` `length: [u8](../../../std/primitive.u8.html)` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/note_getter_options/struct.Select.html # Struct Select ``` pub struct Select { /* private fields */ } ``` ## Implementations ### `impl [Select](../../../noir_aztec/note/note_getter_options/struct.Select.html)` `pub fn [new](#new)(property_selector: [PropertySelector](../../../noir_aztec/note/note_getter_options/struct.PropertySelector.html), comparator: [u8](../../../std/primitive.u8.html), value: [Field](../../../std/primitive.Field.html)) -> Self` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/note_getter_options/struct.Sort.html # Struct Sort ``` pub struct Sort { /* private fields */ } ``` ## Implementations ### `impl [Sort](../../../noir_aztec/note/note_getter_options/struct.Sort.html)` `pub fn [new](#new)(property_selector: [PropertySelector](../../../noir_aztec/note/note_getter_options/struct.PropertySelector.html), order: [u8](../../../std/primitive.u8.html)) -> Self` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/note_getter_options/struct.SortOrderEnum.html # Struct SortOrderEnum ``` pub struct SortOrderEnum { pub DESC: [u8](../../../std/primitive.u8.html), pub ASC: [u8](../../../std/primitive.u8.html), } ``` ## Fields `DESC: [u8](../../../std/primitive.u8.html)` `ASC: [u8](../../../std/primitive.u8.html)` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/note_interface/index.html # Module note_interface ## Traits - [NoteHash](trait.NoteHash.html) - [NoteProperties](trait.NoteProperties.html) - [NoteType](trait.NoteType.html) - [PartialNote](trait.PartialNote.html) --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/note_interface/trait.NoteHash.html # Trait NoteHash ``` pub trait NoteHash { // Required methods pub fn [compute_note_hash](#compute_note_hash)( self, owner: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), storage_slot: [Field](../../../std/primitive.Field.html), randomness: [Field](../../../std/primitive.Field.html), ) -> [Field](../../../std/primitive.Field.html); pub fn [compute_nullifier](#compute_nullifier)( self, context: &mut [PrivateContext](../../../noir_aztec/context/struct.PrivateContext.html), owner: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), note_hash_for_nullification: [Field](../../../std/primitive.Field.html), ) -> [Field](../../../std/primitive.Field.html); pub unconstrained fn [compute_nullifier_unconstrained](#compute_nullifier_unconstrained)( self, owner: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), note_hash_for_nullification: [Field](../../../std/primitive.Field.html), ) -> [Option](../../../std/option/struct.Option.html)<[Field](../../../std/primitive.Field.html)>; } ``` ## Required methods `pub fn [compute_note_hash](#compute_note_hash)( self, owner: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), storage_slot: [Field](../../../std/primitive.Field.html), randomness: [Field](../../../std/primitive.Field.html), ) -> [Field](../../../std/primitive.Field.html)` Returns the non-siloed note hash, i.e. the inner hash computed by the contract during private execution. Note hashes are later siloed by contract address and hashed with note nonce by the kernels before being committed to the state tree. This should be a commitment to the packed note, including the storage slot (for indexing) and some random value (to prevent brute force trial-hashing attacks). `pub fn [compute_nullifier](#compute_nullifier)( self, context: &mut [PrivateContext](../../../noir_aztec/context/struct.PrivateContext.html), owner: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), note_hash_for_nullification: [Field](../../../std/primitive.Field.html), ) -> [Field](../../../std/primitive.Field.html)` Returns the non-siloed nullifier (also called inner-nullifier), which will be later siloed by contract address by the kernels before being committed to the state tree. This function MUST be called with the correct note hash for consumption! It will otherwise silently fail and compute an incorrect value. The reason why we receive this as an argument instead of computing it ourselves directly is because the caller will typically already have computed this note hash, and we can reuse that value to reduce the total gate count of the circuit. This function receives the context since nullifier computation typically involves proving nullifying keys, and we require the kernel's assistance to do this in order to prevent having to reveal private keys to application circuits. `pub unconstrained fn [compute_nullifier_unconstrained](#compute_nullifier_unconstrained)( self, owner: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), note_hash_for_nullification: [Field](../../../std/primitive.Field.html), ) -> [Option](../../../std/option/struct.Option.html)<[Field](../../../std/primitive.Field.html)>` Like `compute_nullifier`, except this variant is unconstrained: there are no guarantees on the returned value being correct. Because of that it doesn't need to take a context (since it won't perform any kernel key validation requests). Returns `None` if the nullifier cannot be computed (when the relevant keys needed for nullifier computation are not available). ## Implementors ### `impl [NoteHash](../../../noir_aztec/note/note_interface/trait.NoteHash.html) for [AddressNote](../../../address_note/struct.AddressNote.html)` ### `impl [NoteHash](../../../noir_aztec/note/note_interface/trait.NoteHash.html) for [FieldNote](../../../field_note/struct.FieldNote.html)` ### `impl [NoteHash](../../../noir_aztec/note/note_interface/trait.NoteHash.html) for MockNote` ### `impl [NoteHash](../../../noir_aztec/note/note_interface/trait.NoteHash.html) for [UintNote](../../../uint_note/struct.UintNote.html)` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/note_interface/trait.NoteProperties.html # Trait NoteProperties ``` pub trait NoteProperties { // Required methods pub fn [properties](#properties)() -> T; } ``` ## Required methods `pub fn [properties](#properties)() -> T` ## Implementors ### `impl [NoteProperties](../../../noir_aztec/note/note_interface/trait.NoteProperties.html) for [AddressNote](../../../address_note/struct.AddressNote.html)` ### `impl [NoteProperties](../../../noir_aztec/note/note_interface/trait.NoteProperties.html) for [FieldNote](../../../field_note/struct.FieldNote.html)` ### `impl [NoteProperties](../../../noir_aztec/note/note_interface/trait.NoteProperties.html) for [UintNote](../../../uint_note/struct.UintNote.html)` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/note_interface/trait.NoteType.html # Trait NoteType ``` pub trait NoteType { // Required methods pub fn [get_id](#get_id)() -> [Field](../../../std/primitive.Field.html); } ``` ## Required methods `pub fn [get_id](#get_id)() -> [Field](../../../std/primitive.Field.html)` Returns the unique identifier for the note type. This is typically used when processing note logs. ## Implementors ### `impl [NoteType](../../../noir_aztec/note/note_interface/trait.NoteType.html) for [AddressNote](../../../address_note/struct.AddressNote.html)` ### `impl [NoteType](../../../noir_aztec/note/note_interface/trait.NoteType.html) for [FieldNote](../../../field_note/struct.FieldNote.html)` ### `impl [NoteType](../../../noir_aztec/note/note_interface/trait.NoteType.html) for MaxSizeNote` ### `impl [NoteType](../../../noir_aztec/note/note_interface/trait.NoteType.html) for MockNote` ### `impl [NoteType](../../../noir_aztec/note/note_interface/trait.NoteType.html) for OversizedNote` ### `impl [NoteType](../../../noir_aztec/note/note_interface/trait.NoteType.html) for [UintNote](../../../uint_note/struct.UintNote.html)` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/note_interface/trait.PartialNote.html # Trait PartialNote ``` pub trait PartialNote where S: [Empty](../../../protocol_types/traits/trait.Empty.html), F: [Empty](../../../protocol_types/traits/trait.Empty.html){ // Required methods pub fn [setup_payload](#setup_payload)() -> S; pub fn [finalization_payload](#finalization_payload)() -> F; } ``` ## Required methods `pub fn [setup_payload](#setup_payload)() -> S` `pub fn [finalization_payload](#finalization_payload)() -> F` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/note_metadata/index.html # Module note_metadata ## Structs - [NoteMetadata](struct.NoteMetadata.html)The metadata required to both prove a note's existence and destroy it, by computing the correct note hash for kernel read requests, as well as the correct nullifier to avoid double-spends. - [PendingPreviousPhaseNoteMetadata](struct.PendingPreviousPhaseNoteMetadata.html)The metadata required to both prove a note's existence and destroy it, by computing the correct note hash for kernel read requests, as well as the correct nullifier to avoid double-spends. - [PendingSamePhaseNoteMetadata](struct.PendingSamePhaseNoteMetadata.html)The metadata required to both prove a note's existence and destroy it, by computing the correct note hash for kernel read requests, as well as the correct nullifier to avoid double-spends. - [SettledNoteMetadata](struct.SettledNoteMetadata.html)The metadata required to both prove a note's existence and destroy it, by computing the correct note hash for kernel read requests, as well as the correct nullifier to avoid double-spends. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/note_metadata/struct.NoteMetadata.html # Struct NoteMetadata ``` pub struct NoteMetadata { /* private fields */ } ``` The metadata required to both prove a note's existence and destroy it, by computing the correct note hash for kernel read requests, as well as the correct nullifier to avoid double-spends. This represents a note in any of the three valid stages (pending same phase, pending previous phase, or settled). In order to access the underlying fields callers must first find the appropriate stage (e.g. via `is_settled()`) and then convert this into the appropriate type (e.g. via `to_settled()`). ## Implementations ### `impl [NoteMetadata](../../../noir_aztec/note/note_metadata/struct.NoteMetadata.html)` `pub fn [from_raw_data](#from_raw_data)(nonzero_note_hash_counter: [bool](../../../std/primitive.bool.html), maybe_note_nonce: [Field](../../../std/primitive.Field.html)) -> Self` Constructs a `NoteMetadata` object from optional note hash counter and nonce. Both a zero note hash counter and a zero nonce are invalid, so those are used to signal non-existent values. `pub fn [is_pending_same_phase](#is_pending_same_phase)(self) -> [bool](../../../std/primitive.bool.html)` Returns `true` if the note is pending and from the same phase, i.e. if it's been created in the current transaction during the current execution phase (either non-revertible or revertible). `pub fn [is_pending_previous_phase](#is_pending_previous_phase)(self) -> [bool](../../../std/primitive.bool.html)` Returns `true` if the note is pending and from the previous phase, i.e. if it's been created in the current transaction during an execution phase prior to the current one. Because private execution only has two phases with strict ordering, this implies that the note was created in the non-revertible phase, and that the current phase is the revertible phase. `pub fn [is_settled](#is_settled)(self) -> [bool](../../../std/primitive.bool.html)` Returns `true` if the note is settled, i.e. if it's been created in a prior transaction and is therefore already in the note hash tree. `pub fn [to_pending_same_phase](#to_pending_same_phase)(self) -> [PendingSamePhaseNoteMetadata](../../../noir_aztec/note/note_metadata/struct.PendingSamePhaseNoteMetadata.html)` Asserts that the metadata is that of a pending note from the same phase and converts it accordingly. `pub fn [to_pending_previous_phase](#to_pending_previous_phase)(self) -> [PendingPreviousPhaseNoteMetadata](../../../noir_aztec/note/note_metadata/struct.PendingPreviousPhaseNoteMetadata.html)` Asserts that the metadata is that of a pending note from a previous phase and converts it accordingly. `pub fn [to_settled](#to_settled)(self) -> [SettledNoteMetadata](../../../noir_aztec/note/note_metadata/struct.SettledNoteMetadata.html)` Asserts that the metadata is that of a settled note and converts it accordingly. ## Trait implementations ### `impl [Deserialize](../../../serde/serialization/trait.Deserialize.html) for [NoteMetadata](../../../noir_aztec/note/note_metadata/struct.NoteMetadata.html)` `pub fn deserialize(fields: [[Field](../../../std/primitive.Field.html); 2]) -> Self` `pub fn stream_deserialize(reader: &mut [Reader](../../../serde/reader/struct.Reader.html)) -> Self` ### `impl [Eq](../../../std/cmp/trait.Eq.html) for [NoteMetadata](../../../noir_aztec/note/note_metadata/struct.NoteMetadata.html)` `pub fn eq(_self: Self, _other: Self) -> [bool](../../../std/primitive.bool.html)` ### `impl [From](../../../std/convert/trait.From.html)<[PendingPreviousPhaseNoteMetadata](../../../noir_aztec/note/note_metadata/struct.PendingPreviousPhaseNoteMetadata.html)> for [NoteMetadata](../../../noir_aztec/note/note_metadata/struct.NoteMetadata.html)` `pub fn from(value: [PendingPreviousPhaseNoteMetadata](../../../noir_aztec/note/note_metadata/struct.PendingPreviousPhaseNoteMetadata.html)) -> Self` ### `impl [From](../../../std/convert/trait.From.html)<[PendingSamePhaseNoteMetadata](../../../noir_aztec/note/note_metadata/struct.PendingSamePhaseNoteMetadata.html)> for [NoteMetadata](../../../noir_aztec/note/note_metadata/struct.NoteMetadata.html)` `pub fn from(_value: [PendingSamePhaseNoteMetadata](../../../noir_aztec/note/note_metadata/struct.PendingSamePhaseNoteMetadata.html)) -> Self` ### `impl [From](../../../std/convert/trait.From.html)<[SettledNoteMetadata](../../../noir_aztec/note/note_metadata/struct.SettledNoteMetadata.html)> for [NoteMetadata](../../../noir_aztec/note/note_metadata/struct.NoteMetadata.html)` `pub fn from(value: [SettledNoteMetadata](../../../noir_aztec/note/note_metadata/struct.SettledNoteMetadata.html)) -> Self` ### `impl [Packable](../../../protocol_types/traits/trait.Packable.html) for [NoteMetadata](../../../noir_aztec/note/note_metadata/struct.NoteMetadata.html)` `pub fn pack(self) -> [[Field](../../../std/primitive.Field.html); 2]` `pub fn unpack(packed: [[Field](../../../std/primitive.Field.html); 2]) -> Self` ### `impl [Serialize](../../../serde/serialization/trait.Serialize.html) for [NoteMetadata](../../../noir_aztec/note/note_metadata/struct.NoteMetadata.html)` `pub fn serialize(self) -> [[Field](../../../std/primitive.Field.html); 2]` `pub fn stream_serialize(self, writer: &mut [Writer](../../../serde/writer/struct.Writer.html))` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/note_metadata/struct.PendingPreviousPhaseNoteMetadata.html # Struct PendingPreviousPhaseNoteMetadata ``` pub struct PendingPreviousPhaseNoteMetadata { /* private fields */ } ``` The metadata required to both prove a note's existence and destroy it, by computing the correct note hash for kernel read requests, as well as the correct nullifier to avoid double-spends. This represents a pending previous phase note, i.e. a note that was created in the transaction that is currently being executed, during the previous execution phase. Because there are only two phases and their order is always the same (first non-revertible and then revertible) this implies that the note was created in the non-revertible phase, and that the current phase is the revertible phase. ## Implementations ### `impl [PendingPreviousPhaseNoteMetadata](../../../noir_aztec/note/note_metadata/struct.PendingPreviousPhaseNoteMetadata.html)` `pub fn [new](#new)(note_nonce: [Field](../../../std/primitive.Field.html)) -> Self` `pub fn [note_nonce](#note_nonce)(self) -> [Field](../../../std/primitive.Field.html)` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/note_metadata/struct.PendingSamePhaseNoteMetadata.html # Struct PendingSamePhaseNoteMetadata ``` pub struct PendingSamePhaseNoteMetadata {} ``` The metadata required to both prove a note's existence and destroy it, by computing the correct note hash for kernel read requests, as well as the correct nullifier to avoid double-spends. This represents a pending same phase note, i.e. a note that was created in the transaction that is currently being executed during the current execution phase (either non-revertible or revertible). ## Implementations ### `impl [PendingSamePhaseNoteMetadata](../../../noir_aztec/note/note_metadata/struct.PendingSamePhaseNoteMetadata.html)` `pub fn [new](#new)() -> Self` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/note_metadata/struct.SettledNoteMetadata.html # Struct SettledNoteMetadata ``` pub struct SettledNoteMetadata { /* private fields */ } ``` The metadata required to both prove a note's existence and destroy it, by computing the correct note hash for kernel read requests, as well as the correct nullifier to avoid double-spends. This represents a settled note, i.e. a note that was created in a prior transaction and is therefore already in the note hash tree. ## Implementations ### `impl [SettledNoteMetadata](../../../noir_aztec/note/note_metadata/struct.SettledNoteMetadata.html)` `pub fn [new](#new)(note_nonce: [Field](../../../std/primitive.Field.html)) -> Self` `pub fn [note_nonce](#note_nonce)(self) -> [Field](../../../std/primitive.Field.html)` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/note_viewer_options/struct.NoteViewerOptions.html # Struct NoteViewerOptions ``` pub struct NoteViewerOptions { pub selects: [BoundedVec](../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Option](../../../std/option/struct.Option.html)<[Select](../../../noir_aztec/note/note_getter_options/struct.Select.html)>, M>, pub sorts: [BoundedVec](../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Option](../../../std/option/struct.Option.html)<[Sort](../../../noir_aztec/note/note_getter_options/struct.Sort.html)>, M>, pub limit: [u32](../../../std/primitive.u32.html), pub offset: [u32](../../../std/primitive.u32.html), pub owner: [Option](../../../std/option/struct.Option.html)<[AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html)>, pub status: [u8](../../../std/primitive.u8.html), } ``` ## Fields `selects: [BoundedVec](../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Option](../../../std/option/struct.Option.html)<[Select](../../../noir_aztec/note/note_getter_options/struct.Select.html)>, M>` `sorts: [BoundedVec](../../../std/collections/bounded_vec/struct.BoundedVec.html)<[Option](../../../std/option/struct.Option.html)<[Sort](../../../noir_aztec/note/note_getter_options/struct.Sort.html)>, M>` `limit: [u32](../../../std/primitive.u32.html)` `offset: [u32](../../../std/primitive.u32.html)` `owner: [Option](../../../std/option/struct.Option.html)<[AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html)>` `status: [u8](../../../std/primitive.u8.html)` ## Implementations ### `impl [NoteViewerOptions](../../../noir_aztec/note/note_viewer_options/struct.NoteViewerOptions.html)` `pub fn [new](#new)() -> Self where Note: [NoteType](../../../noir_aztec/note/note_interface/trait.NoteType.html), Note: [Packable](../../../protocol_types/traits/trait.Packable.html)` `pub fn [select](#select)( &mut self, property_selector: [PropertySelector](../../../noir_aztec/note/note_getter_options/struct.PropertySelector.html), comparator: [u8](../../../std/primitive.u8.html), value: T, ) -> Self where T: [ToField](../../../protocol_types/traits/trait.ToField.html)` `pub fn [sort](#sort)(&mut self, property_selector: [PropertySelector](../../../noir_aztec/note/note_getter_options/struct.PropertySelector.html), order: [u8](../../../std/primitive.u8.html)) -> Self` `pub fn [set_limit](#set_limit)(&mut self, limit: [u32](../../../std/primitive.u32.html)) -> Self` `pub fn [set_offset](#set_offset)(&mut self, offset: [u32](../../../std/primitive.u32.html)) -> Self` `pub fn [set_status](#set_status)(&mut self, status: [u8](../../../std/primitive.u8.html)) -> Self` `pub fn [set_owner](#set_owner)(&mut self, owner: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html)) -> Self` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/struct.ConfirmedNote.html # Struct ConfirmedNote ``` pub struct ConfirmedNote { pub note: Note, pub contract_address: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html), pub owner: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html), pub randomness: [Field](../../std/primitive.Field.html), pub storage_slot: [Field](../../std/primitive.Field.html), pub metadata: [NoteMetadata](../../noir_aztec/note/note_metadata/struct.NoteMetadata.html), pub proven_note_hash: [Field](../../std/primitive.Field.html), } ``` A note that has been confirmed to exist. This struct contains the actual note content and all associated data related to it, including the note's metadata and the note hash that was used to prove its existence. Only `ConfirmedNote`s can be nullified, since emitting a nullifier for a note that does not exist is a meaningless action, and in the vast majority of cases an error. `ConfirmedNote`s can be obtained by reading notes from PXE via [`crate::note::note_getter::get_note`](../../noir_aztec/note/note_getter/fn.get_note.html) and [`crate::note::note_getter::get_notes`](../../noir_aztec/note/note_getter/fn.get_notes.html), or by confirming a [`crate::note::hinted_note::HintedNote`](../../noir_aztec/note/struct.HintedNote.html) via [`crate::history::note::assert_note_existed_by`](../../noir_aztec/history/note/fn.assert_note_existed_by.html). ## Pending Notes A pending `ConfirmedNote` (i.e. one of [`crate::note::note_metadata::NoteStageEnum::PENDING_SAME_PHASE`] or [`crate::note::note_metadata::NoteStageEnum::PENDING_PREVIOUS_PHASE`]) will not necessarily be inserted into the note hash tree: if it is nullified in the same transaction, both note hash and nullifier will be squashed (deleted) by the kernel, resulting in a transient note. ## Fields `note: Note` `contract_address: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html)` `owner: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html)` `randomness: [Field](../../std/primitive.Field.html)` `storage_slot: [Field](../../std/primitive.Field.html)` `metadata: [NoteMetadata](../../noir_aztec/note/note_metadata/struct.NoteMetadata.html)` `proven_note_hash: [Field](../../std/primitive.Field.html)` The note hash used to prove existence. Whether this note hash is unsiloed or unique depends on the note's metadata. ## Implementations ### `impl [ConfirmedNote](../../noir_aztec/note/struct.ConfirmedNote.html)` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/struct.HintedNote.html # Struct HintedNote ``` pub struct HintedNote { pub note: Note, pub contract_address: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html), pub owner: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html), pub randomness: [Field](../../std/primitive.Field.html), pub storage_slot: [Field](../../std/primitive.Field.html), pub metadata: [NoteMetadata](../../noir_aztec/note/note_metadata/struct.NoteMetadata.html), } ``` A hint for a note that might exist. This contains the actual note content and all metadata that is required in order to prove note's existence, regardless of whether the note is pending or settled (see [`crate::note::note_metadata::NoteMetadata`](../../noir_aztec/note/note_metadata/struct.NoteMetadata.html)). This value typically unconstrained (originating from oracles or as a contract function parameter), and can be converted into a [`crate::note::confirmed_note::ConfirmedNote`](../../noir_aztec/note/struct.ConfirmedNote.html) via [`crate::history::note::assert_note_existed_by`](../../noir_aztec/history/note/fn.assert_note_existed_by.html). ## Fields `note: Note` `contract_address: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html)` `owner: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html)` `randomness: [Field](../../std/primitive.Field.html)` `storage_slot: [Field](../../std/primitive.Field.html)` `metadata: [NoteMetadata](../../noir_aztec/note/note_metadata/struct.NoteMetadata.html)` ## Trait implementations ### ">`impl [Deserialize](../../serde/serialization/trait.Deserialize.html) for [HintedNote](../../noir_aztec/note/struct.HintedNote.html) where Note: [Deserialize](../../serde/serialization/trait.Deserialize.html)` `pub fn deserialize(fields: [[Field](../../std/primitive.Field.html); <(resolved type) as Deserialize>::N + 6]) -> Self` `pub fn stream_deserialize(reader: &mut [Reader](../../serde/reader/struct.Reader.html)) -> Self` ### ">`impl [Eq](../../std/cmp/trait.Eq.html) for [HintedNote](../../noir_aztec/note/struct.HintedNote.html) where Note: [Eq](../../std/cmp/trait.Eq.html)` `pub fn eq(_self: Self, _other: Self) -> [bool](../../std/primitive.bool.html)` ### ">`impl [Packable](../../protocol_types/traits/trait.Packable.html) for [HintedNote](../../noir_aztec/note/struct.HintedNote.html) where Note: [Packable](../../protocol_types/traits/trait.Packable.html)` `pub fn pack(self) -> [[Field](../../std/primitive.Field.html); <(resolved type) as Packable>::N + 6]` `pub fn unpack(packed: [[Field](../../std/primitive.Field.html); <(resolved type) as Packable>::N + 6]) -> Self` ### ">`impl [Serialize](../../serde/serialization/trait.Serialize.html) for [HintedNote](../../noir_aztec/note/struct.HintedNote.html) where Note: [Serialize](../../serde/serialization/trait.Serialize.html)` `pub fn serialize(self) -> [[Field](../../std/primitive.Field.html); <(resolved type) as Serialize>::N + 6]` `pub fn stream_serialize(self, writer: &mut [Writer](../../serde/writer/struct.Writer.html))` --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/struct.MaybeNoteMessage.html # Struct MaybeNoteMessage ``` pub struct MaybeNoteMessage { /* private fields */ } ``` Same as [`NoteMessage`](../../noir_aztec/note/struct.NoteMessage.html), except this type also handles the possibility where the note may not have been actually created depending on runtime conditions (e.g. a token transfer change note is not created if there is no change). Other than that, it and [`MaybeNoteMessage::deliver`](../../noir_aztec/note/struct.MaybeNoteMessage.html#deliver) behave the exact same way as [`NoteMessage`](../../noir_aztec/note/struct.NoteMessage.html). ## Implementations ### `impl [MaybeNoteMessage](../../noir_aztec/note/struct.MaybeNoteMessage.html)` `pub fn [new](#new)(maybe_new_note: [Option](../../std/option/struct.Option.html)<[NewNote](../../noir_aztec/note/lifecycle/struct.NewNote.html)>, context: &mut [PrivateContext](../../noir_aztec/context/struct.PrivateContext.html)) -> Self where Note: [NoteType](../../noir_aztec/note/note_interface/trait.NoteType.html), Note: [Packable](../../protocol_types/traits/trait.Packable.html)` `pub fn [deliver](#deliver)(self, delivery_mode: [u8](../../std/primitive.u8.html)) where Note: [NoteType](../../noir_aztec/note/note_interface/trait.NoteType.html), Note: [Packable](../../protocol_types/traits/trait.Packable.html)` Same as [`NoteMessage::deliver`](../../noir_aztec/note/struct.NoteMessage.html#deliver), except the message will only be delivered if it actually exists. Messages delivered using [`crate::messages::message_delivery::MessageDeliveryEnum::ONCHAIN_CONSTRAINED`](../../noir_aztec/messages/message_delivery/struct.MessageDeliveryEnum.html#structfield.ONCHAIN_CONSTRAINED) will pay proving costs regardless of whether the message exists or not. `pub fn [deliver_to](#deliver_to)(self, recipient: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html), delivery_mode: [u8](../../std/primitive.u8.html)) where Note: [NoteType](../../noir_aztec/note/note_interface/trait.NoteType.html), Note: [Packable](../../protocol_types/traits/trait.Packable.html)` Same as [`NoteMessage::deliver_to`](../../noir_aztec/note/struct.NoteMessage.html#deliver_to), except the message will only be delivered if it actually exists. Messages delivered using [`crate::messages::message_delivery::MessageDeliveryEnum::ONCHAIN_CONSTRAINED`](../../noir_aztec/messages/message_delivery/struct.MessageDeliveryEnum.html#structfield.ONCHAIN_CONSTRAINED) will pay proving costs regardless of whether the message exists or not. `pub fn [get_note](#get_note)(self) -> [Option](../../std/option/struct.Option.html) where Note: [NoteType](../../noir_aztec/note/note_interface/trait.NoteType.html), Note: [Packable](../../protocol_types/traits/trait.Packable.html)` Returns the note contained in the message. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/struct.NoteMessage.html # Struct NoteMessage ``` pub struct NoteMessage { /* private fields */ } ``` A message with information about a note that was created in the current contract call. This message MUST be delivered to a recipient in order to not lose the private note information. Use [`NoteMessage::deliver`](../../noir_aztec/note/struct.NoteMessage.html#deliver) to select a delivery mechanism. The same message can be delivered to multiple recipients. ## Implementations ### `impl [NoteMessage](../../noir_aztec/note/struct.NoteMessage.html)` `pub fn [new](#new)(new_note: [NewNote](../../noir_aztec/note/lifecycle/struct.NewNote.html), context: &mut [PrivateContext](../../noir_aztec/context/struct.PrivateContext.html)) -> Self where Note: [NoteType](../../noir_aztec/note/note_interface/trait.NoteType.html), Note: [Packable](../../protocol_types/traits/trait.Packable.html)` `pub fn [deliver](#deliver)(self, delivery_mode: [u8](../../std/primitive.u8.html)) where Note: [NoteType](../../noir_aztec/note/note_interface/trait.NoteType.html), Note: [Packable](../../protocol_types/traits/trait.Packable.html)` Delivers the note message to its owner, providing them access to the private note information. The message is first encrypted to the owner's public key, ensuring no other actor can read it. The `delivery_mode` must be one of [`crate::messages::message_delivery::MessageDeliveryEnum`](../../noir_aztec/messages/message_delivery/struct.MessageDeliveryEnum.html), and will inform costs (both proving time and TX fees) as well as delivery guarantees. This value must be a compile-time constant. To deliver the message to a recipient that is not the note's owner, use [`deliver_to`](../../noir_aztec/note/struct.NoteMessage.html#deliver_to) instead. #### Invalid Recipients If the note's owner is an invalid address, then a random public key is selected and message delivery continues as normal. This prevents both 'king of the hill' attacks (where a sender would otherwise fail to deliver a note to an invalid recipient) and forced privacy leaks (where an invalid recipient results in a unique transaction fingerprint, e.g. one lacking the private logs that would correspond to message delivery). `pub fn [deliver_to](#deliver_to)(self, recipient: [AztecAddress](../../protocol_types/address/aztec_address/struct.AztecAddress.html), delivery_mode: [u8](../../std/primitive.u8.html)) where Note: [NoteType](../../noir_aztec/note/note_interface/trait.NoteType.html), Note: [Packable](../../protocol_types/traits/trait.Packable.html)` Same as [`deliver`](../../noir_aztec/note/struct.NoteMessage.html#deliver), except the message gets delivered to an arbitrary `recipient` instead of the note owner. Note that `recipient` getting the message does not let them use the note, it only means that thy will know about it, including the transaction in which it was created, and prove it exists. They will also not be able to know when or if the note is used (i.e. nullified), assuming the standard note nullifier function. #### Use Cases This feature enables many design patterns that diverge in how notes are traditionally handled. For example, an institutional contract may require to have some actor receive all notes created for compliance purposes. Or a low value application like a game might deliver all notes offchain to a centralized server that then serves them via the app, bypassing the need for contract sync and improving UX. `pub fn [get_note](#get_note)(self) -> Note where Note: [NoteType](../../noir_aztec/note/note_interface/trait.NoteType.html), Note: [Packable](../../protocol_types/traits/trait.Packable.html)` Returns the note contained in the message. `pub fn [get_new_note](#get_new_note)(self) -> [NewNote](../../noir_aztec/note/lifecycle/struct.NewNote.html) where Note: [NoteType](../../noir_aztec/note/note_interface/trait.NoteType.html), Note: [Packable](../../protocol_types/traits/trait.Packable.html)` Returns the [`NewNote`](../../noir_aztec/note/lifecycle/struct.NewNote.html) container in the message. This is an advanced function, typically needed only when creating new kinds of state variables that need to create [`MaybeNoteMessage`](../../noir_aztec/note/struct.MaybeNoteMessage.html) values. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/utils/fn.compute_confirmed_note_hash_for_nullification.html # Function compute_confirmed_note_hash_for_nullification ``` pub fn compute_confirmed_note_hash_for_nullification( confirmed_note: [ConfirmedNote](../../../noir_aztec/note/struct.ConfirmedNote.html), ) -> [Field](../../../std/primitive.Field.html) ``` Returns the note hash to use when computing its nullifier. The `note_hash_for_nullification` parameter [`NoteHash::compute_nullifier`](../../../noir_aztec/note/note_interface/trait.NoteHash.html#compute_nullifier) takes depends on the note's stage, e.g. settled notes use the unique note hash, but pending notes cannot as they have no nonce. This function returns the correct note hash to use. Use [`compute_note_hash_for_nullification`](../../../noir_aztec/note/utils/fn.compute_note_hash_for_nullification.html) when computing this value in unconstrained functions. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/utils/fn.compute_note_existence_request.html # Function compute_note_existence_request ``` pub fn compute_note_existence_request( hinted_note: [HintedNote](../../../noir_aztec/note/struct.HintedNote.html), ) -> [NoteExistenceRequest](../../../noir_aztec/context/struct.NoteExistenceRequest.html) where Note: [NoteHash](../../../noir_aztec/note/note_interface/trait.NoteHash.html) ``` Returns the [`NoteExistenceRequest`](../../../noir_aztec/context/struct.NoteExistenceRequest.html) used to prove a note exists. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/utils/fn.compute_note_hash.html # Function compute_note_hash ``` pub fn compute_note_hash(storage_slot: [Field](../../../std/primitive.Field.html), data: [[Field](../../../std/primitive.Field.html); N]) -> [Field](../../../std/primitive.Field.html) ``` Computes a domain-separated note hash. Receives the `storage_slot` of the [`crate::state_vars::StateVariable`](../../../noir_aztec/state_vars/trait.StateVariable.html) that holds the note, plus any arbitrary note `data`. This typically includes randomness, owner, and domain specific values (e.g. numeric amount, address, id, etc.). Usage of this function guarantees that different state variables will never produce colliding note hashes, even if their underlying notes have different implementations. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/utils/fn.compute_note_hash_for_nullification.html # Function compute_note_hash_for_nullification ``` pub unconstrained fn compute_note_hash_for_nullification( hinted_note: [HintedNote](../../../noir_aztec/note/struct.HintedNote.html), ) -> [Field](../../../std/primitive.Field.html) where Note: [NoteHash](../../../noir_aztec/note/note_interface/trait.NoteHash.html) ``` Unconstrained variant of [`compute_confirmed_note_hash_for_nullification`](../../../noir_aztec/note/utils/fn.compute_confirmed_note_hash_for_nullification.html). --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/utils/fn.compute_note_nullifier.html # Function compute_note_nullifier ``` pub fn compute_note_nullifier( note_hash_for_nullification: [Field](../../../std/primitive.Field.html), data: [[Field](../../../std/primitive.Field.html); N], ) -> [Field](../../../std/primitive.Field.html) ``` Computes a domain-separated note nullifier. Receives the `note_hash_for_nullification` of the note (usually returned by [`compute_confirmed_note_hash_for_nullification`](../../../noir_aztec/note/utils/fn.compute_confirmed_note_hash_for_nullification.html)), plus any arbitrary note `data`. This typically includes secrets, such as the app-siloed nullifier hiding key of the note's owner. Usage of this function guarantees that different state variables will never produce colliding note nullifiers, even if their underlying notes have different implementations. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/note/utils/index.html # Module utils ## Functions - [compute_confirmed_note_hash_for_nullification](fn.compute_confirmed_note_hash_for_nullification.html)Returns the note hash to use when computing its nullifier. - [compute_note_existence_request](fn.compute_note_existence_request.html)Returns the [`NoteExistenceRequest`](../../../noir_aztec/context/struct.NoteExistenceRequest.html) used to prove a note exists. - [compute_note_hash](fn.compute_note_hash.html)Computes a domain-separated note hash. - [compute_note_hash_for_nullification](fn.compute_note_hash_for_nullification.html)Unconstrained variant of [`compute_confirmed_note_hash_for_nullification`](../../../noir_aztec/note/utils/fn.compute_confirmed_note_hash_for_nullification.html). - [compute_note_nullifier](fn.compute_note_nullifier.html)Computes a domain-separated note nullifier. --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/nullifier/index.html # Module nullifier Nullifier-related utilities. Nullifiers are one of the key primitives of private state. A nullifier is a `Field` value that is stored in one of the Aztec state trees: the nullifier tree. Only unique values can be inserted into this tree: attempting to create an already existing nullifier (a duplicate nullifier) will result in either the transaction being unprovable, invalid, or reverting, depending on exactly when the duplicate is created. Generally, nullifiers are used to prevent an action from happening more than once, or to more generally 'consume' a resource. This can include preventing re-initialization of contracts, replay attacks of signatures, repeated claims of a deposit, double-spends of received funds, etc. To achieve this, nullifiers must be computed deterministically from the resource they're consuming. For example a contract initialization nullifier might use its address, or a signature replay protection could use the signature hash. One of the key properties of nullifiers is that they can be created by private functions, resulting in transactions that do not reveal which actions they've performed. Their computation often involves a secret parameter, often derived from a nullifier hiding key (`nhk`) which prevents linking of the resource that was consumed from the nullifier. For example, it is not possible to determine which nullifier corresponds to a given note hash without knowledge of the `nhk`, and so the transactions that created the note and nullifier remain unlinked. In other words, a nullifier is (in most cases) a random-looking but deterministic record of a private, one-time action, which does not leak what action has been taken, and which preserves the property of transaction unlinkability. In some cases, nullifiers cannot be secret as knowledge of them must be public information. For example, contracts used by multiple people (like tokens) cannot have secrets in their initialization nullifiers: for users to use the contract they must prove that it has been initialized, and this requires them being able to compute the initialization nullifier. ## Nullifier Creation The low-level mechanisms to create new nullifiers are [`crate::context::PrivateContext::push_nullifier`](../../noir_aztec/context/struct.PrivateContext.html#push_nullifier) and [`crate::context::PublicContext::push_nullifier`](../../noir_aztec/context/struct.PublicContext.html#push_nullifier), but these require care and can be hard to use correctly. Higher-level abstractions exist which safely create nullifiers, such as [`crate::note::lifecycle::destroy_note`](../../noir_aztec/note/lifecycle/fn.destroy_note.html) and [`crate::state_vars::SingleUseClaim`](../../noir_aztec/state_vars/struct.SingleUseClaim.html). ## Reading Nullifiers Private functions can prove that nullifiers have been created via [`crate::context::PrivateContext::assert_nullifier_exists`](../../noir_aztec/context/struct.PrivateContext.html#assert_nullifier_exists) and [`crate::history::nullifier::assert_nullifier_existed_by`](../../noir_aztec/history/nullifier/fn.assert_nullifier_existed_by.html), but the only general mechanism to privately prove that a nullifier does not exist is to create it - which can only be done once. Public functions on the other hand can prove both nullifier existence and non-existence via [`crate::context::PublicContext::nullifier_exists_unsafe`](../../noir_aztec/context/struct.PublicContext.html#nullifier_exists_unsafe). ## Modules - [utils](utils/index.html) --- ### https://docs.aztec.network/aztec-nr-api/mainnet/noir_aztec/nullifier/utils/fn.compute_nullifier_existence_request.html # Function compute_nullifier_existence_request ``` pub fn compute_nullifier_existence_request( unsiloed_nullifier: [Field](../../../std/primitive.Field.html), contract_address: [AztecAddress](../../../protocol_types/address/aztec_address/struct.AztecAddress.html), ) -> [NullifierExistenceRequest](../../../noir_aztec/context/struct.NullifierExistenceRequest.html)