# Aztec Protocol Aztec is a privacy-first Layer 2 zkRollup solution for Ethereum that enables confidential smart contracts with both private and public state. The protocol consists of multiple components: Aztec.nr (a Noir framework for writing private smart contracts), Aztec.js (a TypeScript SDK for interacting with contracts), the Private eXecution Environment (PXE), and supporting infrastructure for zero-knowledge proof generation. Developers can write smart contracts that support private balances, encrypted transactions, and cross-chain messaging with Ethereum. The monorepo contains the complete Aztec stack including the ZK prover backend (Barretenberg), L1 Ethereum rollup contracts, Noir-based smart contract framework (aztec-nr), TypeScript client libraries (yarn-project), and comprehensive documentation. Smart contracts on Aztec can have both private functions (executed off-chain with ZK proofs) and public functions (executed on-chain by network validators), enabling hybrid state management where tokens can exist in both private and public form. ## Installation and Local Network Setup Install the Aztec toolchain and start a local development network. ```bash # Install Aztec toolchain (aztec, aztec-up, aztec-wallet) bash -i <(curl -sL https://install.aztec.network) # Start local Aztec network with Anvil (local Ethereum) aztec start --local-network # Output shows: # /\ | | # / \ ___| |_ ___ ___ # / /\ \ |_ / __/ _ \/ __| # / ____ \ / /| || __/ (__ # /_/___ \_\/___|\__\___|\___| # [INFO] Aztec Server listening on port 8080 # Import pre-funded test accounts aztec-wallet import-test-accounts # Create a new wallet account aztec-wallet create-account -a my-wallet -f test0 ``` ## Aztec CLI - Core Commands The `aztec` CLI provides commands for compiling contracts, deploying L1 contracts, managing validators, and interacting with the network. ```bash # Compile Aztec Noir contracts aztec compile # Generate TypeScript bindings from compiled artifacts aztec codegen ./target/my_contract-MyContract.json -o src/artifacts # Create a new Noir project aztec new my_project # Run contract tests aztec test # Format Noir code aztec fmt # Get current L2 block number aztec block-number --node-url http://localhost:8080 # Get node information aztec get-node-info --node-url http://localhost:8080 # Deploy L1 contracts for new network aztec deploy-l1-contracts \ --l1-rpc-urls http://localhost:8545 \ --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ --test-accounts # List example contracts available aztec example-contracts # Compute function selector aztec compute-selector "transfer(Field,Field,u128)" ``` ## Aztec Wallet CLI - Account and Contract Interaction The `aztec-wallet` CLI manages accounts, deploys contracts, and sends transactions. ```bash # Deploy a token contract aztec-wallet deploy TokenContractArtifact \ --from accounts:test0 \ --args accounts:test0 TestToken TST 18 \ -a testtoken # Output: # Contract deployed at 0x15ce68d4be65819fe9c335132f10643b725a9ebc # Contract stored in database with aliases last & testtoken # Mint tokens to public balance aztec-wallet send mint_to_public \ --from accounts:test0 \ --contract-address contracts:testtoken \ --args accounts:test0 100 # Check public balance aztec-wallet simulate balance_of_public \ --from test0 \ --contract-address testtoken \ --args accounts:test0 # Output: Simulation result: 100n # Transfer tokens to private balance aztec-wallet send transfer_to_private \ --from accounts:test0 \ --contract-address testtoken \ --args accounts:test0 25 # Check private balance aztec-wallet simulate balance_of_private \ --from test0 \ --contract-address testtoken \ --args accounts:test0 # Output: Simulation result: 25n # Transfer private tokens to another account aztec-wallet send transfer \ --from accounts:test0 \ --contract-address testtoken \ --args accounts:other_account 10 ``` ## Aztec.js - Contract Deployment Deploy smart contracts using the TypeScript SDK with generated type-safe interfaces. ```typescript import { Contract, createPXEClient, waitForPXE } from "@aztec/aztec.js"; import { getSchnorrAccount } from "@aztec/accounts/schnorr"; import { Fr, deriveMasterIncomingViewingSecretKey } from "@aztec/circuits.js"; import { TokenContract } from "./artifacts/Token.js"; // Connect to PXE const pxe = createPXEClient("http://localhost:8080"); await waitForPXE(pxe); // Create and register account const secretKey = Fr.random(); const encryptionPrivateKey = deriveMasterIncomingViewingSecretKey(secretKey); const account = getSchnorrAccount(pxe, secretKey, encryptionPrivateKey); const wallet = await account.waitSetup(); console.log(`Account address: ${wallet.getAddress()}`); // Deploy token contract const token = await TokenContract.deploy( wallet, wallet.getAddress(), // admin "TestToken", // name "TST", // symbol 18 // decimals ) .send() .deployed(); console.log(`Token deployed at: ${token.address}`); // Deploy with custom options const tokenWithSalt = await TokenContract.deploy( wallet, wallet.getAddress(), "AnotherToken", "ATK", 18 ) .send({ contractAddressSalt: Fr.random(), universalDeploy: true // Same address across networks }) .deployed(); ``` ## Aztec.js - Sending Transactions Execute contract functions that modify state with proper error handling. ```typescript import { Contract, TxStatus, NO_WAIT } from "@aztec/aztec.js"; import { TokenContract } from "./artifacts/Token.js"; // Connect to deployed contract const token = await TokenContract.at(tokenAddress, wallet); // Send a transaction and wait for mining const receipt = await token.methods .mint_to_public(wallet.getAddress(), 1000n) .send() .wait(); console.log(`Minted in block ${receipt.blockNumber}`); console.log(`Transaction fee: ${receipt.transactionFee}`); // Transfer tokens (private function) const transferReceipt = await token.methods .transfer(recipientAddress, 100n) .send() .wait(); // Send without waiting (get tx hash immediately) const sentTx = await token.methods .transfer_in_public( wallet.getAddress(), recipientAddress, 50n, 0n // authwit_nonce ) .send({ skipPublicSimulation: false }); const txHash = sentTx.getTxHash(); console.log(`Transaction submitted: ${txHash}`); // Check transaction status later const txReceipt = await pxe.getTxReceipt(txHash); if (txReceipt.status === TxStatus.SUCCESS) { console.log(`Transaction succeeded in block ${txReceipt.blockNumber}`); } else if (txReceipt.status === TxStatus.REVERTED) { console.log(`Transaction reverted: ${txReceipt.error}`); } // Batch multiple calls atomically import { BatchCall } from "@aztec/aztec.js"; const batch = new BatchCall(wallet, [ token.methods.transfer(alice, 10n).request(), token.methods.transfer(bob, 20n).request(), token.methods.transfer(charlie, 30n).request(), ]); const batchReceipt = await batch.send().wait(); console.log(`Batch completed in block ${batchReceipt.blockNumber}`); ``` ## Aztec.js - Reading Contract Data Simulate functions to read state without sending transactions. ```typescript import { TokenContract } from "./artifacts/Token.js"; const token = await TokenContract.at(tokenAddress, wallet); // Read public balance (view function) const publicBalance = await token.methods .balance_of_public(wallet.getAddress()) .simulate(); console.log(`Public balance: ${publicBalance}`); // Read private balance (utility function) const privateBalance = await token.methods .balance_of_private(wallet.getAddress()) .simulate(); console.log(`Private balance: ${privateBalance}`); // Read token metadata const name = await token.methods.public_get_name().simulate(); const symbol = await token.methods.public_get_symbol().simulate(); const decimals = await token.methods.public_get_decimals().simulate(); const totalSupply = await token.methods.total_supply().simulate(); console.log(`Token: ${name} (${symbol})`); console.log(`Decimals: ${decimals}`); console.log(`Total Supply: ${totalSupply}`); // Check if address is minter const isMinter = await token.methods .is_minter(wallet.getAddress()) .simulate(); console.log(`Is minter: ${isMinter}`); ``` ## Aztec.nr - Contract Structure Define Aztec smart contracts using the Noir programming language with the aztec-nr framework. ```rust // contract/src/main.nr use aztec::macros::aztec; #[aztec] pub contract Counter { use aztec::{ macros::{ storage::storage, functions::{external, initializer, view}, }, state_vars::{PublicMutable, Map}, protocol::address::AztecAddress, }; // Storage struct defines all contract state #[storage] struct Storage { admin: PublicMutable, counters: Map, Context>, } // Constructor - runs once during deployment #[external("public")] #[initializer] fn constructor(admin: AztecAddress) { self.storage.admin.write(admin); } // Public function - executed by network validators #[external("public")] fn increment(owner: AztecAddress) { let current = self.storage.counters.at(owner).read(); self.storage.counters.at(owner).write(current + 1); } // View function - read-only, no state changes #[external("public")] #[view] fn get_counter(owner: AztecAddress) -> u64 { self.storage.counters.at(owner).read() } } ``` ## Aztec.nr - Private State and Notes Implement private state using notes and the UTXO model for confidential balances. ```rust use aztec::macros::aztec; #[aztec] pub contract PrivateToken { use aztec::{ macros::{ storage::storage, functions::{external, initializer, internal}, events::event, }, state_vars::{Map, Owned, PublicMutable}, messages::message_delivery::MessageDelivery, protocol::address::AztecAddress, }; use uint_note::UintNote; use balance_set::BalanceSet; #[event] struct Transfer { from: AztecAddress, to: AztecAddress, amount: u128, } #[storage] struct Storage { admin: PublicMutable, // Private balances stored as notes balances: Owned, Context>, total_supply: PublicMutable, } #[external("public")] #[initializer] fn constructor(admin: AztecAddress) { self.storage.admin.write(admin); } // Private function - executed off-chain with ZK proof #[external("private")] fn transfer(to: AztecAddress, amount: u128) { let from = self.msg_sender(); // Subtract from sender's private balance (nullifies notes) self.storage.balances.at(from).sub(amount) .deliver(MessageDelivery.ONCHAIN_UNCONSTRAINED); // Add to recipient's private balance (creates new note) self.storage.balances.at(to).add(amount) .deliver(MessageDelivery.ONCHAIN_UNCONSTRAINED); // Emit encrypted event to recipient self.emit(Transfer { from, to, amount }) .deliver_to(to, MessageDelivery.ONCHAIN_UNCONSTRAINED); } // Utility function - read private state (not part of transaction) #[external("utility")] unconstrained fn balance_of(owner: AztecAddress) -> u128 { self.storage.balances.at(owner).balance_of() } } ``` ## Aztec.nr - Authentication Witnesses (AuthWit) Enable delegated authorization for third-party contract calls. ```rust use aztec::macros::aztec; #[aztec] pub contract TokenWithAuthWit { use aztec::{ authwit::auth::compute_authwit_nullifier, macros::functions::{external, authorize_once}, protocol::address::AztecAddress, }; // Authorized transfer - caller must have authwit from 'from' account #[authorize_once("from", "authwit_nonce")] #[external("public")] fn transfer_in_public( from: AztecAddress, to: AztecAddress, amount: u128, authwit_nonce: Field, ) { // Deduct from sender let from_balance = self.storage.public_balances.at(from).read() - amount; self.storage.public_balances.at(from).write(from_balance); // Add to recipient let to_balance = self.storage.public_balances.at(to).read() + amount; self.storage.public_balances.at(to).write(to_balance); } // Private authorized transfer #[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); } // Cancel an authwit to prevent its use #[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); } } ``` ## Aztec.js - Creating Authentication Witnesses Create authwits in TypeScript to authorize third-party actions. ```typescript import { computeAuthWitMessageHash, computeInnerAuthWitHash } from "@aztec/aztec.js"; import { TokenContract } from "./artifacts/Token.js"; const token = await TokenContract.at(tokenAddress, wallet); // Create an authwit for a third party to transfer tokens on your behalf const transferAction = token.methods.transfer_in_private( wallet.getAddress(), // from recipientAddress, // to 100n, // amount 0n // authwit_nonce (will be set by createAuthWit) ); // Create the authorization witness const authWit = await wallet.createAuthWit({ caller: spenderContract.address, action: transferAction, }); // Add the authwit to the PXE so it can be used await wallet.addAuthWitness(authWit); // Now the spender contract can call transfer_in_private on your behalf const receipt = await spenderContract.methods .spend_tokens(token.address, recipientAddress, 100n) .send() .wait(); // For public functions, set authwit on-chain const publicAuthAction = token.methods.transfer_in_public( wallet.getAddress(), recipientAddress, 50n, 0n ); await wallet .setPublicAuthWit({ caller: spenderAddress, action: publicAuthAction }, true) .send() .wait(); ``` ## Aztec.nr - Hybrid Public/Private State Move tokens between public and private state within the same contract. ```rust use aztec::macros::aztec; #[aztec] pub contract HybridToken { use aztec::{ macros::functions::{external, internal, only_self}, state_vars::{Map, Owned, PublicMutable}, messages::message_delivery::MessageDelivery, protocol::address::AztecAddress, }; use uint_note::{PartialUintNote, UintNote}; use balance_set::BalanceSet; #[storage] struct Storage { balances: Owned, Context>, public_balances: Map, Context>, total_supply: PublicMutable, } // Move tokens from private to public state #[external("private")] fn transfer_to_public(to: AztecAddress, amount: u128) { let from = self.msg_sender(); // Subtract from private balance self.storage.balances.at(from).sub(amount) .deliver(MessageDelivery.ONCHAIN_CONSTRAINED); // Enqueue public call to increase public balance self.enqueue_self._increase_public_balance(to, amount); } // Move tokens from public to private state #[external("private")] fn transfer_to_private(to: AztecAddress, amount: u128) { let from = self.msg_sender(); // Prepare partial note for recipient let partial_note = self.internal._prepare_private_balance_increase(to); // Enqueue public call to finalize the transfer self.enqueue_self._finalize_transfer_to_private_unsafe( from, amount, partial_note ); } #[internal("private")] fn _prepare_private_balance_increase(to: AztecAddress) -> PartialUintNote { UintNote::partial( to, self.storage.balances.get_storage_slot(), self.context, to, self.msg_sender(), ) } #[external("public")] #[only_self] fn _increase_public_balance(to: AztecAddress, amount: u128) { let balance = self.storage.public_balances.at(to).read() + amount; self.storage.public_balances.at(to).write(balance); } #[external("public")] #[only_self] fn _finalize_transfer_to_private_unsafe( from: AztecAddress, amount: u128, partial_note: PartialUintNote, ) { // Subtract from public balance let balance = self.storage.public_balances.at(from).read() - amount; self.storage.public_balances.at(from).write(balance); // Complete the partial note partial_note.complete(self.context, from, amount); } } ``` ## Nargo.toml - Contract Dependencies Configure Noir package dependencies for Aztec contracts. ```toml # contract/Nargo.toml [package] name = "my_token_contract" authors = ["Your Name"] compiler_version = ">=0.36.0" type = "contract" [dependencies] # Core Aztec framework aztec = { git = "https://github.com/AztecProtocol/aztec-packages", tag = "aztec-packages-v0.87.0", directory = "noir-projects/aztec-nr/aztec" } # Note types for private state uint_note = { git = "https://github.com/AztecProtocol/aztec-packages", tag = "aztec-packages-v0.87.0", directory = "noir-projects/aztec-nr/uint-note" } value_note = { git = "https://github.com/AztecProtocol/aztec-packages", tag = "aztec-packages-v0.87.0", directory = "noir-projects/aztec-nr/value-note" } address_note = { git = "https://github.com/AztecProtocol/aztec-packages", tag = "aztec-packages-v0.87.0", directory = "noir-projects/aztec-nr/address-note" } # Utility libraries compressed_string = { git = "https://github.com/AztecProtocol/aztec-packages", tag = "aztec-packages-v0.87.0", directory = "noir-projects/aztec-nr/compressed-string" } balance_set = { git = "https://github.com/AztecProtocol/aztec-packages", tag = "aztec-packages-v0.87.0", directory = "noir-projects/aztec-nr/balance-set" } ``` ## Node and Sequencer Configuration Start Aztec node components with custom configuration. ```bash # Start full Aztec node aztec start --node \ --l1-rpc-urls http://localhost:8545 \ --registry-address 0x5FbDB2315678afecb367f032d93F642f64180aa3 \ --data-directory ./aztec-data \ --port 8080 # Start sequencer/validator aztec start --sequencer \ --l1-rpc-urls http://localhost:8545 \ --sequencer.validatorPrivateKeys 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ --sequencer.coinbase 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 \ --sequencer.maxTxsPerBlock 32 # Start PXE (Private eXecution Environment) aztec start --pxe \ --pxe.nodeUrl http://localhost:8080 \ --pxe.proverEnabled true \ --port 8081 # Start prover node aztec start --prover-node \ --proverNode.nodeUrl http://localhost:8080 \ --proverNode.acvmBinaryPath /path/to/acvm \ --proverNode.bbBinaryPath /path/to/bb # Start with P2P networking aztec start --node \ --p2p-enabled \ --p2p.p2pPort 40400 \ --p2p.bootstrapNodes enr:-... # Environment variables alternative export ETHEREUM_HOSTS=http://localhost:8545 export REGISTRY_CONTRACT_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3 export DATA_DIRECTORY=./aztec-data export AZTEC_PORT=8080 aztec start --node ``` ## L1-L2 Token Bridging Bridge ERC20 tokens from Ethereum L1 to Aztec L2. ```bash # Bridge tokens using CLI aztec bridge-erc20 1000 0x066108a2398e3e2ff53ec4b502e4c2e778c6de91bb889de103d5b4567530d99c \ --l1-rpc-urls http://localhost:8545 \ --token 0x5FbDB2315678afecb367f032d93F642f64180aa3 \ --portal 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 \ --mint \ --private # Get L1 to L2 message witness aztec get-l1-to-l2-message-witness \ --contract-address 0x15ce68d4be65819fe9c335132f10643b725a9ebc \ --message-hash 0x1234... \ --secret 0xabcd... \ --node-url http://localhost:8080 ``` ```typescript // Bridge tokens programmatically import { TokenBridgeContract } from "./artifacts/TokenBridge.js"; const bridge = await TokenBridgeContract.at(bridgeAddress, wallet); // Deposit from L1 to L2 (public) const depositReceipt = await bridge.methods .claim_public( wallet.getAddress(), amount, secret, messageLeafIndex ) .send() .wait(); // Withdraw from L2 to L1 const withdrawReceipt = await bridge.methods .exit_to_l1_public( l1RecipientAddress, amount, callerOnL1, 0n // nonce ) .send() .wait(); ``` Aztec enables developers to build privacy-preserving applications on Ethereum through its unique combination of private and public execution. The protocol supports a wide range of use cases including private tokens, confidential DeFi applications, private voting systems, and encrypted data storage. Smart contracts can seamlessly transition assets between private notes (UTXO model) and public state (account model), providing flexibility for different privacy requirements. Integration with Aztec typically involves writing smart contracts in Noir using the aztec-nr framework, generating TypeScript bindings with the codegen tool, and interacting with contracts via Aztec.js. The local development environment provides a complete stack including an Ethereum node, deployed protocol contracts, and pre-funded test accounts, enabling rapid iteration. For production deployments, developers connect to the Aztec testnet or mainnet, use proper key management, and implement sponsored fee payment for better user experience.