Aztec Improvement Proposal 165, named in reference to EIP-165, aka
supportsInterface
standard.
Motivation
In our involvement in Aztec Hacker Residency 2025, we (Wonderland) set our sails in 2 main missions, (1) have our Token be used across different apps in the testnet, and (2) make it upgradeable, to make it easier to iterate between future versions.
The testnet has been launched a few weeks ago, and the main paradigm shift about interacting with it was (a) the persistent storage (contracts would be deployed to never be forgotten), and (b) composability between different apps. To simplify the flow for this document, we’ll reduce the goal to the following:
Mission Composability: Have Wonderland’s Token (upgradeable), be
supported in Nemi AMM’s smart contracts, and be able to prove transactions using Obsidion Wallet.
The fact that the Token is upgradeable (still to be tested) was a relief for the rush of deploying it, it was lacking public metadata getters (for name
, symbol
, and decimals
), yet they could be added later. We decided to go with the version we had, and deploy 3 tokens for the AMM and Wallet to integrate, we released an NPM package containing the deployed Contract Artifact, and a GitHub release tag, so that both Noir and Javascript codebases could import and interact with it.
Noir composability
So far, easy cake, Developer Experience seemed to be seamless, only when trying to integrate the Aztec Noir codebase to the Wallet and AMM circuits, we faced the first few problems: Expected type AztecAddress, found type AztecAddress
. We’ve been working on the latest stable version v0.85.0
, while the other teams were working with v0.85.0-alpha-testnet.2
and v0.85.0-alpha-testnet.9
respectively.
Since the Aztec version was different, imports were taken from different Nargo folders, and causing issues at compilation time. The multiple imports that the Token has were being fetched and compared against the other codebases, and failing to compile.
Javascript composability
Then came Javascript, the shared NPM package has a truthful representation of the deployed Tokens, so, no problems were expected here. Only that the Token is upgradeable, and that the circuit can change in a future. We thought that, for every version of a Token, we’d release a new NPM package, when the Token was upgraded, the Wallet would detect the new Contract Class ID, and voilá, use the correct Artifact. But then a simple question arrises: how’s the Wallet supposed to be compatible with both version 1 and version 2 of the Token? Given that one may be upgraded and the other not. Should the Wallet import each NPM package separately? Or should the NPM package be incremental and include all previous versions to disponibilize them?
Improvement Proposal
Noir interfaces
The truth is that for the AMM to import the Token Noir circuit, it doesn’t require the multiple imports that the token has:
[package]
name = "token_contract"
authors = [""]
compiler_version = ">=1.0.0"
type = "contract"
[dependencies]
aztec = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v0.85.0", directory = "noir-projects/aztec-nr/aztec" }
uint_note = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v0.85.0", directory = "noir-projects/aztec-nr/uint-note" }
balance_set = { path = "../libs/balance-set" }
authwit = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v0.85.0", directory = "noir-projects/aztec-nr/authwit" }
compressed_string = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v0.85.0", directory = "noir-projects/aztec-nr/compressed-string" }
contract_instance_deployer = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v0.85.0", directory = "noir-projects/noir-contracts/contracts/protocol/contract_instance_deployer_contract" }
Importing the whole Token with it’s Nargo.toml
file seams inefficient, and prone to problems, considering that the only thing the AMM needs, is “an interface”: it doesn’t need the whole transfer_private_to_private
circuit, it just needs to know there’s a method whose raw signature can be derived from transfer_private_to_private((Field),(Field),u128,Field)
. That’s it. The whole Token Interface could have only 1 import (#[aztec]
), and be matched with the AMM’s Nargo.toml
file:
use aztec::macros::aztec;
#[aztec]
pub contract TokenInterface {
use aztec::prelude::AztecAddress;
fn transfer_private_to_private(
_from: AztecAddress,
_to: AztecAddress,
_amount: u128,
_nonce: Field
) {} // NOTE: empty circuit!
fn transfer_private_to_public(
// ...
This setup reduces unnecessary imports (only aztec
), and it’s everything the AMM needs to compile!
Should we require to import the whole Token contract, we should make sure that for every Aztec release, we publish a GitHub tag, so that Noir developers have a compatible version with their current one.
Javascript Artifacts
Now, whenever the AMM swaps TokenV1 for TokenV2, is up to the Wallet to create the proof that involves: (1) the Wallet’s Account Contract, (2) the AMM Contract, (3) the TokenV1, and (4) the TokenV2. This is handled through a central database that links an AztecAddress -> ContractClassId
, and requires the Wallet’s PXE to have registered every ContractClassId, in order to create a valid proof for the circuit execution path.
- (1) Should be self-provided, the Wallet’s repo should have the Contract Artifact
- (2) Should be provided by the AMM (NPM package)
- (3) & (4) Should be provided by us (NPM package)
The difficulty comes when the Wallet needs to import every past version of the Token NPM Package, named token_contract-Token.json
, and figure out which Contract Class ID corresponds to. Moreover, when the upgrade from V1 to V2 happens, the Wallet should be smart enough to re-register the Token contract, with the new Artifact, before proceeding to generate a proof (else it will be invalid).
When thinking how a seamless integration would look like, the Wallet should ask the DEPLOYER_CONTRACT_ADDRESS
(a pre-compiled contract that holds record of every address and their declared latest ContractClassId
), and be able to fetch (from somewhere) the corresponding Artifact.
Different than the EVM, where the bytecode of the contracts can be fetched exclusively from the chain, in Aztec we have “bytecode commitments” onchain, to which we need the pre-image, in order to interact. Different than the EVM, where pre-image of bytecode usually can be explained with 1 to 10 (let’s say) Solidity files, Aztec contracts depend on +100 files to be tracked! #[aztec]
itself already depends on infinite interlinked dependencies, where any malicious line could result in a privacy-leaking circuit. Different than the EVM, where smart contracts (used to) have a limit of 24kb, Aztec Artifacts can weight (for example for the Token contract) more than 4mb — x100 (or more) storage requirement for each contract!
Should we release incremental NPM packages (each version publishing all the previous Artifacts), we’ll very soon end up with gigabyte-sized packages to download!
The need for a centralized source of Artifacts (as Etherscan centralizes “smart-contract verification”) rises up. But facilitating downloading circuits to be run is a security hazard. The need for social score of Artifacts appears, tagging them as “trustworthy” because they appeared in some GitHub CI action, of some self-compiled package, using some valid Aztec dependency. Audit companies may also like to tag the codebase as safe, making the Wallet aware of the security score of each downloaded Artifact, to request the User to approve running the circuit in their local PXE.
Not facilitating this availability of circuits can cause the load of composability to fall in the Wallet’s shoulders, as they’ll have to (1) track every new Token version and add it to their codebase, and (2) be aware of every Token upgrade to support the new versions.
Of course, some due diligence must be made by the User, to make sure they’re not executing a privacy-leaking circuit (i.e. that emits to me their secrets), but expecting the User to go online to fetch every possible Artifact of the circuit execution he wants to interact with is naïve, and a terrible User Experience.
Closing Comments
Let’s remember where we started off: allowing the User to interact via a Wallet, swapping TokenA for TokenB in an AMM. And both TokenA and TokenB being quite simple and isolated tokens, none was executing external calls (as we plan for the ARC-403, aka AuthToken).
This simple transaction, requires a heavy load of complexity, that this AIP aims to simplify with (a) Contract Interfaces (avoiding Noir developers the mess of maintaining package versioning), and (b) a Centralized Source of Artifacts (avoiding JS developers or Users the need to find the correct Artifact for the ContractClassID they’re interacting with).