How to Build Private Smart Contracts on Aztec for Solidity Developers
Aztec Developer Lessons · April 2026 · 5:41:02
Ciara Nightingale hosts a full course for Solidity developers getting started on Aztec. It explains how the network works, introduces Noir and Aztec.nr, and then guides learners through a first Aztec smart contract and a private token contract. The course also covers testing and deploying private smart contracts on Aztec.
Speakers
Topics
From Aztec Developer Lessons, hosted by Aztec Network.
Transcript
Introduction to the course
Hello and welcome to the most exciting course I have done so far in my career. I cannot wait to see how you guys like this course and what you guys build afterwards because this, drum roll please, is an Aztec starter course. We are going to be talking all about Aztec, how it works, why it's really awesome, why you should become an Aztec developer, and how to become an Aztec developer. And we're going to build our very first smart contract in Aztec. And then we're actually going to build a second smart contract which is going to be a lot more complex and that is a private token smart contract and it's not like an ERC20 because we have private state. Anyway, I'm going to get into all of that later. I just want to say thank you all so much for watching this. The first little section is a
little bit I want to say dry. I'm just going through what you need to know beforehand the prerex and what you're going to learn. If you would like to just skip that, please please go ahead. I will leave down below in the description if this is on YouTube, which I think it is, the different timestamps. So, feel free to skip ahead. If you're not enjoying a section or you feel like it's a little bit slow and you already know it, just skip and go at your own pace. I will not be offended. I promise. Or if you just want to have this on in the background while you're eating dinner and you just want to listen along, then please feel free to do so. Anyway, I'm going to stop rambling. I'm going to go on to the section where I'm going through some slides of what you
need to know and what we're going to learn, and then we'll get into the learning. I'll see you shortly. I am so excited for this course because it is going to teach you how to build private smart contracts. And I don't just mean private as in solidity private as in there's no automatic getter function and inheriting contracts can't access it. I mean truly private data. Do you know how long we've been waiting for this on the blockchain and now it is live? In this course, we will be learning all of the differences between Aztec and Ethereum because things do work a little bit differently because we have privacy, we have private state, we also have public state as well. So, we have to learn how to handle the two
different types of state. But if you don't know what Aztec is, also don't worry because we are going to be going through that as well. I just want to make a quick disclaimer before we get started. Everything that we cover in this course is subject to change. Now, of course, in technology, this is to be expected, but it is especially true of Aztec because currently Aztec is in its alpha phase. I will leave a link in the GitHub repo associated with this course for you to understand more about alpha and what that means for you, the developers, how to transition state from the old rollup to the new rollup. But this also means it's the perfect time for you to start learning, for you to start creating protocols and deploying them to test net. I am so excited to see
what you guys build on Aztec after watching this course. And you can think of this as like your introduction to learn all of the fundamentals that you need to know to become a developer on Aztec. And if you're not sure whether you want to be a developer on Aztec, I will also be convincing you why you should. So, who is this course for? This is for any solidity developers or Ethereum developers or maybe Salana developers who want to become Aztec smart contract developers. You need to have some fundamental knowledge of blockchains and development on blockchains, but we won't be using Solidity anyway. We're going to be using Noir and a framework called Aztec NR to build smart contracts. But it is for anyone wanting to become a developer on Aztec. If you don't know what a blockchain is, then I recommend that you
go back and watch the blockchain basics course on Updraft, which I will leave a link in the GitHub repo associated with this course. I will show you how to find that shortly. If you're watching on YouTube, there'll be a link to the GitHub repo down below. But if you want to become an Aztec smart contract developer building private smart contracts on the blockchain, then this course is for you. You need to have some basic prerequisite knowledge beforehand. So you need to have an understanding of what a zk proof is. So kind of what a circuit is, what a constraint is. If I said that a piece of code was constrained, you need to kind of understand what that means. You need to know what a witness is, witness generation, and what a proof is. Of course, you need to have intermediate
solidity knowledge. Now, I won't actually be using solidity in this course, but I'm going to be using a lot of references to solidity and calling back to it to be like, "Ah, so you know this thing in solidity that you had. Here's the comparable way of doing the same thing, but in Aztec NR." I will expect that you know what a Merkel tree is, commitments, hashing, and I'll also expect you to understand what a nullifier is and the concept of nullifying something. If you don't know what any of those things are, then please go and watch up to the end end of advanced foundry on updraft and ask your favorite AI agent.
Why should you take this course? Now, I am going to cover in a little bit more detail in the next section why you should really care about private state on the blockchain, but I just want to give you a little teaser. So, the first thing is that zero knowledge proofs are the future. We need private state. We need things to be able to be verifiable but also private. So, I don't want to reveal my balance to you, but I can prove that my balance is over some threshold. I don't want to reveal my date of birth, but I want to prove to you that I'm over 18. Second to that, Aztec is the future. Aztec is, spoiler alert again, a ZK rollup, which also has programmable privacy, so you can choose what state you want to public and private. And it's the first fully decentralized L2 on Ethereum.
Blockchains need privacy and Aztec is the first blockchain to be decentralized and also have private state. What will we learn in this course? So, we will learn about Aztec, what it is, why it's really cool, why you should care, but also how to write, test, compile, and deploy Aztec smart contracts. And before we get into all of that, I just wanted to do a little intro to myself in case you don't know who I am. My name is Kieran Nightingale and I'm a lead technical instructor. I do, as of very recently, work for Aztec. So, I should probably add that as a disclaimer. Although, I did work really hard to be able to work for Aztec. I used to work for a company called Siphon, just making these courses 24/7.
And then I decided that Aztec was what I believe to be the future. And so, I wanted to help other people be able to build applications on Aztec by working on documentation, creating videos like this one, but also working internally on the team for DevX. Pretty much anything I can get involved with to be honest. My Twitter handle or ex handle is Kira Nighting Gal. So if you have any feedback, if you build anything from this course, if you enjoyed it, please let me know by tagging me on Twitter. I would love to hear from you. I also have my own YouTube channel which is Kieran Nightingale because the actually did fit this time and I post some YouTube videos, you know, creating content on technical topics over there. So if you'd like to give me a follow, then please do
so. But no pressure, of course. A little bit about my background. I have been working in the web 3 industry for three years. No, four years now. Gosh, it's really time has flown. I started off by doing a hackathon. So, just building stuff from scratch and before that I did a physics degree. I have created over 10 hours of content on cipher and updraft. I think it's quite a lot more than that now. So, I'm pretty used to doing these video tutorials. And with all of that said, let's get started learning a little bit about Aztec and get into what actually is Aztec and how does it work and then we can start building some smart contracts and doing some coding.
Before we get started, I just wanted to have a little talk about AI tooling because we are going to be using it a little bit throughout this course, namely to finish off writing test files. And if you have any issues or bugs or whatever during your time through this course, then I really encourage you to use AI. The first thing I'm going to say is I encourage you to use claw code. You are going to be using this as part of your development journey. I'm assuming if you're a solidity developer, then you're probably already using claw code. But just anyway, it helps with just all the things it's saying here. Reads your codebase, edits files, run commands. I don't really use it to run commands so much because I prefer to do that myself.
I just don't want it to run something like get removal or something like that. And it's available in your CLI and it has the getting started here if you need to do that. So I'm assuming you already have claw code or whatever AI agent in your CLI that you choose. And then as part of Aztec we have created I say we it was the amazing Devril Josh he created a MPC server. What it does is it clones the reference repository. So, we're going to see shortly. This is Aztec packages. And this gives the AI tool all of the context it needs to be able to help you. It has all of the docs. It has all of the source code. It has all of the examples. And this MCP server has functions to be able to
scrape this code and give you the best responses. And the instructions for how to install it are here. So, it's just these two commands. You add the plugin from the marketplace and then you install the specific plugin that you need. So, this also will have as well as the NPC server additional skills, commands, and agents to make sure that the information that the AI gives you is as accurate as possible. I found that I had to close my terminal and reopen it, like start a new terminal in order for this to be able to work. So, you may need to do the same. So, if you have any questions, please ask Claude Code or whatever. and also use it to help you with writing tests because there are instances in which I'm not going to handw write all of the tests with you because this would be a waste of time
and in real life you're never going to be doing that. You're always going to be using Claude and then checking the output or starting it and then adding comments for Claude to finish things off and you need to be integrating AI as much into your workflow as possible because that is how you stay current and up to date. So yeah, that was a little note on AI. Please go to this AI tooling page if you want to read more about what we have on Aztec and Noir. onto the next lesson. I also just wanted to briefly show you some helpful resources. So the first one is this awesome Aztec repo. And in here we have links to the website, the docs, Aztec starter, which is a repository that you can clone that just helps you get started on Aztec. Writing a simple smart contract. There's the forum.
GitHub. So this is the official Aztec protocol GitHub. awesome noir which has examples and helpful links for specific noir things. This is like circuit writing. Probably don't go here because this is going to confuse you a little bit. We're not going to be writing circuits. Instead, we're going to be writing smart contracts with Aztec NNR which are written with noir, but it's you know a framework on top. Then we have some helpful resources for learning the talks and workshops. You've got some blog posts and articles and then tutorials. Now the things I specifically wanted to show you were this OTC private OTC desk example. This is a good Aztec NR and also AztecJS example. We've got the smart contract standards. In this course, we will actually be writing the token standard from scratch and it's
actually a slightly simplified version, but this is a great place to look at example smart contracts. So if we go inside source, then we can actually see all of these different example contracts. This token contract is what we will be writing except as I said simplified. Then we also have an NFT contract, an esco contract and vault contract. Then of course the other resources are going to be the course GitHub repo which I'm scrolling through here. Here you'll have all the helpful links for things that I mention, extra learning resources, any resources that I use during the course and then also of course the source code for this course.
So if you want to see what the full completed code looks like up to date to the latest version, so it will differ slightly from the actual course, then this will be here. So if you're unsure about how to update to the latest version, that will be your best source of reference. The other one I wanted to mention is Aztec packages. This is where all of the source code is, including the docs. So if you notice any errors in the docs, please PR here. And then also inside of noir projects and then Aztecr. This is where all of the code to write Aztec NR actually exists. So if you want to see how something works, maybe it's if we go inside of Aztec source and then state vase, you want to understand a little bit more about the the different
state variable types, then you can look in here. Or maybe you want to look at the different macros, which we're going to learn about shortly. The documentation in here is absolutely fantastic. So I really encourage that you come here if you are confused. And then also we of course have the documentation but inside of Aztec NNR we also have this API reference which does link to the source code but you can sort of click it directly. So let's go to macros and maybe you want functions and you want to look at external functions then there's a bit of documentation here and this is taken directly from aste packages. It's just formatting it nicely. And then also we have framework description for Aztec NR which takes you through all of the different concepts in Aztecr like state variables and
describes it in a bit more detail. I just wanted to give you a quick overview of all of the different resources in case you get stuck. Welcome to the next section of this course where I am going to teach you a little bit about Aztec and what Aztec is and how it works from a relatively high level. There are some technical things that we need to understand before we get to any smart contract development or private DAP development. So, what is Aztec? Aztec is a blockchain. Specifically, it is an L2 rollup on Ethereum. But Aztec isn't just a regular run-of-the-mill ZK rollup because it also uses ZK proofs for privacy. And this enables private state. And this is sometimes referred to as a ZKZK rollup.
What is Aztec and how does it work
So Aztec in a nutshell is an L2 rollup on Ethereum with programmable privacy. This means that you, the developer, can choose what state and what functions you want to be public and private in your smart contract. Do I want my balances for my token to be private, whereas the total supply to be public? Do you want all of the users to be able to see a liquidation threshold? Do I want people to know that I sent my mom $100 for her birthday, but my sister only $10? Probably not. Aztec is the first and currently only decentralized L2 roll up And it's also the first stage 2 rollup.
That's pretty freaking cool, right? I mean, at least I think it is. Maybe I'm just a nerd, but I assume that you watching this video are also a fellow nerd, so probably think this is cool, too. Hence why you're here. Before we get into Aztec and the Aztec stack, how it works, how this public and private state are managed, how things are just different from the Ethereum world, and therefore how you're going to be able to build smart contracts. Spoiler alert, it's not with Solidity. I'm first going to let you watch this video that I made quite a while back now on what zero knowledge proofs actually are. And the reason for this is that I really want you to understand the power of zero knowledge proofs, why this is so important, but also what they actually are. Because you need that information front of mind when you are developing
applications that have privacy and then hopefully you'll understand why Aztec is fundamental for the future of the blockchain world. Sounds pretty dramatic, but in my opinion, that's true. And therefore why you should learn to be a developer on Aztec. And with AI, this is now easier than ever. And I do encourage you, as I said earlier, to use AI tools where you can to accelerate and help your learning. So if you don't understand anything, please just ask AI the questions. Obviously, feel free to ask me, too. But anyway, I digress. I'll let you now watch this video on what are zero knowledge proofs.
What if I could tell you that I could prove I could solve a Rubik's cube without showing you the solved cube? Impossible. Not with zero knowledge proofs. They are the mathematical equivalent of proving you know a secret without actually having to reveal it. >> Wow. Welcome to our journey into zero knowledge proofs. Today we are going to be diving into in my opinion one of cryptography's most interesting topics. So whether you're interested in blockchain privacy, scalable computation, or just love elegant maths and cryptography like I do, then gather around and welcome. zero knowledge proofs are transforming how we think about privacy and verification and by the end you will understand what they
are from a high level and why I think they're really really cool. Let's start with a very simple scenario. Imagine you have two friends. Let's call them as we always do Alice and Bob. Alice has discovered that she knows the solution to Bob's very complex puzzle and she wants to convince Bob that she's solved it. However, she doesn't want to reveal her solution because anyone watching or Bob himself might claim the solution as their own. And this is where zero knowledge proofs come in. They allow Alice, who we call the prover, to convince Bob, who we call the verifier, that Alice in fact knows the solution.
and crucially without revealing any information about the solution itself. The magic here is that Bob can be 100% convinced that Alice does know the correct answer but gains zero additional knowledge about what the solution actually is. To create a little definition, a zero knowledge proof or ZKP is a cryptographic method that allows one party who we call the prover to convince another party called the verifier that they know some specific information without revealing that information itself. Let's give a real world example. Let's say you are over 18 or over 21 and you want to enter a club.
In order to prove your age, you would have to reveal your date of birth. What if there was a way to prove that you are over 18 or 21 without revealing your age or date of birth, without having to hand over any ID? In order for a zero knowledge proof to be valid, they must satisfy three fundamental properties. And we're going to go into these in a little bit more depth in a different video, but I'm going to summarize them now from a high level. The first one is completeness. If the statement is true, an honest prover must be able to convince the verifier. The second one is soundness. If the statement is false, no dishonest prover can convince an honest verifier except with maybe a teeny weeny tiny probability, but we're going to ignore that for now. And third, and
finally, of course, it must be zero knowledge. The verifier or anybody else for that matter must learn nothing except that the statement is true. There's a lot of different terminology that we're going to go into in a different video, but I just wanted to take you through a few of the use cases. Since blockchains are inherently transparent, we need a way to also include privacy of our data and information such as wallet balances, we need to enable scalability without compromising on security. Enter rollups and so much more. Zero knowledge proofs might sound like magic, and they kind of are, but they're based on solid mathematics and cryptography. They provide a system in which we can have blockchain systems, which also have an element of privacy and verification.
Before we continue, I think it's important to define some terminology that's going to be used throughout this course and also future ZK courses on updraft. So, if you're going out into the wild to do your own research on ZK proofs, then you'll understand what these terms mean. I think it's important to point out here that although I am going to define these terms, not everyone uses these keywords in exactly the same way. Sometimes it depends a little bit on the context and there is some disagreement on the exact definitions. So, in some circumstances there may be a little bit of variation. you're not going to find full consistency everywhere. Regardless, it's still important to understand what these key words mean before you see them being used out in the wild and in order to understand what they mean depending on the different contexts. So, the first
key word is claim or statement. And often these words are used interchangeably. A claim or statement is an assertion that something is true. In the context of zero knowledge proofs or ZKPs, it refers to the property being proven without revealing any additional information. An example of such statement or claim is I know a number x such that x ^ of 2 equals to 9. This is the statement or claim that the prover wants to prove to the verifier without revealing x. Another example could be I am over some minimum age. Therefore I can enter your adults only club. The prover needs to convince the verifier that they are over the minimum age without revealing their age or date of birth. So to summarize the statement or
claim is the thing that the prover is proving to the verifier that they know without revealing any additional information about what it is that they know. Next up we have private and public inputs. An input is like a variable to your zero knowledge proof system. This is going to make a bit more sense as we go on so bear with me for a second. So in a zero knowledge proof system inputs can be categorized two ways. First of all private inputs. These are values in the system that are only known to the prover and not the verifier or anybody else. For example, passwords or secret keys. And these are part of something known as the witness, which we're going to define shortly. This is as usual most easily understood through an example.
Let's return to our original examples. In the statement, I know X such that X^ 2 equals to 9. X is an example of a private input. Or for the claim, I am above some certain age and therefore I can enter the club. Your age is an example of a private input. The second type of input is a public input. And as this sounds, this is a value that's known by both the prover and the verifier, and it's used to check the proof's validity. This can include things like commitments, a public key, or a hash digest. But again, this is best explained using an example. The first example doesn't have a public input, but the second example could. I am above the threshold age, and therefore I can enter your club. The public input could in fact be the minimum age, 18 or 21, etc. This could
be an example of a public input that's verifier. The next key word is a constraint. This is a mathematical equation or condition that must be satisfied for the claim to be true. The constraints define the rules the inputs must follow to be valid. Exactly how they expressed depends on the specific constraint system. I'm going to touch on this from a very high level, but don't worry too much about proving and constraint systems. Again, let's return to our examples. So for the claim I know x such that x ^ of 2 equals to 9. The constraint could be assert that x ^ of 2 equals to 9. The specific structure of this constraint depends on the specific proving system. For example, in an R1 CS proving system, this could be broken
down into intermediate steps where we first say that x * x equals to an intermediate value zed and then zus 9 must be equal to zero. And this specific structure is used in the R1 CS proof system. And this is exactly the same as saying that x to the^ of 2 equals to 9, but it's just broken down into an intermediate step. Don't worry about this though because in most instances when we are writing constraints, this is abstracted away. So you don't need to know too much about this. I just wanted to touch on it very briefly. Let's return to our second example. I am over the minimum age and therefore I can enter the adults only club. The only constraint would be that we would require that the age is greater than or equal to the minimum age. So there are multiple types of constraint systems
that dictate how these mathematical rules and assumptions are structured like plonkish constraint systems or R1Cs but most of the time these differences are abstracted away. For example, if you're writing your zero knowledge proof program using a language like noir or circum next keyword is circuit which you might have heard already in the context of zero knowledge proofs. But what actually is a circuit? A circuit is a system of these constraints. So it's represented as a series of mathematical relationships and operations. The circuit defines how all of these constraints work together to verify the claim. So all of the constraints together need to pass to convince the verifier. Let's return to our second example. So for our first example, we could only have one constraint. So therefore, our circuit is in fact just
this one constraint. In our second example, I am over the minimum age. Therefore, I can enter the club. Perhaps we also want to have a maximum age. I am over this minimum age but below some maximum age. Therefore, we would have two different constraints. These two different constraints together would make up the circuit in this example. The next key word which we've mentioned already very briefly is a witness, which again you might have seen out in the wild when reading up on zero knowledge proofs. A witness is the set of secret values that allow a prover to demonstrate that a statement is true. It does include all of the private inputs, but it could also include intermediate values computed during proof generation, such as all of the information that the prover needs in order to prove that their initial statement is true. The
witness must satisfy the constraints of the circuit to ensure that the claim is valid. In some instances, such in Vitalik's QAP's blog, the witness refers to the assignments of the variables in the circuit. For example, for the claim I know X, such as this X the^ of 2= to 9, the witness could be X, but in some examples, it could include intermediate calculations such as X ^ of 2. Finally, now that we have defined all these other terms, let's redefine the prover and the verifier using this terminology so that we get used to things. So the prover as we said before is the entity that generates a proof by demonstrating knowledge of the witness while satisfying the circuit constraints. The prover does not reveal the witness but still convinces the verifier of proof of computation and this enables them to
prove that the statement was initially correct. So they can prove that their inputs satisfied the constraints and then they can prove that this computation was successfully performed. The verifier checks whether the proof is valid. Given the proof and inputs, the verifier runs a verification algorithm to confirm whether the prover's claim is true without revealing any additional information. This verification could be done offchain or a verification smart contract could be generated in order to verify the proof onchain by anyone. So that was just a whistle stop tour on different ZK terminology. If you found any of this confusing, don't worry too much. Just make sure that you keep reading blogs, keep watching videos, re-watch this video in order to get familiar with this terminology because
it is difficult. There's a lot of nuance here. So, the more you surround yourself with the information, the easier you're going to find things. Aztec is very different from other EVM chains because as well as having public state, it also has private state. So, first of all, why do we need privacy? Now, if you haven't read Zach's privacy manifesto, I really recommend that you do so if you need convincing as to why we actually need privacy with blockchain technology. But the TLDDR is this. Blockchains are great for many reasons. They are of course decentralized. Well, most of them are. They're credibly neutral, trust minimized, permissionless, but they are also transparent, which is a good thing because it means that no one can lie and
everyone can see everything. And it also means that we can verify things like balances. You can check whether someone has some minimum amount of balance to be able to participate in your protocol. However, there are instances and lots of them where we need and want our data to be private. Particularly big institutions, they don't particularly want to be sharing all of their data to everyone in the whole world. And also for you the individuals, you also want your data to be private. I don't particularly want everyone to be able to see what I invest in, how much money I have, and everything about my onchain activity. Not very good for offset. So what if we can combine worlds where we have state that is both private, so you own your own data and no one else can see it, but also verifiable. For example, I can prove that I do have
enough money to buy those tokens. I can prove that I do have that access pass NFT without actually revealing my balance or any other information. I want to be able to buy those tokens, decrement my balance, verify all of the state transitions without revealing anything about any of the execution or any of my data. And this is exactly what Aztec achieves. Programmable privacy. So you the developers have the ability to choose what state is public and private. What functions are executed in public and what functions are executed in private. This of course means that developing on Aztec is pretty different from developing on Ethereum. And this is because of that addition of private state and private functions.
Let's go into a little bit more about public and private state as they work quite differently from each other. The first call out I want to make to avoid some confusion is a big mistake that solidity developers make at the start of their journey and that I personally also made which is that assuming that Ethereum already has public and private state. We obviously have those visibility specifiers public and private that we can prepend to our state variables. But we now know this does not mean that private state variables cannot be read by blockchain observers. Private just means that other smart contracts cannot read that state variable including inheriting contracts. Public means that you automatically get that get a function that other contracts can call. These specifiers control the
visibility between smart contracts, not between the blockchain and end users. Anyone can of course come along and analyze the contract storage to obtain all of the values even if they're private. Aztec however has of course private state and this is private with respect to the end users. The private estate is cryptographically hidden from everyone except the owner and of course anyone they choose to share their data with. Whilst public state is visible to everyone. It's transparent. And these two types of state so public and private state are managed using two different state models. Public state is accountbased. So it can be directly updated and read by anyone. So public state works pretty much exactly the same as it does on Ethereum. Whenever data,
for example, a balance is stored, a key value pair is created. For example, some owner's address and their corresponding balance. When the data is modified, the value corresponding to the key is updated and this accountbased state is updated directly when the transaction is executed on chain. Public state is stored on Aztec in the public state Merkel tree. Private state is not account-based and it's where things get a little bit wacky. If you do get confused in this section, please please please visit the documentation and do some reading around the subject. It does take a little bit of time for you to really understand what's going on here and to get a mental model of how private state works and then how it connects to
the blockchain etc. So firstly to keep things private private functions cannot be executed on chain and private state cannot be stored directly on chain because otherwise of course everyone would be able to see it and know what happened. Instead private functions are executed locally and private state is stored locally on the user's device and then a zero knowledge proof of the execution is sent to the sequences and verified onchain. Private state is not accountbased. To understand why and really see the problem, let's assume for a second that it is. Let's say we have an account-based state model for private state and instead we're just encrypting the data. So we have some encrypted address that maps to some encrypted balance and maybe only the user can
decrypt it. When we send a transaction, this value in this storage slot will be modified directly. So even if the key and the value are encrypted, the data location is [clears throat] leaked using correlation techniques. This can of course expose information. For example, we could link a user to their balance location and this breaks privacy assumptions. So instead, private state uses a UTXO based model a little bit like Bitcoin and uses something called notes. We're going to dive into notes very shortly, but let's have a look at UTXOs. The UTXO based model is very similar to physical cash. When you have cash in your wallet, you never actually store a total balance. Instead, you have
individual bills, maybe a $10 bill and a $20 bill. If you wanted to pay someone $15, you can't split your $20 bill in half or update the amount. Instead, you have to hand over both bills and then the person receiving it would have to give you $5 back as your change. And Aztec notes work a little bit similarly. Instead of having a total balance of 30 tokens, instead you might have one note containing 20 tokens and another note containing 10 tokens. So what are these pesky little notes? Notes are just private data. They're not particularly complicated actually. They are just a strct containing some data, eg some note owner and maybe the value of that note
and then some methods to do things like create a commitment and create a nullifier which we're going to come back to shortly. For example, this is one of Aztec's notes called the uint note. So you can see here that you have the strct and then down here we have some methods on this note. So notes are literally just private data and they are stored locally on the owner of that note's device. So the user's device and they are never stored on chain in their raw form. Instead we create a commitment of this data. So a hash of the data with some randomness to prevent brute force attacks is stored in something called the note hash Merkel tree. The key thing is that notes cannot be modified. So the note hash tree is append only. We cannot
delete notes from the tree and we cannot change them in the same way that you can't edit the value of a $10 bill. It's just $10. Notes are either spent or what we call consumed or unspent. Let's say Alice has a note with a value of 10 tokens and another note with a value of 20 tokens. Let's say she wants to send Bob 25 tokens. She would have to mark her two notes as spent or consumed and then create two new notes. One note for Bob with a value of 25 tokens, the amount she wanted to send, and then another new note for herself with five tokens representing her remainder. But what does it mean to consume a note? How
do we mark it as spent? We can't remove it from the note hash tree because it's append only. How do we know if notes in the tree are spent or unspent? This is where nullifiers come in. A nullifier is a deterministic piece of data derived from the note that it corresponds to and it can only be calculated by the note owner. And presence of this nullifier marks a note as spent. And these nullifiers prevent double spending because if the nullifier is present, it means the note is spent. Therefore, it cannot be spent again. And nullifiers are stored in yet another Merkel tree called, of course, the nullifier Merkel tree. So, let's go back to Alice and Bob. So, Alice wants to send Bob 25 tokens. She consumes her two notes, her
20 token note and her 10 token note by first proving that she has a valid commitment in the note hash tree by proving knowledge of the pre-image which when hashed creates a valid commitment in the note hash tree. And then she creates two nullifiers, one for each note, which are then added to the nullifier tree to mark these notes as spent. She then of course creates two new notes, one for Bob and one for herself as the remainder, which she can then spend at a later date. But how does she share this 25 token note with Bob without posting the raw data on chain?
He needs access to that raw data to be able to spend his note at a later date. This data can be shared in a number of different ways. She could share the raw note data offchain with Bob maybe by email or WhatsApp QR code carrier pigeon or she can use something called note discovery. This is out of scope of this video as it's a little bit complicated but basically it's a way for Alice to let Bob know about his note onchain without revealing the raw note data with anyone else. Please visit the Aztec documentation if you'd like to understand more about note discovery.
And that is pretty much it. So public state is accountbased and works pretty much the same as Ethereum. Public functions are executed onchain and public state is stored onchain. Private state is UTXO based and uses the concept of notes. Raw notes are stored offchain locally. Note commitments are stored onchain in the note hash tree which is an appendon Merkel tree. If a note is spent or consumed, a nullifier corresponding to that note is stored in the nullifier tree. private functions are executed offchain and then ZK proofs of the execution are verified onchain to make sure that all state transitions are correct. So it's great understanding all of the theory about the Aztec protocol, but what about Aztec smart contracts?
Aztec smart contracts have to deal of course with private state, public state, private functions, public functions. They have to be able to create zk proofs of the execution and so many more other complexities. Therefore, of course, they cannot be written using solidity. Instead, they are written in noir and using a framework called Aztec Noir. Aztec Noir just provides some helpful functions and wrappers to be able to make it easier to handle notes, private state, creating nullifiers, creating commitments, and so much more. Let's talk very quickly about execution environments because I'm going to use some terminology even if I've already covered this. I just want to go over it very quickly. Again, private state and private execution happens in private
land. This is on your local device in something called your Pixie, which stands for private execution environment. This is literally just a JavaScript library which has all of the functions, all of the methods, all of the the database to store your notes, to operate on your notes, to execute private functions, to store all of your keys for your account, etc. And that is private land. exists offchain completely on your local machine. In public land, everything is executed by the sequencer. So, first we have to apply any side effects from the private portion of the transaction. This is things like appending new note hashes or nullifiers to the Merkel trees. Then it will execute any public function calls
through the AVM, the Aztec virtual machine. And it will update any public state via the public state tree. So everything public is handled on the sequencer. and they are the two execution environments. Very quick whistle stop tour. If you want a reminder of any terminology, I also just want to remind you that there is a glossery in the Aztec documentation which has all of these terms. So if you forget and you're like, "Oh, Kira, just stop speaking all of these words that I don't know." It's going to be there. Gas on Aztec is called mana. Gas and mana have exactly the same meaning. They are just a measure of computational resources. So how much resource it takes to execute some computation or store some data. So if we're talking about
computational resources on Ethereum of course we will refer to it as gas. And if we are talking about computational resources on Aztec we will refer to it as mana. Now in the codebase and historically we have also referred to mana as gas on Aztec but because they have the same meanings whenever you hear mana just substitute in your mind for gas. They have exactly the same meaning. Fees on Aztec are also paid a little bit differently than on Ethereum. And this is because on Aztec there are no native tokens. You don't have Aztec ETH. Instead, fees are paid with something called Feed Juice. Now, I know this is a really strange name. And initially when I heard the name, I actually did talk to the team and I was like, hey, I I think we should rename Feed Juice. But I have
come round to it and I will explain why it is called Feed Juice. And maybe hopefully you will also agree with the naming. In fact, it's just kind of silly. And a lot of the naming in Aztec is funny and silly. For instance, the prover is called Baritenberg, named after the cake Battenburg. Just very silly silly, but kind of sweet. So, Feed Juice is actually Aztec tokens on Ethereum that have been bridged to Aztec. Okay, so why not just call them Aztec tokens then? And that's because Feed Juice is different. It's pretty special. They're not really tokens because once you've bridged them, they're non-transferable. You can't send your fee juice balance to someone else or even bridge it back. And crucially, you can only use fee juice to pay for one thing, fees. So, we could call them
fee tokens, but they don't really act like a token at all. Hence, juice. You got to juice her up, baby. You use your fee juice to pay for mana to be able to send your transactions. The same as you have to pay gas fees to pay for your gas to send your transaction on Ethereum. It's all exactly the same. Now, the amount of mana that you consume to send a transaction and the calculation for how the fee juice is paid is a little bit complex and actually subject to change at the moment of recording this video. So, I'm not actually going to go into that in this video, but I will leave some resources linked in the if you would like to see how those numbers are calculated. But essentially, it works very similarly to EIP1559
type 2 transactions on Ethereum. It follows a similarish format. In fact, the format was inspired by type two transactions. So, you have some kind of base fee and then a priority fee to get your transaction included earlier. And the amount of gas you use depends on two variables. The first is how much computation you're doing in public, so on the node itself. This will not include any private functions that you're executing because they are of course executed locally in your Pixie on your local machine. And secondly, data availability. You need to pay for your blobs for your data to be available on Ethereum. And this will increase when you're doing things like adding to the Merkel trees, adding note hashes, adding nullifiers, adding public state, L1 to L2 messages. I can't remember any of the
other trees. I've got one more archive tree of course. And then those updated routes are then submitted to Ethereum and are made available via blobs. So you need to pay for that. I will also leave in the GitHub repo associated with this course all of the costs so that you can look through those if you're interested. Particularly if you're doing something like optimizing a smart contract, you may be interested in these numbers. But just remember if you're modifying any public state, it'll cost mana. Reading state in private actually costs mana because of course you have to nullify the note just to read it, which will mean that you're updating the nullifier tree and also the note hash tree.
Modifying private state of course costs mana for exactly the same reason. any kind of simulation on the sequencer costs manner because you have to deal with side effects and as I said DA on L1 costs manner. So for all of these details specifically how the calculations are done etc if you're interested are linked in the GitHub repo associated with this course. I know this was a super whistle stop just on fees and mana on Aztec. All you need to really take away is gas is the same as mana and rather than fees being paid in native tokens, fees are paid in fee juice which is bridged Aztec tokens from L1 to L2 which cannot be transferred and only used to pay for fees and there is no native token on Aztec. That's it.
Okie dokie art. Oh my god, it's been so long since I've said that. In this lesson, we are going to be going through accounts on Aztec because they are really, really cool and I've been so excited to record this video and talk all about accounts just because I just really like them. I think they're real neat. So, we're going to be going through what are Aztec accounts, how do they work, and how they're different from anything you've really seen on Ethereum before. And we're actually going to be looking at the code for an account contract. How an account contract actually works is super open-ended for the developer, which has been intentional to keep things as least to keep things least opinionated as possible. But Aztec does have some out ofthe-box accounts for you guys to
deploy and also use to learn how accounts work. I will be trying to be as clear as possible about what is an opinionated implementation versus what you actually do have to do for the protocol throughout this video. The other thing I want to note is that you will find the code confusing because it is written in noir. It's a Noir smart contract. We have yet to look at any of those. So, this is going to be your first look at a Noir smart contract. Yay. So, I do not expect you to understand how it works. I will just be highlighting the blocks of code and being like, "This is kind of what this bit does. This is kind of what this bit does." Do not worry if you get lost. I don't really care if at all if you understand the code for the account
smart contracts, and I will be clear about what I want you to take away from this video. Nonetheless, I think it is good to have a little look at some code before you even start learning because the more you see code, the easier it will become. Anyway, I shall stop rambling now cuz I know I do it a lot. And let's get into Aztec accounts. I am assuming of course you are a Solidity developer and you are familiar with account abstraction on Ethereum. If you're not, I will leave linked in the a video from Patrick Collins going through ERC 4337 and account abstraction. This video was in the advanced boundary course on updraft, but it is also on YouTube. Aztec takes this concept of account abstraction and has
built it directly into the protocol. And that has major implications for how things like authentication, authorization, and privacy work. But before we get into Aztec, let's just do a very very quick recap of account abstraction and how smart accounts work on Ethereum. On Ethereum, you have two types of accounts. I'm going to go through this quick. You have externally owned accounts or EOAs and account contracts or smart accounts. EOAs are controlled by private keys that are used to sign via ECDSA signatures and that's it. If you have access to the private key, you have access to the account.
There's no custom logic. There's no flexibility. If you want to create a multi-IG, have social recovery, session keys, spending limits, etc., you need to have a smart account. A smart account is where the account is itself a smart contract. The logic is written in code. And ERC 4337 is the standard approach. You deploy a smart contract the account which will have a function called validate user op that will define your authentication logic. And instead of submitting transactions directly to the memp pool, you have an altmeool. So like another layer on top of Ethereum with rather than having validators, you have something called bundlers. And you don't submit transactions, you submit user operations. Bundlers will pick up these user operations from the altmen poolool, validate them, and then submit them to a
singleton smart contract called the entry point. This all works great, but you're kind of bolting account abstraction on top of an existing protocol that wasn't actually designed for it. And all of the validation logic runs onchain, which means that all the nodes need to re-execute the logic. And this limits how complex it can be and can cause DOS issues. On Aztec, there are no EOAs. Every single account is a smart contract. This isn't an opt-in layer like ERC4337. It is the protocol. Account abstraction is native on Aztec. And these account contracts can be built literally however you like. It is intentionally open to the developer to implement the logic however you choose. However, as I said,
Aztec of course has some opinionated implementations that makes things a little bit easier for the developers to have some out ofthe-box options. Also, you do need to have certain things in order to be compatible with other parts of the stack like AztecJS, which is a JavaScript package making it easy to interact with Aztec. So, you'd have to kind of implement the front-end side yourself from scratch if you did things differently. Anyway, Aztec having native account abstraction has a few implications. First, there is no entry point singleton contract. Instead, every transaction starts with calling a private function on your account contract to make it interoperable with the JS side of the stack. This function should be called entry point. And this
function is the transaction gateway. It validates the transaction, handles the fee payment in feed juice and then dispatches the actual function calls to be executed either via the pixie so locally for private functions or via the node for public functions. Secondly, all authentication logic runs on the user's device via the pixie. This is of course because the entry point function is private. Therefore, it executes locally. Then a zero knowledge proof of the execution is created and then that proof is sent to the sequencer for verification. The secret then just has to verify the proof. They never see the authentication keys or whatever is used.
They never have to re-execute the orth logic and the verification cost is constant regardless of how complex the orth logic is. This means you could have something like a multi-IG which requires two out of three signatures, time locked social recovery, biometric verification, literally anything you can imagine and create a verifier for and it will cost the same no matter what. Third, this solves the DOS problem on Ethereum. Of course, if validation logic is too expensive, a malicious user could submit invalid user ops and waste bundle of resources on Aztec. Of course, because the Pixie generates this fixed size proof which is then verified by the sequencer, the sequences cost is constant and predictable and invalid proofs are cheap to reject. And the
fourth thing is that account addresses are predictable. They are deterministic. A contract's address, so this means any contract on Aztec in fact is derived from its public keys and contract code before deployment. And this means that you can receive funds at an account contract address before the account contract has even been deployed. This is something we call counterfactual deployment. It's similar to create two on Ethereum, but it's default for every account. Notably, you also don't even have to actually deploy a contract on chain. You could also just initialize it which is where you kind of call its constructor which can only be do done once and then the contract exists purely in private. This is a little bit advanced so I don't expect you to have
your head around that right now. We are going to go over this concept again. Now let's take a look at the actual code. So the key takeaways for this lesson again don't worry if you didn't understand any of that code is this. Every account on Aztec is a smart contract. There are no EOAs. Authentication runs client side in the pixie in private because the entry point function, the function which is called when you execute a transaction has to be private. Therefore, the sequencer only has to verify a fixed size proof of the execution of this private function which does all the authentication logic. The third thing is the opinionated accounts on Aztec have three main functions. The entry point function which is your transaction gateway, verify private orwit which verifies third-party authorization. So
you want someone else to be able to execute an action using your account and is valid impulse which is where you define the custom orth logic. Fourth, everything else is implemented in account actions. So it's all done for you unless you want to modify anything you choose. Fifth, you can use any or scheme you like. So this could be ECDSA or anything else you can write a verifier for in Noir. And finally, complex orth is free for the sequencer. All they need to do is verify the proof. Okay, let's look at an example. I'm looking inside of Noir projects. Noir contracts contract account ECDSA K account contract and then in this contract you'll see I'm going to skip most of the code because it's a little
bit confusing as you don't know Aztecr yet. Here this is just the storage and in storage we are saving the signing public key. So this is the expected public key that we need to verify signatures using. Then we have the constructor to just set this key. Then we have that entry point contract and in here this is a little bit confusing but inside here is where we are actually calling out to is valid impulse. This is basically just setting that function as the function for this actions to call inside of actions entry point. Don't worry about it but yeah your calling is valid input inside of here. We then have the verify private orwit function. We're not going to talk about orwitz just yet.
We're going to talk about that in the later section when we write the token contract from scratch because authorit are a little bit confusing. And then finally, we have the is valid impul function which will take the signature. It will take the expected public key. I'm going to skip all of that confusing stuff. But then down here, we actually verify the signature. So we take the expected public key, the X part and the Y part. We then take the signature, the hashed message and check like normal like we do in Solidity. We check whether the signature is valid. And that's it. Thank you for watching. In the next Aztec wallets. So, do we have MetaMask on Aztec? Spoiler alert, at the moment, no. And wallets actually work a little bit differently cuz we don't just have
one set of keys. We actually have multiple. And the wallet needs to talk to the account. It needs to talk to the pixie. It needs to talk to the sequencer. And how all of those things intermingle together. So, we're also going to be talking through the transaction flow. What is an Aztec wallet? On Ethereum, we think of a wallet as, you know, like MetaMask or Rainbow. Something that holds and manages your keys, makes it easy for you to sign transactions, and helps you send them off to the network. On Aztec, a wallet does all of that, plus some extra stuff needed for the privacy element. It also incorporates the Pixie. So it is also a private execution environment, a ZK prover, an encrypted database storing all of your
notes, so all of your private data. And it enables you to discover new notes sent from other people. This is called note discovery. Allows you to send transactions with multiple transactions nested in together and handle things that have no EVM equivalent. In this lesson, we are going to go into Aztec wallets, a little bit about how they work and also the transaction flow. So, what happens when you send your transaction via a wallet, how does it interact with the Pixie, the network, your account, and so on. On Ethereum, the transaction life cycle is pretty simple. You have a wallet, so MetaMask, Ruby, Rainbow, whatever. This wallet manages your private key. When you want to interact with a contract, your wallet allows you to sign a transaction using your private key and send it to the
network to call that function on the contract. The node then adds the transaction request to the mempool. A validator will pick it up, execute it, and then include it in a block. And every node will re-execute the transaction to verify it. Your private key never leaves your device. So for instance in metam mask but everything else so the function you call the arguments the state changes all of that is public on Aztec the life cycle is pretty different and this is of course because of privacy. Private functions are of course not executed on the node itself because the network doesn't have access to your private data. So it can't read it and it can't write to it.
Therefore these private functions are executed locally in your pixie as we know. And the wallet is actually what calls out to the pixie. The Pixie is just a library, a JavaScript library that enables you to do these things, manage the database of notes, modify those notes, execute private functions, hold your private keys as notes inside of your database, and so on. You then prove the private execution, so all of the order of private functions that you need in your transaction. You can prove the validity of that execution using a zero knowledge proof, which you then send the proof to the network. Also, some other transaction effects. We call these the side effects are also sent with the proof. This includes any changes to any of the five Merkel trees.
So if you've updated any of the notes in your database or added nullifiers if you've consumed notes and any incued public calls that you want to make which are executed of course on the node. And all of this means that an Aztec wallet needs to do a lot more orchestration than an Ethereum wallet. And so it's a little bit more complicated. Let's break down the key components. So first of all we have the app. This is your application that you are interacting with. Let's say it's a decentralized exchange or maybe it's a token contract, I don't know. Then you have your wallet and this will have some kind of front-end component. And then it will also integrate with the Pixie. The Pixie is as I said a JavaScript library that allows you to do your private execution and manage your private data. So a
database of your notes. It also will manage your keys and your accounts. So your account is your smart contract with a corresponding TypeScript part that allows you to create authentication objects, usually signatures, but this could be Face ID or something else. So the wallet will talk to the Pixie, the node and the Aztec account. The account is what you call to send a transaction and will create authentication objects NTS and validate those signatures as well. We're going to be going through this flow in a little bit more detail shortly. This is how the flow of a transaction looks. So here you can see the user will initially connect their wallet. They'll for instance be interacting with a token contract. They input the amount of tokens in the front end that they want to transfer or
whatever and then they click the button. This will kick off a function in Typescript in Aztec.js called send. This is implemented in the front end for the application itself. They will type in their application send which will allow the app to talk to the wallet. The wallet will then take this transaction request and forward it on to the pixie which will then call to the account to create a signature and app payload. So the app payload will include this signature as part of the strct. And this is just all of the actions that you want to execute how you're going to pay for fees and the authentication object. It will then call the entry point function
on the account contract using this app payload. This entry point function will then call out at some point via an oracle to the pixie to execute the private function and it will modify the context object with any side effects. So the context object is just all of the data that has been modified. So all the side effects and the message sender things like that. It's just all of the private context needed to be forwarded on to the node. The pixie will then call back to the wallet once it has done all of the execution and the wallet will then orchestrate the proof to be created via the pixie. So the pixie will then prove valid or and private transfer by creating a zero knowledge proof. It will
then send that back to the wallet which can then call node sendtx to send the transaction to the node to validate the proof, execute the public functions, prove those public functions and also apply any side effects. So adding to the Merkel trees, it will then send this transaction to Ethereum and then on the L1 the rollup proof will be validated. This is like normal ZK rollup stuff and the new state route will be recorded in a blob. So this is the data availability part. The L1 inclusion will then bubble up back to the wallet to say okay everything is finalish and the wallet can send back a transaction hash and receipt. And that's pretty much it. The wallet is just the orchestrator of all
of the different parts. So the account which is both a smart contract and some TypeScript logic for creating signatures or authentication objects. It will talk to the Pixie for getting back the executed private function, creating the proof and then it will also talk to the node. And that's pretty much all of a wallet is. So it needs to understand what the account looks like. So what the entry point function signature is. It needs to know how to talk to the Pixie. It is probably going to be implemented using the wallet SDK which was created by Aztec.js. But as long as you are both the account creator and the wallet creator, you have complete freedom if you want to use Aztec.js and the wallet SDK. Obviously Aztec have made some opinionated choices. This is just a flow of what the opinionated flow looks like.
Although you could orchestrate this to be however you choose. And the Aztec wallet, well actually the Pixie ends up managing three separate keys. And those three keys have three functions. The first one is for creating nullifiers. The second one is for being able to discover notes on chain that are for you and then decrypting those notes so that you can get the data and add it into your pixie. So if I want to send you $10, I will create a note and then encrypt that note and post it on chain for you to then discover and decrypt. So that is the note tagging key. And then the third key is of course your signing key. Now this doesn't actually have to be a key. It could be a password or whatever, but typically it's going to be some kind of signature key, some access to your account. In the next lesson,
we're going to be going through those keys in detail and what they do. That's pretty much a high level and all you really need to take away. There's three different keys, but we're going to be going through why you need these three different keys, what they're currently being used for. Although, at the moment, this is kind of subject to change. It's not likely to change massively, but as we know, all of this is likely to change at some point because Aztedc is so new. But hopefully if you get up to speed with how things work right now, then any incremental changes will be easier to understand. So let's get into that. Now in this lesson, we are going to be learning about Aztec keys because they're slightly more complicated than Ethereum keys. Some of this might be a little bit confusing because we don't have some of the context we need to
understand it. I again just want you to be familiar with some of the terminology and the fact that there are multiple keys. That's all I really want you to take from this video. There are multiple different keys that any account will have and each of those keys have different purposes. Another video if I knew that that was simple, but it's pretty interesting. So hopefully you enjoy this one. So let's recap Ethereum accounts. So Ethereum accounts have one key pair. They're an ECDSA key pair where you have a public key which is used for your onchain identity and to verify signatures and your private key which you use to create signatures. And that's it. Man, when we learned about that, that sounded that seemed so confusing and now it's just the tip of the iceberg. Aztec accounts on the other hand use multiple specialized key pairs.
In fact, there are three different types of key pairs on Aztec. And in this lesson, we're going to be learning about those three. The reason on Aztec there are multiple keys which all perform different actions is number one for privacy isolation by having the different keys be separated for different purposes. It limits what an attacker can do if they access those keys and it prevents correlation attacks. It also enables selective disclosure. So you could share one type of key to enable someone else to perform one action on your account without giving them full access to the full account. Maybe you'd be able to just give them read access without giving them write access. So they can see the tokens in your wallet but they can't necessarily spend them. Aztec also has a per application security for one specific type of key which is called the
nullifier key and this is where the key is specific to that one application. This is called siloing where the key is hashed with the contract address to make it specific to that contract. And by leaking it, you only compromise the privacy of that one application for that one account. We're going to go into that in more detail in a second. Let's go through the three different types. I'm going to say them all off the bat just so that we know what's coming. We have the nullifier keys, we have the incoming viewing keys, and we have the signing keys, which we've also met before. Let's go through all three of them. Starting with the nullifier keys. Nullifier keys are, as they sound like, used for spending notes. It enables you to create nullifiers for your account's notes, and it's created automatically by the Pixie.
When you want to spend a note, of course, you need to be able to prove that that's your note, and you have permission to nullify it. It's not enough just to know the note contents. You have to be the owner of the account that owns that note. Makes sense, right? So each account will have this master nullifier key pair which consists of a nullifier public key and a nullifier hiding key. Previously called the nullifier secret key, it's now called a hiding key. You can think of them as the same thing. And this key is embedded in the account address. So when we derive the account address, lots of things are hashed together because it's deterministic, of course. And one of those things is the nullifier public key. And this means that once the account is created, this nullifier key cannot be rotated. it cannot be changed
because it's baked into the account itself. Each application, so let's say it's a token contract or a DeFi contract or anything else, will have a nullifier key specific to it for that account. And this is done via siloing. So let's say we have some notes in a token contract to nullify those notes. We will use a nullifier hiding key specific for that app, which will be our nullifier hiding key, the master one, which is then hashed with the contract address. So the token contract address, as I said earlier, this is called siloing. Siloing is just where you take some generic key or hash and then hash it with a contract address to make the key specific to that contract. And there are two primary reasons that this needs to be done. The first one is if the nullifier key is leaked, then it's only leaked for that
one application, not all notes stored in your pixie. The second is privacy preservation. You can't correlate different app activity via the nullifier keys because they're all different. So to spend a note, you first need that nullifier hiding key specific to the app. So hashing your master nullifier hiding key for your address with the token contract address. The protocol then verifies that this app nullifier hiding key is derived from the master nullifier hiding key using a proof that you generate locally and it also checks that the master nullifier hiding key was used to derive the account address. So it is the actual nullifier key for that account. So that's the nullifier keys.
Second, we have the incoming viewing keys. This consists of the incoming viewing secret key and the incoming viewing public key. That is a mouthful and took me like so many times to record by the way. And these keys are used for encrypting and discovering notes which is stored in logs on chain. So if I want to send a note to Bob, I would emit that note as a private log on chain which I would encrypt that note data so that no one else will be able to see it using Bob's incoming viewing public key. Let's go through how this works a little bit. This is going to be a little bit complicated and we are going to cover it in a future video because note delivery and note discovery is a little bit complicated. So I'm just going to go through it from a high level. If you
don't understand then don't worry. The incoming viewing key is just used for encrypting and decrypting note data when it has been shared to you by someone else. So the sender will generate something called an ephemeral secret key which will consist of a secret key and a public key. They will compute a shared secret s by multiplying the ephemeral secret key by the receivers's incoming viewing public key where this is elliptic curve scala multiplication. I will leave a link in the GitHub repo associated with this course if you'd like to learn about scala multiplication and how this works. And for those who know what scala multiplication is, ESK is of course a scalar because it's just a private key. Whereas IVPK is a public key. So it's a point on a curve because
it's a public key. It was created by multiplying the secret key with some generator point. Again, scala multiplication. Don't worry if you don't understand any of that. We then create the encryption key by hashing this secret. And then we use this key to encrypt the note using AES encryption. And this is the cipher text. The sender then sends the ephemeral public key and the cipher text onchain via a private message. The receiver can then recomputee S via Diffy Helman by multiplying their incoming viewing secret key by the ephemeral public key.
Again, this is scalar point multiplication. They can then derive the key by hashing the secret. And they can now decrypt the note using the key that they just calculated and the cipher text. All you need to really understand here is that it's using elliptic curve diffy helman encryption. Both parties can compute the shared secret S. But only the receiver has the private key needed to decrypt the information, the secret key, which is of course the incoming viewing secret key. The incoming viewing key is also embedded into the address like the nullifier key. So it cannot be rotated or changed. So these first two keys are known as protocol keys as they are embedded into the account's address. And protocol keys are generated automatically by the pixie and this happens when you create the
account. So the Pixie will handle this initial key generation. It will handle the app siloing for the nullifier key. It will handle storage and access via oracles which is when a function in Aztec NR in a smart contract will call out to the pixie to do some calculation using these keys like creating a nullifier. And both of these keys use elliptic curve cryptography on a curve called the grumpkin curve which I love so much that it's called a grumpkin curve. It's like a little grumpy pumpkin. It's just a specific curve that Aztec chose to use to derive their keys.
The secret keys or public keys, whatever you want to call them, are of course scalers. And the public keys are elliptic curve points generated by multiplying using scala multiplication. The secret and the generator point. To learn more about elliptic curve multiplication and generator points, I will leave resources in the GitHub repo associated with this course. The third type of keys we have already heard before and this is the signing keys and they are the normal keys used for transaction verification and signing and things like that. Exactly the same as on Ethereum except on Aztec they do not necessarily have to be a normal key pair. It could be a signature created via some other signature scheme other than the ECDSA or it could be a password or it could be biometric. Basically any orth method you can implement in code
and create a verifier for which we talked about in the account lesson. Let's just say it's a signature to keep things simple. Let's say you've signed a transaction. The account contract will just take this signature and verify it against the stored public key. As you can see in this example contract crucially the address of the contract is not derived using these signing keys which means that they can be rotated modified anything you like. It's completely up to the account implementation. So that was just a very quick overview of Aztec keys. We have three different types of key pairs. We have the nullifier keys, we have the incoming viewing keys, and we have the signing keys, which might not even really be keys as we've heard them before. It could be biometrics or anything else that we've said. And the ones which are protocol keys are the
nullifier keys and the incoming viewing keys because they are embedded in the account address. And the nullifier keys are siloed, which means that the key is hashed with the specific app contract to make a specific nullifier key specific to each app. So, that was a lot of information. I'm going to leave it there and I'll see you in the next lesson. Contract creation and contract deployment work very differently on Aztec than it does on Ethereum. In this lesson, we are going to cover contract deployment and how it works on Aztec, including contract classes and contract instances and what they are, how Aztec separates the contract code from the contract deployment, and what that means for contract addresses, how we can have a fully off-chain smart contract and
still be used without ever being onchain. Contract constructors, otherwise known as initializers, how they work, and when you need to include all of these optional things like deployment and initialization. Let's first go through contract classes and contract instances. Aztec separates the code of a contract from the deployment of that code. This is the class and instance model. A contract class is a collection of functions plus a storage layout. So you have all of the functions plus what the storage variables are. The contract class defines what a contract does, but it holds no actual state. A contract instance is a specific deployment of a contract class. So it's at a specific address and it can hold state. Each class can have multiple
associated instances just like you could deploy different ERC20 tokens from the same Solidity smart contract code. Each one would have its own address and its own balances, but they're all running the same logic. Each contract class has its own associated class ID and it's computed from the artifact hash, which is just the contract's compilation artifacts. Don't worry about it. the private functions route and this is a Merkel route of all of the private functions verification keys and the public bite code commitment. Don't worry about what all of these individual things are. Classes are registered using the contract class registry and this is a protocol contract. So it's deployed in the genesis block by the protocol and it's at a known address. A contract instance is defined by its address and
the address is deterministically derived from the class ID assault. An initialization hash which is a hash of the constructor function selector because constructors on Aztec are not required and they can be called whatever you want and then also its arguments. If you have no constructor, this is just zero. Then the deployer address and finally the instances public keys which we saw earlier. So the incoming viewing public key and the nullifier public key and again as we said earlier because the address is fully deterministic it means we can compute it before doing anything on chain instances are created and registered using the contract instance registry which is another protocol contract at a known address deployed at the genesis block by the protocol. Now
here's something a bit weird. You can have a fully offchain smart contract that is never deployed and you can use this contract without even deploying it. And this is because private functions on Aztec don't execute on the sequencer. They executed in private land in the Pixie on the user's own device. The Pixie only needs two things to run a function. The contract artifact which is just the compiled bite code plus the AI and the contract instance metadata. So the address which we said we can already calculate, the keys, the class ID and so on which we again already know. And these things can be registered locally in the Pixie without anything existing on chain because we can premputee them.
And this means we can then call all of the private and utility functions in the pixie. This could be useful for things like a personal vault, a stealth payment contract, etc. However, off-chain smart contracts cannot call public functions because these need to execute on the sequencer onchain. You cannot send L1 to L2 messages because of course this is communication that exists onchain and it can't be called by other smart contracts because other contracts can't reference something that the network doesn't know about. So what happens when you do want to deploy a smart contract? First you need to do class registration and this is done by calling publish on the contract class registry and after registration this means the contract bite code is available to the sequences for any public execution on an instance of that class. When a class is
registered a nullifier is emitted to tell the protocol that the class has been registered. So when we want to publish an instance we know that class exists. Then secondly we need to create an instance of that class. This is actually deploying our smart contract. Creating an address for our contract. We call publish for public execution on the contract instance registry. Again, this submits a nullifier so that the same address for a specific instance can't be reused. Again, you only need to do this instance registration if you have public functions that need to be executed on chain because instance publication is what unlocks public function execution that executes on the sequencer. You can't publish an instance without first registering the class. and the instance
registry actually will check for that class ID nullifier before publishing the specific instance. However, both of these things can happen in the same transaction. Initialization is like a solidity constructor. However, it can be called anything you like and it's where you set up initial state. An initializer can be either be public or private and a contract can even declare multiple different initializers. However, only one can ever be called per instance. When the initializer gets called, it gets committed to into the instances address via that initialization hash which is part of the address derivation.
This means that calling the wrong initializer or with different arguments causes the address to not match and the transaction will fail. To make sure that the initializer can only be called once, it emits an initialization nullifier and this is stored in the nullifier Merkel tree. There are actually two different initialization nullifiers, a private initialization nullifier and a public initialization nullifier. And the reason for this is because if the initializer was a private initializer which incued a public function. So it said after this execution has finished please perform this public execution. Then the private execution would emit a nullifier to say that the contract had been initialized.
It would then be added to the nullifier tree before the public execution even happened. So then when the public execution tried to happen, it would fail because it would say that the contract had already been initialized. The private initializer nullifier is what private functions use to check whether the contract has been initialized. And the public initializer nullifier is what public functions check to see whether this guarantees one-time initialization. So if you want to understand whether you need to have an initializer in your contract, you just need to think about whether you need to initialize state or not and whether that state is public or private as to whether you need the initializer to be public or private.
Then you need to think about whether you actually want to deploy your contract. And the question for that is, do I have public functions or not? If you have public functions, then you do need to deploy your smart contract. Now, there is actually two really helpful pieces of documentation on this and they both exist at dogs.c.network. So if we head to build foundational topics, this contract deployment page which talks all about contract classes and contract instances and initialization and then in Aztec NNR there is a contract deployment reference which has this helpful diagram to work out what you need to do with your smart contract. So I hope that was helpful and you learned a little bit about contract creation in this class. See you in the next one.
This hopefully should be a pretty quick lesson and this is on some privacy assumptions, some things you need to be aware of when working in privacy land because things work a little bit differently than when we're purely working in public land as we have been up until this point. When we say state is private, let's say it's a token balance. Let's say it's your health data. Let's say it's your passport information. Let's say it's your secret message to your crash. I don't know. I can't think of anything private information your donation to a charity any data when we say that the data is private you cannot assume that this means it's private forever I'm going to explain what I mean by that privacy public and private keys are based on the assumption given some public key you
cannot derive the private key given some cipher text you cannot get the original data back without having access to the private key that encrypted the data but this assumes No quantum computing technique we have that is somewhat quantum computing resistance is hashes. Given some hash you cannot compute the hash pre-image. Now even quantum computing would weaken that security though because it would shorten the amount of time that you'd need to just brute force hashes to find the pre-image. But there is a future in which these assumptions break particularly with elliptic curve cryptography of which most of the blockchain is based upon. And in this instance, all secret data that was made secret using elliptic curve diffy helman
encryption and other encryption mechanisms which aren't quantum computing resistant are now public and those accounts can be used by everyone. They can access all of the data. So in terms of you the smart contract developer, what does this mean? It means that you need to be super mindful about what private information you are putting on chain. So even if it's hashed or encrypted or committed to whatever can't assume that that means that it's private forever, it still exists on chain in the note hash tree. And it also exists in private logs. So maybe it's okay if some of my purchases get leaked 10 years later or my passport information gets leaked 10 years later after the passport is already no longer valid. Maybe it's
okay if my chats with my friends are made public in 15 years. But there's not really a timeline in which government communications or your medical records are ever made public. And by no timeline, I mean around the order of sort of 100 years. So just be aware of this quantum computing and the assumptions that we're making about the cryptographic primitives that we're using under the hood and what that means for what data you want to be putting on chain. And that's kind of it. That's all I really wanted to say was just that little note. Okay. So now that we have the foundational topics in place, again don't worry if you didn't understand parts of this. It really really does not matter. You can read the documentation, ask your favorite AI agent questions if
you didn't understand. And also just as we go through building our smart contracts, we are going to be refamiliarizing ourselves with these topics. The main thing that I really want you to understand is how notes and nullifiers work and how they are different from public state. Because when we are interacting with private states because we are working with notes in general and how we can't modify those notes, we can only nullify them and then create new notes. I want you to have that concept really really deeply understood because it's going to impact our smart contract development. So again, please go read the documentation.
Please ask AI or ask on the Aztec Discord if you're really really stuck. But in the next section, we are going to be using Aztec NR, which is the framework for building smart contracts using Noir for Aztec. So, we're going to be building some private smart contracts. And in the next section, we're going to be building a counter contract, which is really just a sort of the classic hello world type contract for smart contracts. And we'll introduce us to public and private state, public and private functions, constructors, and more. So I recommend that you go and take a little break now because we have done a lot of learning here. Go have a nice hot drink or a cold drink. Go to the gym. Go for a walk. Have some food.
Maybe go to bed. Let this information digest and then you can come back fresh and we can start actually coding which is super exciting. So I'll see you there very shortly. Hello. We are now finally ready to start building. I hope you have had a nice little rest and recharge. you have let all of that information sink into your brain. Also remember, of course, all of those concepts, so like notes, nullifiers, commitments, all of the different Merkel trees, how private and public state work, accounts, all of that. If this feels super confusing and you're still really, really lost.
Your first Aztec smart contract
Please, please don't be disheartened because we are going to be going through these concepts many times until you get to the point where it kind of feels second nature. It's like when you were learning solidity. There was a point when blockchains as a concept felt super confusing and then you just immersed yourself to the point where it becomes pretty simple. So hopefully the same will happen with Aztec. Anyway, so we are now at the Aztec documentation as it says here which is docs.asc.network and we can click start building and this will take us to specifically the build documentation rather than the ones for the node operators. If you click operate, this is literally just for node operators. So you don't need to go there unless you of course want to become a
node operator in which case please do. Anyh who, so there's now an overview of what Aztec is. Um and there is my video which you can give it a watch if you so choose but there's pretty much no point now that you're watching the course. I really need to stop rambling but we are going to instead click getting started on local network and we now need to install Aztec. There are a couple of things that we need that this bash command is going to install for us. Let me just click copy and let's now head over to our terminal. Let me zoom in a touch so you can see I have just made a directory in which to put all of my code in. And now I'm going to paste that command with the command V.
Okay, a note on versioning because it's a little bit confusing. when I actually install using the install command the Aztec CLI and initially at the start of this section I'm using 4.0.0-devet.patch blah blah blah that is not the version I actually use for the remainder of this course I changed version shortly after just showing you how to do everything in the narago and installing the CLI the version I actually use is v4.1.0-rc.4 4. I've put that up on the screen for you so you can really see what version I'm using. Then at the end of this section, I actually update to v4.2.0-zeknr-
rc.2. Again, I've put that up on the screen for you. This is exactly the same syntax wise, but it just allows you to deploy to test to test net. So, I recommend that you use the latter. And also just check the documentation for what the latest most upto-date version is. and you should probably use that. Remember to check the migration notes and use AI to update to the latest version because you are going to need to be iterating quickly when using Aztec because things just do change quickly. So you need to be very comfortable with changing versions. So yeah, that's the version of Aztec I'm actually using in this course, not that DevNet version. So don't be confused when you see this other version I'm using at the start. That's not the version I'm using. Additionally, the code in the GitHub repo will be up to
date hopefully with the latest version matching what's in the docs. So, you'll be able to see the most up-to-date syntax as well there. And now we're going to run this bash command and you'll see welcome to the Aztec installer and it's going to install Nargo which installs the Noir programming language. Of course, Aztec smart contracts are written in Noir using the Aztec NR framework. So, we need to have Noir installed of course. BB which is the proving back end. So it enables you to be able to create and also verify proofs. It installs Aztec which is a CLI tool which enables you to be able to compile test your contracts and lots of other stuff. Aztec up which like Foundry up or rust up is it or
can't remember what the command is. It enables you to manage which version of C of Aztec you are using and Aztec wallet which is a CLI wallet which we're probably not going to be using in this course. Anyway, I'm going to hit Y to continue. so that it can install all of the things that it needs to install. I also wanted to note that you should be using Node version 24, I think. Actually, I think it might say in the documentation. Yeah, Node.js version 24 just to make sure that everything runs smoothly for you. This takes a little bit of time because Aztec packages is a massive package. Okay. And now we can run Aztec- version. And you should see it say the version that you installed.
Okay. Now we are ready to start making our first smart contract. So I'm going to clear my terminal and I'm going to run Aztec new counter. So this new command will just create the structure of the project with all of the necessary files and counter says this is the name of the folder I want you to create. So I hit enter and if I type ls I should see a folder named counter. So if I now open that up in VS code dot and I remember to zoom in. Now, the other thing we need to do before we get started is to install the Noir LSP. So, if we go to extensions, I think it's this one. Then type in Noir LSP. It's this one here.
Noir language support. So, it gives you syntax highlight highlighting, compile errors and warnings on file save. So, they come up as like little squiggles. And to be honest, they're the most important things. And they also have the auto format on save. They're the things I'm really using. Now, please make sure that you do install this correct one because there are a lot of vulnerabilities that come from installing incorrect extensions in VS Code. So, please make sure that this is the correct one. You can see because the publisher is Noir Lang, it's got 5,000 downloads. We can check the identifier is the same. We can check the repository, which is correct. It's Noir Lang VS Code Noir. And we can check the license. But anyway, please install this. And then you might need to sort of
close and reopen VS Code sometimes. I'm pretty sure you don't, but you might have to. But yeah, please install this so that we can have nice syntax highlighting for our noir files. And I open up counter, you'll see this source folder and a narago.l. Okay. Once we open up our folder in VS Code, we'll be able to see the project layout. Now, I'm not going to worry too much about rest specific sort of terminology. The main thing I want you to really get to grips with is just writing the smart contracts and just sort of getting a feel for how all of the different components fit together.
But I am going to define some of the terminology. So, what we have here is essentially a crate. A crate is just a single unit of code that gets compiled by Nargo. Well, actually, because we're creating an Aztec smart contract, it'll be an Aztec flavor of Nargo called Aztec Nargo. But you won't really know that because it's all just handled by the Aztec CLI. I just wanted to note that. So, it's a collection of noir files that get compiled together to produce one output. And here you'll see this nargo.l file. This is very similar to your foundry toml. And one project has one nargo toml file. So it can this contains
of course all the metadata. So like your name, version, authors, and then also the type. This is the type of your crate. You can think of a package exactly the same as a crate because one package equals one crate. You don't really need to worry about what a crate is except there are different types. It's just the smallest amount of code that the noir compiler can consider at a time. In fact, actually, if you want to read more about crates and packages and what they are and the different types, then I encourage you to look at the noir documentation.
It won't be the Aztec documentation because this is specific to the programming language noir. So, as you can see here, there are three different types of crates. And of course, the one that we are going to be using is going to be a contract. So, if you read here, it says they're similar to binary crates. Basically binary crates are what you use when you are just creating a normal circuit. So if you're writing some constraints like require that this number is greater than 18 whatever it is it's like that except it compiles to both ACIR for private functions and then also of course AVM byte code for the public functions.
They're different from binaries. I mean, unless you've taken the no articles, you won't really know what a binary is, but they basically just don't have to have one single main function. Instead, they are structured like a contract, a bit like in Solidity, you'll see in a second. And they're a collection of functions, both private and public, and can [snorts] be deployed to the Aztec network. And this is what we're going to be creating. So, if you see in our narco toml, we have specified that the type is contract. This is the type of our crate. You'll also see the dependencies here automatically because we used Aztec new it has installed or added into the dependencies list Aztec. This is Aztec NR. So it's saying in this project we want to use Aztec NR. And if we
commandclick then it will take us to Aztec NR. And you can have a look around and see how everything's working if you wanted to. But I'm not going to look at this right now cuz it's going to be a know what anything is yet cuz we haven't seen anything. But the actually the one that I could show you is for instance this is the different types of state variables in Aztec NR. So there's different types of private variables and different types of public variables that we're going to see shortly. You'll also see this source folder. This is where your smart contract is defined in a file called main.nr. The main entry point to the crate needs to be called main.
Doesn't mean that it needs to have a main function. It just means that the file name needs to be main. And we are going to name our contract counter. And you'll also see that we have automatically got some boilerplate code here. So this first line is our dependencies. So we use the use keyword to say we would like to use this dependency. And then this is the path. So in the Aztec dependency inside macros, we would like to use the Aztec macro. And this Aztec macro is what you have to do. This little line here says we would like to use the Aztec macro for this contract. So what this will do is
it tells the compiler this whole module. So this file is an Aztec contract. Compile it with the Aztec specific rules. This runs at compile time and it modifies how everything is interpreted essentially. So this is exactly what makes it into a contract. Then we use the contract keyword and then we have the name of the contract and then we put all of our code inside curly braces. So the structure of this actually does look very very similar to a Solidity smart contract. Nothing particularly crazy so far. We've never really seen this hash with the square brackets and a word inside. This is just how you use macros in both Rust and also Noir. If you want
to see all of the different types of macros available, it's in the documentation. So obviously in the build documentation, Aztec NR framework description and then Aztec macros and you can scroll through the list of them here. And then also they are in the API reference which is a little bit difficult to m navigate around sometimes, but you can just click macros here. So you can see for instance all of the different functions, macros, etc. Again, it's probably not going to make too much sense for you. But when you're a bit more advanced, then this is where you probably will be going to see everything because the API reference has documentation that is written by the developers who wrote the thing. They wrote Aztec themselves. And then also for AztecJS, which is if you're building
a front end or you're writing end to end tests and you're using AztecJS. Anyway, I digress. Don't worry too much about that. A little bit of a confusing tangent. Let's go back to our smart contract. So we now have the boilerplate code to be able to write a smart contract. So when we create a smart contract in solidity, the first thing that we want to do is decide what is it that we want to do in our smart contract. We probably make a constructor. We set some storage variables. We write the functions and then we test it. Now the flow is going to be literally no different. There is a couple more things that we need to think about and I know slightly more convoluted to do those things but for the most part it's pretty much the same.
So we want to have some kind of counter storage variable in solidity. This would be some kind of mapping of address to maybe I don't know a u int 256 except we want it to be private. I don't want anyone else to be able to know the value of my counter. This is obviously not completely useful. It's a good example to show you how to write smart contracts. It's nice and simple. So, we've got some kind of counter where each address, each Aztec address will have their own counter value. But we want we want the value of the counter to be private. So, we need to have a private function. Therefore, private function to increment
counter. And I need to have a getter function to be able to read my counter value. Before this, we also probably need some kind of constructor to initialize the state. Maybe we have the deployer have a counter head start value. Maybe everyone else will, you know, obviously have empty state initially, default [snorts] to zero, but we want the person who deployed it to start with five. Actually, before we do anything else, let's compile our project. So, control back tick. I'm going to run as tech compile. And you'll see it says I
couldn't find a narago tunnel. So, instead I need to navigate inside of counter. So, I'm inside of the package. And then run the command again. and compilation is complete. [snorts] So now anytime that we change anything we can just recompile the contract and then you'll see that it creates a target folder which has our bite code. So this is called the artifact inside this counter counter.json JSON, you'll see the AI. And then if we scroll all the way down, this file map is actually just all of the contract logic. And then in this bac file, we scroll all the way to the end.
Then this here is the bite code. So here it says bite code. Then you've got all of the bite code. Now we can start writing our smart contract properly. The first thing we need to do is we actually need to install some specific things that we need to use from this Aztec dependency. Import the macros that we need to use. So the first thing that we're going to do is we're going to define some storage. And to do that we need to use storage macro. And storage is defined inside of a storage strct. A structure it contains lots of different variables inside. And this is how you define storage. So it's a strct that we
name storage. We also in angled brackets need to put something called context inside here. And then we use some curly brackets. And then we put all of our storage variables inside here. So to define storage we first need to annotate with this storage macro. We need to create a strct called storage and it takes the context parameter. So this is just basically saying inside of this storage I'm going to need to be able to use some different contexts. And those contexts are of course the private context and the public context. And in those two different contexts we need to be able to have access to some different
context variables. Things like the message sender or the notes that we have available to ourselves. So I need to keep track of all my different notes. I need to be able to add nullifiers. And these are all called side effects of executing functions. Again, this sounds a bit confusing, so I'm not going to go too in depth into context just yet. We're going to touch on that a little bit later, but just bear with me a second. So, of course, we're going to need a storage variable called counter. So we know that we want to have some sort of counter storage variable, but we need to of course specify how we want the contract to store the state in solidity. Of course, this means deciding number one, what data type do we want it
to be? Do we want it to be a UN 256? Do we want it to be an address? Do we want it to be suddenly I can't think of any other data types. The second thing we want to decide is whether we want it to be constant, immutable or the default which is just mutable. We also want to decide whether we want it to be public or private. And in solicit world, this means whether we want it to have expose a getter by default and whether we want inheriting contracts to be able to access it, not whether we want the the state to actually be public or private like we were talking about in the foundational topics section. The last thing we want to decide is do we need it to be some kind of structure? Do we need to have a container effectively? Like do
we want it to be a mapping of some address to some 256 which in this case yes we do or do we want it to be an array? Like do we need it to have multiple values? Do we want it to be a strct? So these are still the same kinds of questions that we are going to ask ourselves in Aztec. And then we have this extra layer of consideration that we need whether we want the data to be public or private. And in the case of Aztec, this means truly private. So only you have access to the data. The data will be stored locally only, not stored or on chain in its raw form and only you will be able to access that data unless you choose to share your value with someone else. So we need to think about
what properties we need our counter variable to have. So for our counter we need every person or every Aztec address to have their own associated value. Therefore we need it to be a data structure like a mapping. We also want the data to be mutable. We want to be able to update the counter, increment it by one and decrement it by one. And actually we should add a comment here to say that we want to be able to decrement the counter. So in Aztec there are still those three things that we need to consider. What do we want the data type to be? What do we want the state variable type to be? Do we want it to be mutable, immutable, etc. And do we need to have a data structure? And we are
placing all of our storage variables inside of the storage. Now if we head over to the docs again just for a second and then if we head inside Aztec NR and inside framework description and then click state variables there is a fantastic breakdown in here written by the head engineer for Aztec and R and it goes through all of the different types of state variables. So if we start with I've just scrolled down all the way public state variables there are three different types which is public mutable public immutable and delayed public mutable. Delayed public mutable is a little bit confusing to understand at
first. Essentially it's a public variable that you want to mutate as a side effect of a private function call. So you want to incue a call to public that changes the state but only after some delay. This is particularly useful in some instances. I do encourage that you read the documentation on delay public mutable down here because it is very good but that's a little bit complicated for now. So the first thing we need to specify essentially is whether we want our public state variable so not private of course to be able to change or not able to change. Of course, we want our counter variable to be able to change. Initially, let's just
assume we're creating a counter in state that is public and it can be publicly updated and then we can see the difference later by creating a private counter variable and how the API and the things that we do change very slightly and then this will give you experience with dealing with public state variables but also private state variables. So this is equivalent to using the public keyword with your state variable on solidity. Now if we go down to how it is used. So for instance this collateral asset we see we've got this public mutable some angled brackets and then inside we have actually the data type.
This is an address and in Aztec we have this type which is Aztec address written exactly like this which we will need to import. So we've got a public state variable that we are able to change and the type of that data is an address. Got this thing called context which I just want you to accept for now and we are going to go through very shortly. I just want you to get to grips with public state variables and then also private state variables. We're going to come back and look at the private state variables in a second once we've got used to the public state variables. So normally of course we would do some variable called counter. Oh my god. and we would make it public which it would be by default and then we would say what the type was. So we now know how to of
course decide whether we want our data type to be public or private and whether we want it to be immutable or mutable based on the state variable types. We know how to define the data types. But how do we create a mapping? What containers can you use in Aztec? On Aztec we have this map and it is just like solidity's mapping. So for an example, you'd have a map, some angled brackets. You'd then have the key type, which is an Aztec address, and then the value type. The type of the value is a public mutable. We want the data to be public, and we want it to be able to change. And then the data type is a U 128. This is exactly the same as a UIT
128. This is the data type. This is the state variable type. This is the type for the key. And this is the type for the container that we're going to hold the whole thing in. So it can be accessed with an address to obtain the public mutable that corresponds to it. So we're going to do exactly this. And the second part of course is we need to pass in these context things that we're going to define shortly. So we need one for the public mutable one 128 and then one for the map. So we can define a map of Aztec address to pro oh public mutable and the type of this mutable is u 128 and the syntax for noir
is just u128 rather than u int. There is no u26 because under the hood all arithmetic is operating over a large prime number which is roughly 2 to the^ of 254. You don't really need to know what that means. You just essentially need to know that this is like the largest number that the system can natively work with. Any numbers in your contract cannot exceed the maximum number which we're using in our system. So if you wanted to use a number greater than that, you'd have to split up into multiple values. And this would mean that you'd have extra checks which is more expensive. So instead in Noir, you only have up to a U 128. And then there's this also this field type which
is up to the size of the field that we're using in the arithmetic. So later on you'll see we use this field type and they're also a little bit more efficient. This means there are less checks so it's slightly cheaper. So we have no U26 only U128 and then later we'll see the field type. If you need more information on this then please visit the noir documentation. Then we need to pass in the context for the public mutable. We need to pass the context for the map and then we need to close out the square brackets. And that's it. Oh, that's not it. We need to now import these things that we have used. We have now used the storage macro, the state variable container map,
the state variable type public mutable, the type for an Aztec address, and that's it. So far these things do not come automatically just by adding this use line. Instead we have to as well inside of the contract use the use keyword go inside of our dependency Aztec some curly brackets and then Aztec address comes from protocol colon address colon colon Aztec address. Add a comma because we're going to add more things. We need the state bars colon colon and we've got two state variable types. The first is map container and
public mutable. We also of course need to add the storage macro. So that is in macros colon code storage colon code storage and they are just the paths for those different things that we are using inside of our smart contract. What the hell is this context thing then? Context is just an object which is made available in every function in Aztec NNR and it contains all the information needed to be able to execute that function. And during execution, the function will update information inside of this context as well. For example, in private, it needs to keep a running total of the notes being created, the nullifiers being created, any public
functions it wants to call after private execution has finished, etc. At the end of the function's execution under the hood of course handled by AztecNr the context is returned to the circuit to create a proof and to be checked for validity. We already know of two different types of context and that is public and private but that's actually another one called utility and they have different information inside of their context object. This is the private context. It includes the message sender for the first call. This will always be empty because this will be a call to the entry point on the account contract. But any subsequent calls will always be the previous contract. But here we also have you'll notice the new note hashes to be added, new nullifiers, the private call
stack, the public call stack, so any public calls for the sequencer to execute afterwards and so on. And these are called after effects. The sequencer will need to apply all of these things. So add all of the nullifiers and notes etc to the different trees and execute any incued public calls. Everything is onchain in public land. So nothing needs to be passed by the context. Here you'll see we only have the args hash. This is just the hash of the arguments that's passed to the public function being called. This is of course because the AVM can just read state directly via op codes. Nothing needs to be passed here.
In private of course it has no access to the live chain state. So everything that the function needs has to be passed in via the context. Things like the call context like the message sender, the block header, etc. Then finally, we have the utility context, which again is also running locally on the user's device, but instead there's no proof generated. We'll see this shortly, but it's basically when you have a function which is just running some logic, maybe doing some calculation, but not modifying any state. They're read only. So you could use it as like a view function to check a private balance. And the utility context just holds enough to query historical state. So it's the block header and the contract address. State variables like private, mutable, public, mutable, etc. As you can see here, all take context as a generic type
parameter. This is how Aztec enforces at compile time which operations are invalid in which environment. So if you see here on private mutable, there are different things available whether you're in utility versus a private context. And each state variable defines different methods for these different context types. For example, private mutable with a private context gives you gives you initialize, replace, and get note. But then in utility context only has view note and is initialized. And there's no implementation for public context because you of course can't read or modify notes in public. That makes no sense. The good news is that you almost never deal with context directly. So don't worry if you don't really
understand this because the Aztec macro just handles all of it for you. But I just wanted to give you a little bit of understanding about what this context thing that we keep throwing around is. It's basically just where in private, public and utility, the state variables know what functions can be called in the different context and then also keep a running total of the things that have changed in private and then also give access to information like the message sender in public and private. Note that you don't have access to message sender in utility as well here. And that's pretty much it. An overview of what context is.
Before we even compile, we can see that the Noir LSP has picked up on some problems. So, we need a comma here. And we need a semicolon after we define all of our imports from Aztec that we're using so far. Okay. So, we now have this storage strct which has this generic type context. We have a counter variable which is a mapping. So this is a container of an Aztec address to a public mutable. So a public storage variable that we can change that is the data type u 128 which is the equivalent of a solidity u int 128. And then we have these context parameters which like we explained in the previous lesson
allow us to update the context strruct. So all of the data that the node needs to know in terms of public state and then also the pixie needs to change in terms of private state. So updating notes and nullifiers, being able to get the message sender, all of that kind of stuff. Now let's create an initializer. So an initializer is like a constructor in Solidity except we call it an initializer in the Aztec world. The reason we do this is because your contract doesn't actually have to be deployed to be able to call your constructor. Like we were talking about before on Aztec contracts can be used without even being deployed. They can exist just in private land. We can then initialize them. So calling this special initialize function to make sure that
onchain everyone knows that it exists. We can set the initial state and things like that. So we need to create some kind of initializer to initialize the state. And the way we mark a function as an initializer is we add the initializer macro literally exactly like this. And then we also need to define the state visibility of the function. Now we haven't actually talked about state visibility in terms of functions in Aztec yet. So we should do that now because we're going to define a function with the function keyword fn. We're going to name it something like initializer or constructor whatever you want. We can pass in some parameters and then we can define the function body. Normally in solidity we need to say something like
external here. This is not what we do in Aztecr. Instead, we have some macros that define the state visibility. Those are hash external and hash internal. Why don't we have private or public? The reason we don't have private is because private is of course overloaded in that private insolidity refers to whether inheriting contracts can view those functions and interact with them. Since AztecNr well actually noir has no concept of inheritance that doesn't exist in Aztec NNR or noir. So the functionality of internal and then solidities private would be the same.
But of course we also just don't want to make it confusing by adding private when private refers to private states on Aztec. So it's a little bit confusing. And then public is also a bit weird in the solidity realm in that it means it's external plus it can also be called internally which shouldn't really be used anyway because we kind of want to be specific and precise about where we want our functions to be called just for security purposes and code readability. So the only ones that really get left over are external and internal. And now of course the other thing we need to define is whether the function is intended to be called from public or private. So the way we define that is we use some parenthesis and then in quotations we put private or public. Now external is of course going to be called
externally. So you're going to have your account contract call into this function or you're going to have it called by another contract. Internal is when you want to restructure your code and you want to maybe do have code reusability. So the logic inside this internal function is used multiple times and again this would be maybe called from private or public. This of course wants to be an external function cuz it's an initialize. You would have some contract deploy the contract. Maybe your account contract is deploying the contract. And so it needs to be external. And because we want to be able to set public state, we are going to make this function public. And I'm actually going to call this initialize public like this. Now we need to import that initializer macro
and and the external macro as well. So the way we do that is because we've now got multiple, we're going to put some curly braces around storage. And then we're also going to have some macros from the functions library. We're going to have multiple. So put it in curly braces. And then inside here, we need initializer. Initializer. And we also need external like this. We give it a save. Hopefully it makes the error go away. Nice. Now we can put some logic inside of our function. Maybe we have some contract owner and that is the person who is going to call this initialize. I've just realized I've spelled this the British way. So initialize with Zed. We're going to put
have some variable called head start which is going to be of type U 64. We don't need a whole U 128. And then we need the owner of the contract which is going to type Aztec address of course and no new types to import there because this is a built-in noir type. This we've already imported. So now we can set the state of the counter for this owner who called the initialize function. And the way we do that is using the self keyword. Self allows us to access this storage. So now we can use dot and you'll see that we've got all these interesting things on self including message sender by the way which we'll be
using later in Q self which is like an inq call to public but also storage and we're going to be using storage. Then the name of our storage variable is counter and actually I'm going to make this plural since it is a mapping counters. If we do the dot we can see at which means we are accessing the mapping for this key. The key is of course the owner. Then we are going to dot write because we want to write the state for this storage variable. Now inside here we need to get the current value which is of course going to be zero. So actually we don't really need to do that. We can just set it to the head start and then that's it. Now of course we've got this error because it expects the type U128 and it found the type U64.
So we can do as U1 128 or we could just convert this to actually be a U128 which I'm actually going to do. Now let's compile it since we've changed some stuff by running Aztec compile under the hood. This is of course going to use Aztec nargo and compilation was complete. So we have learned so far how to import dependencies. We've learned that contracts need this Aztec macro. We use the contract keyword to define our contract. We have a name for our contract. We then import the specific parts of the dependency that we're using. We then define a state inside of the contract using the storage macro and a storage strct which takes this generic
context type. It's like boiler plate that allows us to post in whether we're working in public land or private land. And then we have this counters state variable which is a mapping of an Aztec address to a public mutable variable. So a public variable that we that can change that is a data type U128. We then created an initializer function using the initializer macro and is of course an external function since we want to call it from outside of this contract. We made it public so that we can modify public state without having to incue the change. And we passed in a head start to give the person who called this initializer function a head start for their counter. A little bit of a
contrived example, but hopefully you're starting to understand how the structure of these smart contracts works. In the next lesson, things should be getting a little bit easier since we are going to write our increment and decrement functions for our public counter. And then we'll be getting on to doing some testing. And then finally before we wrap up this section which is actually going to be a little way away we are going to work with private state which is going to be super exciting. So in the next lesson let's write some functions. Doie let's start writing some functions.
So the first one we're going to do is actually not a private function. We're going to do a public function to increment the counter. So let's do a little comment public function to increment the counter. Okay. So now we need to make sure we mark the function as external because we want to be able to call this function externally and we need to mark it also as public like that. Then we can use the function keyword fn name it increment public something like that and then put the logic in our function body. Now we need to just increment the counter which means we need to get the current value of the person who's calling this
functions counter and then we need to add one to it. So we need to read the current state and then write to the current state. So we use it again self.counters dot counters at and then we can access self message sender that we saw before and that's the key and then we can do do read like that and we need to move this brace there semicolon. Now we can save this to a variable. So we can use the let keyword to say let this variable which we're going to call current value equals to and then we can increment the counter. And the way we do this is again using self dots [snorts] storage at self dot message sender dot write
current value plus one. Now let's compile this Aztec compile and it's compiled successfully. Let's now create our decrement function which is going to be pretty much exactly the same. In fact, we can pretty much just copy paste it except we'll call this decrement public and we need to do the current value minus one. Now we also need to check that the value is not zero cuz otherwise we can't decrement. So we can do an assertion assert after we do current value. We can assert that the current value is greater than zero or we want to revert with counter value is zero. Now let's compile it again and it
compiles successfully. So let's now do the same thing and learn about private state instead of public state. And then finally we can get on to writing some tests. You could now just write your tests for your public functions but I like to go ahead and do the private stuff first. So now that you know how to declare, read and write to public state via public functions which are of course executed onchain via the node using the AVM code and changes to that state are updated in the public Merkel tree. Let's go through the same process now but for private state. So of course private state is stored locally on your local
machine in your pixie and private functions are also executed locally. Private state is stored inside of notes which are then hashed and the note hashes are stored in the note hash tree. Then when we consume those notes we create a nullifier which is then calculated from that note information and stored in the nullifier tree. So as well as having different private state variable wrappers, we also need the note type wrapper. So if we look at some private state variables, if we go back to Aztec NR and then we go to Aztec source state vase, scroll down to let's look at private mutable. Interesting.
Could be a problem with GitHub. So if you look at private mutable, so these are private state variables which can be changed. These private state variables will of course be stored locally. I know I keep repeating that but I just think it's important and you can actually read these comments. They are super useful because they are written by the team who created Aztec. So they are very useful for understanding what's going on. But if we scroll down down down uh where am I looking here? We'll see that this private mutable takes a note and then the private context. So if we compare that now to if we open that in a new tab public mutable it just takes the type like U 128. So now we have this extra
wrapper which is note and if we go back to Aztec NR we can actually look at some examples of note types. A note is just we look at uint note source uint note it's just an nr file and it defines the data in the note via the strruct which we have actually seen. So if we scroll to here we go pubstruct u note and it's got one value which is just a u128. So you can see that this type is just a wrapper around u128 and it also implements note hash. So it it has helpful functions like compute note hash
and compute nullifier and then it also implements things like how to create a partial note and how to complete that partial note which you don't need to worry about right now. So it has the data in the note via a strct and then some functions to create the note hash and then other files effectively. So it handles all of the private fluff for you if you will when working with notes. So you can also write your own custom notes but Aztec art has some pre-built note types of course this uint note but then there is also field note I believe field note which of course will just wrap the field type. So if we have a look here yep it just wraps the field type and because it's got this
hash note this is where all of the logic and the functions for creating the note hash and the nullifiers are. And then there is also if we go in address note there's also this which of course just wraps an address type. And then finally we have a balance set which is when you want to be able to have lots and lots of notes which will make up your full balance. Like we were talking about before. You might have a $5 note, a $1 note and a $10 note. You can't have one balance storing your entire balance because that would mean that when someone sends you a note, they would have to know what your balance is for it to be able to be updated.
Instead, they just send a value with the amount that they want to send and then you just store that in your Pixie. A bit like regular cache. So, this has helpful functions for you. So, it's a private set which is a type of private state variable, a bit like private mutable or private immutable. We'll go through the different types of private state variables in a second, but this is a state variable where you can have a set of notes. So, lots of different notes all collected together associated with one owner. And then also some helpful functions to be able to add to your set of notes and subtract from your set of notes because that's a usual thing that you would like to do if you would like all of your notes in your set to be able
to add up to a balance. And then it also has a helper function called get balance. Oh no, balance of sorry. But we'll come back to balance set in the future when we were working with balances. Right now, because we're not allowing anyone else to add to our counter, only ourselves. We can just have a one universal value. So we can just use the uint note because we already know what our total amount is going to be inside of our pixie. So we can from private add to and subtract from that safely without worrying about having multiple different notes because we don't need anyone else to be able to send us notes. I hope that makes sense.
Okay. So now let's go back to state variables and private mutable. So we can see that it implements this owned state variable. So what is this? It is a trait. So if you know what Rust is then you'll know what traits are. A trait is basically just a way to define shared behavior. So it's like having inheritance or an interface in Java or something like that. So any type that implements the trait must provide certain functions. So any variables which implement owned state variable need to be wrapped in another state variable cuz actually really this private mutable doesn't implement state variable at all. So instead it needs to be wrapped in something which does implement state variable to be a true
state variable and that is owned. So owned is a type of state variable because it implements state variable and it takes an owned state variable which needs to have some function new which if we looked at owned state variable a second says it needs to implement this function new which initializes a new owned state variable at the given storage slot and owner. So if we look at for a second private mutable, let's go back a second and look at private mutable. And there should be some function new. Yep, we have implemented this function new. So if you're using private mutables, you first of all need to decide what note
type you want the private mutable to be. So like what the strruct is with the data types inside the strruct like your little envelope around your data. And then second of all, you need to wrap that private mutable in an owned state variable to make it a true state variable type since this implements state variable. Now, if we look at this owned state variable docs, we'll see that this trait defines the interface for the Aztec state variables that have an owner. Unlike regular state variables, own state variables need to be wrapped in the owned state variable when placed in storage. So, we'll see exactly like this in a second. So, we've got our storage. We've got the name of our storage variable. We then have a private set which is another type of
private state variable which doesn't implement the state variable trait. Instead it implements owned state variable which means it needs to be wrapped in owned which does implement state variable and it has a note type and then of course we need to pass along the context to the private set and then also context again to this owned trait. Owned state variables are used when working with private data only and this kind of makes sense. So as it says here in the public context anyone can read the data but in private of course only the owner should be able to read their own data because it requires knowledge of the note and possession of this thing called the corresponding nullifier hiding key which we learned about in the
first section. So essentially all notes should really be associated with an owner because they're the owner that's going to be having it and storing it in their pixie. So this is just a decision that the Aztec NR team have made currently at the time of recording to make it enforced that all notes should have an owner that is tied to that piece of data. If we go to Excalador for a second, I can kind of draw this. I've already got some diagrams, but if we go over here, we zoom in a second. So we imagine these are all notes. So remember notes are basically just like a strct.
This is obviously pseudo code. And inside that strruct we might have a balance which is of type uint 128 and then maybe I don't know some other data like a string oh sorry a message of type string with a length of 9 something like that and this is our note and it will also have some functions inside of it. Now we might have if we scroll out a second in the universe multiple of these notes. Now of course they'll all be stored in different pixies but it doesn't really matter because the note hashes will be stored in the note hash tree and then also we will emit notes as messages
which we're going to learn about in a second. We're going to post the encrypted notes on chain so that if we were sending a note to someone else we could decrypt that note. And even if we're sending ourselves a note, we can still have that information on chain to decrypt in case we lose access to our pixie or something like that. And our pixie needs to be able to actually find the note and store it locally. So we have lots of these different notes and we're basically saying each note is itself tied to an owner. So it's like we're going like this. This one is Mr. Red Guys. This one's also Mr. Red Guys. This one is Mr. Blue guys. This one's also Mr. Blue. Maybe this guy is Mr.
Yellow. This guy is Mr. Green. This one's Mr. Green. This one is Mr. Blue. And this one is Mr. Red. I'm not sure why they're all misters, but there you go. So each note itself is tied to an owner. So we know now that most private state variables will need to be wrapped in this owner state variable. So each note can have an associated owner. And the private state variables are private immutable. So data that you don't expect to be able to be changed and you don't want to be able to be changed. So cannot. Don't know why I said it like that. Private mutable. So data that we want to be able to be modified. We also have private set somewhere here. Private set. Oh no, that's a test. Private set,
which is, as we said, we've got a load of notes associated with one owner, not just one value. And we also have balance set, which actually isn't inside this folder. It's over here inside Aztec and art. balance set. We're not going to talk about balance set yet, but we did talk about it a little bit earlier. We're going to use that in the next section. The only ones which aren't wrapped in owned are single private immutable and single private mutables. This is when you want a piece of private data which you might share with others. There's no inherent owner and it's not tied with a specific owner, but there is only one value for the entire contract.
And then there's also this singleuse claim, which is a really cool state variable. And I encourage you to read the documentation on that, but we're not going to go into that right now because I don't want to overwhelm you too much. Anyway, so now that we have had a little look at private state variables sounds really confusing, it's really not once you start using it. So let's actually start using it so you don't get too confused. Okay, let's now create a private counters. Now the first thing I want to do is I just want to rename counters just to make sure that everything is super clear. So we can double click it and then hit command F. Then we're going to hit this down arrow and then I'm going to make this public public counters. And then I'm going to replace
using this button and I'm going to step through them just in case there's any that I don't want to change, which there's not. Okie dokie. So now we need to create a second storage variable called private counters. This doesn't need to be a map because the mapping associates an address with a U128 which is a public mutable. We you might be thinking that we do map Aztec address and then this will be private mutable like this and completely finished it off. I mean that [clears throat] would be the line. We don't actually need to do this because private mutable is of course owned which means that each note each private mutable has an associated
owner attached to it already by default which means we don't actually need a mapping. Instead we have owned private mutable and we need to use that uint note and then pass in context. This is our note type which wraps a U128 inside of a strct and then has some helpful functions for creating the note hash and the nullifiers. Then we wrap that in the private mutable. I mean I say state variable type but it's an owned state variable type. And then we wrap that again in the owned state variable type to say that all of these private mutables need to have an owner associated. Let's now make our increment and decrement functions but in private.
Oh, before we do that, we need to import these types and the uint note. So, inside of our nargo tunnel, we need to say that the dependency we want to use is the uint note. And it's actually going to be exactly the same as this. So, I'm just going to copy it actually. But then instead of the directory being as tech we need it to be uint-note need to close this out with a curly brace and hit save. Then in main we can use our Aztec uint note you note. Oh sorry we need to put that inside of the contract so that we can actually see it from inside the contract here. And as copilot is
helpfully saying we need to import private mutable and owned. Then we can save. And let's just do a little compilation. Aztec compile. Oops, that is not how you spell compile. Compile. Okay, let's now make our private functions. So below our public functions, let's now create some private functions to increment and decrement the counter. Oops. As that comment was saying, we need a private function to increment the counter. hash external private function increment private. Let's close that curly brace. Now, here's something interesting.
So, our public counters will have a default value of zero because the public mutable storage type will look at the data type it holds and that will have a default value. However, private state variables do not have a default value. if they have not been set trying to read them will revert. So we actually need to check whether the counter has an initial value for some address and remember that each private mutable instance will have an associated owner. So there'll be lots of different UI notes all with different owners stored in different pixies. So to do that we can actually use a function
on the private mutable type called initialize or replace which will check whether the private mutable has an initial value for some owner whether it's been initialized and if it has it will replace it and if it hasn't it will take some value. So we can write self.ts storage same as before.private counters. So this is obviously the owned type right now. So then we can do at to access the private mutable for a specific owner. And for this we'll use message sender. This needs to be self dossage sender message sender with parenthesis. And then we can
do dot initialize or replace. If we click this you'll see that it takes as argument a function. And this function will take an option note and a note. So I'm looking in the API docs right now, the API reference. So in the docs, it's Aztec.nr. Scroll down to Aztec.nr API reference. And then I've just clicked on state vs. And then I clicked private mutable. And here you can see initialize or replace. So it initializes the private mutable if it's uninitialized or replaces the current note using a transform function. If uninitialized, it is initialized. If it already initialized, it's replaced, which
retrieves the color and no nullifies it, inserts the transformed note. Because of course, we cannot modify data. We can only destroy old notes by nullifying them and then create new ones. So, as argument, it takes a function that takes an option type with the current note and returns the note to insert. This allows you to transform the current note before it is reinserted. The option is none if the state variable was not initialized. So the way we can do this is something called a closure and it's just a shorthand way of writing functions with these two pipes and then curly braces for the body. So in here you put your parameters in here you put your logic.
So for our parameter where it is going to take some well it's really an option note option note and an option note just means either a note or not a note. So it's optionally a note it could be none aka there's no note yet. And let's write the function body. The first thing we need to do is get whether there is an existing note and if there's not return zero. Now, if it's zero, it will eventually try and subtract one and then it will revert. But we're going to come back to that. So, we need to take our option note. And option notes actually have all these different functions on them. And we're going to do unwrap all because this will get the value of the
note or it will unwrap it or it will set it to some default value. The default value we want it to be is obviously still a uint note type with a value of zero. And then we take this unwrap or which will either return zero or the value and then get the value that has been returned. So we do that using dot value. We now have the value of the current note or zero. And then we need to finally minus one from that. And now we have the value that we want to set our note to. So we could be like let oops this is increment sorry plus one.
We could be like let note value equal this. And this is now a U128. And then we need to create a new UI note where the value is the new value. So now we have created a new note. We've set the storage slot to that new note for this specific owner. But we actually need to do something else. And that is we need to deliver the note using the deliver function. And the reason for this is because the pixie needs to know to update its storage note delivery. and why you need to do this thing called delivering notes is a little bit confusing. And in the next lesson we are going to learn about note discovery and note delivery because it is a little bit complex and you have to understand how
the different pieces are working together and why this needs to happen and how this actually happens. It's it's actually not that complicated but I think it's just a little bit of a different way of thinking about things and understanding things. So, I'm going to have a little segue where I'm going to hopefully do some nice diagrams and things like that so that you can understand what's going on and then we'll come back here. How does a wallet or a pixie find out about its own notes? So, let's say in a smart contract we create a note for ourself or for someone else. How does that get added to the pixie the local storage? Whenever we create a note in a smart contract, there are two parties involved. the entity creating the note, which we're going to call the sender, and then the entity who will own the
note, which we'll call the receiver. They're the person who will add the note to their pixie. There are two instances in the smart contract. When calling a private function, we could create a note for ourself. Let's say we're sending someone else tokens and we need to create ourselves a note with the leftover tokens. Let's say I had a $5 note and I wanted to send Jess $3. I would have to give myself a $2 remainder note. Or in the example of our counters smart contract, we're creating a counter note where we're incrementing the value and we want to send that note to ourselves. We want to store it in our own pixies. The second instance is when the note has been created for the sender
to be different from the recipient. Let's say I want to send tokens to Jess. I'll need to send her that note information. In both instances, the note needs to end up being stored in a pixie. It needs to be found by the receivers's wallet, whether that receiver is equal to the sender or someone else. And this is known as note delivery and discovery. The process of sending a note to someone else and then picking that note up and finding it and storing it in their wallet or in their pixie. To do this, there are a couple of ways. Either the note needs to exist on chain in some way in a private way so they can be found by the recipient without anyone else being able to access the note information or if it needs to be shared directly with them offchain so in person or on
WhatsApp or whatever. First we need to actually create the note. So creating the note hash adding the note hash to the Merkel tree. But then we also need to deliver the note. So sending the note contents to the recipient so they can actually add it to their pixie and use it. So in our smart contract, we actually need to specify how we're going to deliver the note. When we use the function deliver when we create a note, there are three different options. We have onchain constrained, we have onchain unconstrained, and we have off-chain. Let's start with the onchain methods. The note is encrypted using diffy helmet encryption. This means that only the receiver can decrypt the note.
So the sender encrypts the note using the ephemeral secret key and the receivers's incoming viewing public key. We send that as an onchain message which the receiver then decrypts. The problem is how does the receiver know without seeing all of the note messages onchain and trying to decrypt every one and then seeing which notes are for them. And to solve this, Aztec uses a tagging system. When a sender creates a note for a recipient, they tag it with a value derived from the shared secret between the two parties. The secret used for the tag is generated in exactly the same way as we did when we were encrypting the note. But instead of using the ephemeral secret key and ephemereral public key, we instead use the sender's incoming viewing secret key and incoming viewing
public key. And remember because the incoming viewing public key is baked into the address, we can actually get it from the sender's address. Which means that to make sure that we can pick up tags, tagged notes from a specific sender, we have to register the sender itself in our pixie so that it is able to calculate the shared secret to get the tags. The recipient's pixie can then query for logs matching that specific tag without having to decrypt every log on the network. So to discover notes from a specific sender, the wallet must register the sender's address with the pixie via register sender. This lets the pixie compute the shared secrets created using that elliptic curve diffy helman encryption needed for tag computation.
The pixie will then sync the with the chain periodically to pick up its tagged notes and add them to the note database stored locally. So we said there were two types of onchain delivery and that was constrained and unconstrained. With unconstrained, there is no content guarantees. The encryption, so encrypting the note information runs outside of the circuit. This of course makes it cheaper because we don't need to create a proof for it and then verify the proof. But the sender could encrypt a load of rubbish or use the wrong key. So the circuit isn't verifying that the cipher text is actually the note information. It also isn't verifying that the correct key was used to send it to the correct receiver. So you should only use this method when you trust the
sender. Let's say we're sending a note to ourselves. The second type is constrained. So encryption of course runs inside the circuit. So a proof is created and allows for verification that the cipher text is the correct encryption of the actual note for the actual sender. This of course makes it more expensive, but it does guarantee that the recipient can decrypt and discover the note. Currently at the time of recording for Aztec even note tagging itself is still unconstrained. Actually encrypting the note is constrained but note tagging is not but this is something which is going to be fixed in the future. The third type of delivery mechanism is offchain. So the note is encrypted and handed to the receiver offchain. Of course here the sender
could just not send the information and therefore the receiver will never discover their note. So we've talked about the three different types of delivery onchain constrained on trained unconstrained and off-chain. So that was a very quick summary of note delivery and note discovery and note tagging. If you would like to learn more about note delivery and note tagging then please visit the Aztec documentation or talk to your favorite AI agent. You have three different methods to allow your pixie to discover this note information. It is encrypted using diffy helman encryption which just means that there's a shared secret and then the notes are also tagged using this shared secret which they can both compute which needs to be registered by the pixie so that the
pixie can see all of the notes that are intended for that receiver. Okay. So now that you understand why we need note delivery and what the different delivery modes are, we can pass in the delivery mode. How do we want this message to be sent? And we are going to use message delivery type. And on here we have either onchain constrained onchain unconstrained and offchain. And because we are only sending a note to ourselves, we can trust the sender. Therefore, we can do onchain unconstrained strained like that. And that is it. We have completed our increment private function.
Actually, no, we haven't because we need to import message delivery. And that is inside messages. Messages colon message delivery message delivery. Now we can do the same thing but for decrement. And the logic is going to be pretty much exactly the same. Decrement. Oh, we need the macro first. External private. Oops. decrement private, which isn't going to take any arguments. And then we're actually just going to do exactly the same thing. And Copilot actually helpfully did it for me, except we're not going to do the decrement when we take the note value because we want
to check whether the note value is equal to zero is greater than zero because if it's zero, we want to revert with some helpful revert message. Counter value is zero like before. And then we create our new note and decrement by one. And then we deliver on unconstrained onchain. And that's it. Okie dokie. Chokey. Oh, there we go again. So far we have done a lot. So I just want to do a little recap and then we're going to get on to making some tests which is pretty fun actually. Um I think that our tests are yeah pretty exciting. I don't know why. So what have we done so far? you have learned how to use macros to inject code. Don't really
need to know what that means. If you are a bit curious about macros, then on updraft there is a rust course that will teach you about all these primitives like traits, macros, crates, packages, blah blah blah. In my opinion, you can kind of get by without really knowing these things and what they need to do. You kind of just need to know that they're like little annotations so that the compiler knows what what it is that you're doing. But of course if you want to know more then you can go and watch those courses or you can just ask AI to be honest and I think that it'll give you hopefully a satisfactory answer. If it doesn't then just keep querying it just keep chatting to it. So we learned how to install and import dependencies specifically the Aztec dependency. We then learned how to declare a contract
for private smart contracts on Aztec using Aztec NR. We then imported the specific parts of the dependency that we needed including using a uint note into our contract itself so that the contract has access to all of these different things. We then learned about the storage which is where we declare all of our storage variables and we have this context which allows the storage variables to manipulate the context which is just where all of the temporary things that are going to change during the duration of the function call are stored. things basically side effects if you're creating any notes nullifying any notes etc then it will be stored in the context for private context and in terms of public and utility just a little bit
different like we saw but you don't really need to worry about this it just you know injects the information that we need and allows us to update information we then declared two state variables a public counter which was a map container so that on the blockchain we can see an address associated with a counter number which is stored in a public mutable which says that it's a public state variable which we can change and the data type is a U128. We then declared a private counter in storage as well. And instead of a map because we don't need to be able to see everyone's address and associated counter, we instead have an owned private mutable, which means that this private mutable state variable, each one will have a specific owner. And
a private mutable is just of course a private state variable that we can change and implements owned state variable, which means that it needs to have be wrapped in this owned state variable type. And this takes a note type, which for us was a uint note, which just wraps the U1 128 data type into all of the information that we need to make it into a valid note, including functions to be able to create the note hash and the nullifier. Then we I mean, we didn't do it in this order. I'm just walking through the contract in order.
We have an initializer, which we annotated with this initializer macro, and we made it external public. We could have made this private by the way. And so this function can now be called from the blockchain itself on the sequencer. And inside of this initializer, we incremented the public counter for the person who called the initializer. We learned how to write public functions to increment and decrement the counter for a specific person, which meant that we learned how to read public storage variables and write to public storage variables. We then did the same exact thing in private which was a little bit different because we have some different functions associated with the storage
variable because of course it is private which means that we're working with notes. So we don't have just normal read and write instead we have unwrap, initialize or replace and unwrap or which is where we either read it or set it to a value and initialize or replace which either of course initializes the storage variable or replaces it with a new value. And then we learned about message delivery to deliver our note to the recipient. In which case for this example it is the message sender. So we could do it unconstrained because we trust ourselves to be able to send the onchain message without needing to constrain it. So without needing to prove that we have the correct receiver recipient and also the correct encrypted
message. So now that we have done a little summary, I hope this makes a bit of sense. Don't worry if you're still a little bit confused. I don't expect you to understand everything a completely 100% if you feel like you're just kind of hanging on and coasting by that is absolutely okay because the more contracts that you write the more you familiarize yourself with Aztec NR the more contracts that you read even the more you are going to become familiar and understand what is going on and all of the concepts of you know notes and nullifiers and note hashes and merries all of these things will start to become more and more familiar the pixie why you need to encrypt the notes why you need to share them on chain, how you can share them offchain, all of this is going to start to become second nature.
So, I just want you to keep tagging along, keep writing the code along with me just for these couple of sections, so up to the end of this course and then hopefully you're going to feel it a little bit more like you know what's going on. And if you don't, that is your chance to start writing some actual contracts yourself. But anyway, we're not there yet. So, let's write some tests. I do tend to go on some tangents. Testing, testing. One, two, three. Time to start testing. Okie dokie. In AztecNr, testing is a little bit different from what you have seen in Solidity if you're a Foundry Stan, which I am, and I'm kind of hoping you are too. Anyh who, for Aztec NR, rather than having a s a test folder
outside of our source folder like this, which I'm not sure why we put it out there, probably just have it. We need to put this test folder inside of source like this. And the reason for that is because it's a crate. So anything inside the source folder is part of the crate and anything outside is invisible to the compiler. Unless we import the stuff to main, it also won't be visible. So we will need to do that as well. But we also need to make sure that everything exists inside source. So we'll have this folder called test. And we're actually the way I like to do it is to have a separate test for each function in my smart contract and then test all of the different pathways through the function itself. So what things do I expect to
revert? What things do I expect to pass etc. And then I will have a file called test.nr outside of the folder that will sort of add all of the files inside test inside it. So then I can just in main do oops mod test like this rather than having a long list of mods and mod just stands for module because code that exists outside of this main.nr is called a module. It's so that we can have nice code sort of structure and be able to write functions and logic outside of this main.nr. And this is basically like importing those other files except it's just including those mods. So in here we
will have mod um and what the function calls uh increment public. It'll be mod decrement public mod increment private mod decrement private and then we'll also have a mod called utils and inside utils this will be like our setup function because we need to actually deploy the smart contract and like set up the initial state things like that. So the same as you'd have a setup function in foundry you basically just put that in its own file. So let's create those files. So we've got utils NR. We have increment public.
We've got a new file increment private dot decrement nr and decrement private and they are going to be our test files. So let's start with utils so that we can actually deploy and set up the initial state. So the first thing we're going to want to do is we're going to use the use keyword. We're going to have a crate and that is going to be our counter smart contract because we need this file to be able to see the code see the actual smart contract so that we can use it.
We're also going to want to use Aztec. And inside here, we're probably going to want to use the test helpers code on test environment. Test environment. And this is like your board standard library test. It just has some helpful functions and an environment to be able to test your smart contract. I'm not going to go into too much depth about this test environment because you don't really need to know, but essentially it's like having the local storage and local execution environment and then also a node to be able to execute the public stuff and then collect all of that state together. So what we're going to do is actually create our first test, our first helper function. So that we're
going to use the pub keyword so that we can use it inside of other files. unconstrained. Constrained because everything in tests doesn't need to be proven. We don't need to create proofs for it because we're literally just running functions. Fn for function deploy counter contract. And this is going to send back to us the test environment and an Aztec address which is the address of the contract itself. So we need to import Aztec address which is exactly what copilot is saying protocol address Aztec address and then we need to create the test environment. So we go let mute because we want to be able to change the
environment env equals test environment and then we want to run the function inside here new. So what's happening here is we're creating a variable called environment which we're saying is immutable. So we can change and modify the elements inside of this to test environment type. So we're creating a new test environment which we're naming env. And this allows us to do things like create accounts for testing. It allows us to deploy smart contracts. It allows us to execute functions. Now we want to have an actual deployer for our smart contract. We're going to have an owner. So we're going to say let owner because obviously they're going to have a head start if once we initialize
equals env. And then we've got a helperful function called create contract account. We also can do create light account which is just rather than it being a smart contract it's just for testing. We're going to do create contract account. Then we are going to create the call interface. So this is a little bit strange but effectively we need to create the interface with which we're going to call when we do a function call. So of course when we deploy our smart contract we need to call the initializer. To be able to call the initializer we need to know what that call interface is going to look like. And the way we do this is we use the let keyword to create a variable.
Initializer call interface. So just the name of our variable equals and then on the counter contract we want to get the interface. So the functions on it and then in the interface we will have all of the different functions and the function that we want is called initialize public and it takes an owner which we've already created. Oh does it? No it creates a head start and an owner. So the head start we're going to do is going to be actually just zero. Then we actually need to deploy the smart contract using the initializer. So we're going to let counter contract address because it returns an address equal env dot. Then on here we have a
helpful function called deploy. And then we need the path which is going to be inside of target and it's inside quotations for the path. And then the name of the contract is just counter. And then we do a dot to say on this thing which is deployed we want to call a function. And then the function is with private initializer with public initializer or without initializer. These are the three options when you deploy. And we want to do it with a public initializer. The person who is calling this is going to be the owner.
And then the initializer call is the initializer call interface. And then we want to return back the test environment and the counter contract address. And because this is a return, we don't need a semicolon. And I'm missing a semicolon here. And that's it. We have now created a function to be able to deploy our counter contract. Let's now compile everything. Let's remove these. Aztec compile. Probably going to have some warnings or some errors, but we didn't. So everything is okay. Let's now continue creating some functions. Now, we are also going to want to be able to read our public counter value and our private counter value. And on Aztec, no
variables, even if they're public, come with default getters. You can't just like do the my contract private counter and then pass in the address. That doesn't work. So, we actually need to create some getter functions for these two variables. And we're going to do that in the next lesson. All righty. Let's make some read functions inside of our actual smart contract. Now, the first one that we're going to do is the public one and then we're going to do the private one because the private one is a little bit more interesting and a little bit cooler. So, but that also makes it a little bit more difficult to understand the first time round. Anyh who, we need to be able to call this function
externally. So we're going to make it external public because we want to be able to call it from public to read a public storage variable. Now the other macro that I want to introduce is view. And this is basically saying that we just want to be able to read state. We don't want to write to it. We're going to use the function keyword and we're going to call this function get public counter. It is going to return a u 128. And then we need to exactly as it says read self got the storage strct get public counters read the value at this key which is the message sender who is calling this function and then do read and that's it. Now let's do the same thing. Oh I just accidentally deleted my
semicolon but for private and this is a little bit different. So we are going to be using a new macro and that is going to be utility which we have introduced very very slightly earlier. Well not I think slightly is the wrong word here. What am I looking for very briefly? Thank you brain external because we still want to be able to call it externally from our pixie utility and utility functions are basically where we're saying we don't need to prove anything because we're not doing any logic. uh or modifying any state really or just doing anything really where we need to create a proof. We don't we're just querying the state in the pixie. So
yeah, we don't need to create a proof. That's it. And because we're not creating a proof, it can be unconstrained. And this unconstrained is a noir thing. It's not spec. This this macro is obviously Aztec NR, but this is a noir thing. We're basically saying this function is not constrained. We don't need to create proofs for this. And we're going to call this get private counter. And it's not going to take any arguments because we just want to get our own. I mean, actually, to be fair, for public counter, we could just get someone else's. Yeah, we could do that. We could we can pass in an owner. This is an Aztec address. And here we're going to get the make the key the owner.
We could do that because anyone can read this public counters for some random person. Anyway, let's carry on with private counter. This is going to return again a U128 because we want to get the counter value and then we need to so there's one interesting thing about the logic here and we need to check whether our counter has actually been initialized or not because if it's not been initialized this call will revert and actually we just want it to return zero if it hasn't been initialized yet. So we can say let counter equals and this is where we're going to do the read self dot storage counters. Oh, it's private counters isn't it? Private counters. I'm going to do because it's got do app because it's ownable. So at
this owner which is going to be self message sender. Then we're going to check whether it has been initialized yet and otherwise return zero. So if counter dot is initialized then we are going to dot is initialized and actually this is incorrect. This should be view note. As you can see here, there are two different ways to view notes or get notes. You have view note and this is the utility variant. And because we're in utility, that's what we need to use.
And then we also have get note and this is the private version and it is constrained which means the extra information which needs to be used when proving its existence. Things like the note nons, the note hash etc. also needs to be included. The explanation is here. And then we do dot value because we've got the note here. Well, here we've got hang on. No message sender. Maybe it doesn't need curly braces. Ah, we can't do message sender in utility. So this we need we still need to pass in an owner. Owner as address and this is going to be owner and we need to import view. So wherever those are I think here we can put view maybe. Yeah. Now this will of course return zero for any owner other
than ourself. But that is fine because we'll just pass in here. It's kind of a dummy example anyway because we could make increment and decrement pass in an owner here and we could have like a local version of our accounter for other addresses. It just it doesn't really make sense but it's kind of yeah like an arbitrary contract like this. This contract is kind of pointless anyway. So it doesn't really matter. It's more just to get you used to the different functions on the different variables. Azure I also just wanted to show you this quickly. So in different contexts, different state variable types have different functions available. So I'm looking right now at private mutable.
And if we look at in a private context, then it has available the functions get initialization nullifier which is not public. So you wouldn't be able to call this on the private state variable. But we do have initialize which we've used before. Actually, have we used it? I'm not sure. Replace. Initialize or replace which we did use earlier. Kind of combines the two of course. Get note. And then in utility we have different functions available because of course we can't access any sort of transaction information like the message sender. So instead we have the public functions is initialized and view note which are the two functions we've actually just used.
And the view note will just return the note. And then to access the data inside the note, we have to do value to get that actual strct information which was value which is the variable name inside of a note which is the note type that we have initialized our private counter with. I hope that makes sense. I just kind of wanted to do a little sub segue to say that. Now we have two read functions. We can now use them in our test because we were currently creating utils. So let's continue creating some tests. Let's start with increment public. We need to get utils and then and we also need to get the counter. So we can use create the other crates in our project.
We want test utils. Uh and the other crate we want we need curly braces because we want multiple is going to be counter. So all of our tests are going to start with this line. So I'm just going to copy and paste it into each one like this so I don't forget. Then we are going to use the test macro which we haven't yet seen. And this basically just says this following function is going to be actually a test. And we can just keep using unconstrained because all of the functions don't need any proofs. So we're going to create a test to check that the incrementation actually works. So we'll call it test increment public. This doesn't need to take any parameters. And then inside
here we need to call that deploy counter contract utils function. So we're going to say let and then we want to get that env. And we need the counter contract address exactly as it's saying like this. The other thing we need here though is we need to say that this variable is a mutation. So it means that we need to be able to modify this test environment. You know, modify the storage inside of it, etc. Then we're going to create a user that is going to call the function. It's going to be different from the deployer.
And we're going to do envre light account because we don't need the full shebang. We could do contract account. It doesn't really matter too much. So we're going to get the initial value of the counter. Now in public, of course, this has a default value of zero. So hopefully it should be zero. So we're going to say let counter before because it's before we done the incrementation counter before equals and we need to call that get public counter function equals and then on the environment we have a helper function called env view public. So we want to view a public function and then this function this takes the function that we want to call.
So we got counter oops I should put caps on. So we've got the counter interface, the counter mod and then at and we've got the contract address contract address. Actually, this is called counter contact address, isn't it? Counter contract address. And then we're going to do dot and then the function get public counter and then the owner is going to be the user. That's it. Then we need to do an assertion that the counter before is equal to zero, the default value because the initial counter should be zero. Then we need to do the commentation. So we're going to do the env dot call public from the user and the call is going to be counter at the counter contract address increment
public. Then we need to read the value after which is exactly the same as the line before. And then we need to assert that the val counter after is equal to one. I'm also just going to assert that the county after oops that the counter after is equal to the counter before plus one. I mean we could just do one of these assertions but I'm just being verbose here. Right. So now let's compile. And now let's run Aztec test to run the tests. There should only be one. And it's passed. Amazing. You've just written your first Aztec NR test. and it's passed for your very first Aztec NNR smart contract. So, this is really, really cool. And now we're going to do
the exact same thing, but we're going to do it for private and we're going to do it for for decrement. We're almost there. We're almost at the end. And then finally, we're going to be deploying to test net. So, we're going to create this function or logic to test in private. So, we're going to use the test macro again to say that this is going to be a test that needs to be run. unconstrained function test increment private. Then we're going to do exactly the same as before. Let the mute oops environment test environment counter contract address deploy the counter contract. Then we can do the user again create light account except now we want to get the private counter before. Now, of course, we have
already in our contract created this utility function to get the private counter and we do have a default value otherwise it would have reverted. So, we don't need to worry about reverts because we already have a default value that we've set ourselves. Let's go back to increment. Um, so we need to create do let counter before equal to env. And then how do we call utility functions? It is called what's it called? What is the function called? Execute utility I think it's called but I'm not seeing it here. Execute utility. Sure. And then the call that we want to do in utility is counter again counter dot at not sat on the map at the counter contract
address. Counter I don't know why I made this such a long name. Counter contract address. [snorts] And then we want to say the function that we want to call is dot get private counter. Again, pretty easy because this is not actually a private call. This is a utility call. Now we need to assert that the counter before is zero. Then we need to do the incrementation. Increment the private counter by calling a private function. call interface equals counter colon.
Wait, I didn't do a colon here. This needs counter contract address. Then the function we want to call is increment private. Now we need to actually do the call. So we can do env.all private and this takes a from and then the call itself. So the from is going to be the user and then the call itself is the going to be the increment call interface. So we're saying this is the actual call that we want to execute. Then we're using this call private function on the test environment to actually call and execute that private function. Right? We need curly brackets here just to end the function. Then we're going to get the new counter value afterwards which is going to be exactly the same as before. And we're going to say counter after should be one and it should be equal to counter before plus
one. Now I'm going to Oops. Let's just finish that off. Ah, we need to pass in here the account that we want to get the counter for because of course in utility we couldn't use message sender. So we can just pass in the user. This should now work which it does. So let's run this test now as tech test running two tests and the two tests pass. So we now only got two more files left to write and that is decrement public and decrement private. So let's do that now. As per usual test unconstrained function test decrement public. We're going to need two tests. We're going to want to test that first. If we try and decrement when the value is already zero or hasn't been set, it reverts with the correct message. So, if we go back to our main
file, decrement has these assertions that should be hit if the value hasn't been set or it's zero. So, we want to check that that does actually revert. And then we also want to check the happy path. Those are the two things that we're going to do. So, we're going to have two tests. So we're going to have another hashtag test unconstrained function test decrement fails if counter is zero. Oops. Whoa. I don't want it to write it all for us cuz we are trying to learn. And then we're going to do some curly braces like this. So in the next lesson we're going to be writing this function.
Okay. Let's do the failure path first just because I don't know it's a little bit more interesting I guess. I just fancy doing it. So the way we can check that something fails is that we actually say we want this to fail and we do that with braces like this inside test inside the macro and then we have this helpful little thing called should fail with and then we can say what the actual message is that we expect it to fail with. So in here I can't remember what we said it was going to be but main public decrement counter is zero. This one here. So we go back to decrement public and we paste that guy in there. And then we need to write the actual test itself.
So we need to do decrement. So it's going to be pretty much the same as increment public. So actually should we just should we copy all of this? No, because we need to decrement. What am I talking about? Well, we yeah, I mean, sure. All right. Let's copy it and then modify it. I like doing it like this. So I'm going to teach you how to do it like this. Right. So we've got the environment and the counter contract address because we deployed it from the utility function. We've got a user. We've got the counter before. We assert the counter before is zero. And then we call public. And this needs to be decrement.
Decrement public. And we don't need counter after because we don't expect it to pass. And then actually, you know what? That's it. Oops. Let's pull up the terminal. And we're going to save this. And then we're going to go Aztec test. Now we've got four tests to run and four tests pass. Don't worry about this contract level blahy blah. That is because the default log level has changed. It's hanging here on these pending partial notes. It's a bit annoying. So I'm going to I'm going to report that. So hopefully that won't happen when you guys are running this. If not, then you're just going to have to hit control C to make it exit that. Right. So we've done one function call. And now we need to do the exact same thing, but we want it we've done one test, but now we need
to do the exact same thing, but instead of wanting [snorts] it to fail, we want it to pass because we actually before we do decrement, we need to call increment. A little bit strange, but we're basically saying like I want you to already have increment public uh a value of one and then decrement from that and then check that the value is zero. So I'm actually going to do two assertions just cuz why not. I'm actually kind of checking the increment works at the same time in a sense. Actually we need to read the value again as well. So counter before we could do like counter during counter after something like that. Counter during assert that counter during equals one. We don't need to do the other one.
It's not too much of a big deal. Let counter after equals that. And then we want it to be equal to zero. We want counter before to equal counter after. I mean we could do like you let counter before counter after equal equal counter during minus one or something. I mean this is a little bit overkill but you get the point right. So now let's run that again tech test and four tests should pass and they do right c control back to pull that down.
Okie dokie. This is pretty much the same as increment private except so if we just copy this and chuck this in here except rather than let's walk through it. Okay, let's just walk through it. Decrement private, we're going to deploy it. We're going to get the private counter using the utility function. We're going to check it's equal to zero. We're then going to do the decrement call interface because remember we have to do that when we are working with private functions because this envir uh takes the person who wants to call the function and then also the function interface. Then we use this execute utility to get the private counter
during actually you know what we also need to do an incrementation beforehand. So this actually does need to be increment. I was wrong. Incre increment private. And then we need to change this to increment. Increment. I'm not going to do the middle assertions or I don't know. I'm going to make this after after increment. Call interface. This needs to be increment. Oh my goodness. Escape. I don't want that. Right now we need to do this again because this needs to be increment. increment. So I'm going to do this whole thing again, but I'm going to do decrement because we need it to be equal to zero, then one, and then back
to zero. So if we do hold down options, click, click, click, uh, options back, and we're going to call this increment. Nope, we're not options back. Decrement. And then, oh I forgot that one. Oh, oops. I forgot that one. All right, decrement private. Now we've done the decrement. Then the counter after is equal to that. And then we want the counter to after to be equal to zero. Counter after to be equal to before. Oh, we need to check that the counter. Yeah. Okay, fine. Sorry, I'm being a bit chaotic here. Let counter during equals the utility function call. And I also want to check I mean this is a bit bit silly now, isn't it? Assert counter after equals counter during minus one.
Why are you not working now? Command A, command X, command V. Go out and save. See if the error is still there. It is. Oh man. Decrement private not type counter. Right. Let's have a look at main decre. Oh no. Decrement. [laughter] Right. Let's go again. Pull up the terminal. Aztec test. It failed. And the reason it failed is because we actually need to compile it first because it doesn't automatically recompile. So Aztec compile because we actually changed our contract itself. Therefore, we need everything in our target folder to be updated. And then we run again as tech test. And hopefully our five tests
should pass. Oh, come on. Dive test passed. Okay, final final final test. We're going to copy this whole dang thing and we're going to check. Uh we aren't going to do this incrementation. And so we don't need this counter during either. And we are going to decrement do the call and then we don't need any of this stuff. We just expect it to revert. So in here we're going to do exactly what we did in decrement public. So I'm actually going to just copy that. Oops.
Copy decrement private. And I'm going to put that in there because the test should fail with counter value is zero. And we actually need to change the name of the function too. Test decrement private fails if counter is zero. So what we done here? Got the test environment. We've got the counter contract address from our utils function. Deploy a counter contract which was our setup function. We got to name this setup by the way that wouldn't made a difference. Then we've got a user to be able to do our things with. This is like um make adder if you like. I don't know why I didn't say that before. Counter before we've executed this utility function to be able to get the private counter value for the user which will default to zero because we made it do so in the actual contract. We've done
an assertion to check that the value is zero. We've then attempted to decrement by creating the call interface for the decrement function. So on EMV we do call private which needs to take the from address and the call which can be obtained by calling the appropriate method on a contract type. So we created the call and then we called it and that's it. And then we're going to run the test. Don't know why it's doing this. Aztec test and hopefully six tests should pass. Running five tests. One, two, three, four, five, six. Huh? Where is our test gone? One, two, three, four, five, six. Should be six tests.
Test test. H. Let's just run Aztec compile again. Maybe. Aztec test. Running six tests. I don't know why that happened. That shouldn't have happened, but okay. So we've got six tests now which are all passing and that is it. So now we've done literally everything. We've wr written our first smart contract. We've learned all about how it works. We have written our if it does this by the way the um Noir LSP. Just hit command A, command X, so select all and cut all. Command V to paste and then command save. And you should get a more up to-date feel as to whether there actually is any problems. Um, which I
know there's not cuz it's compiling. I know there's not. So, I'm not sure why it's shouting at me. Decrement, increment. They all exist. Sometimes it just screws up a little bit. And that's okay. We all do that sometimes. Anyway, so in the next and final lesson of this section, we are actually going to deploy our smart contract. Okie dokie. We are going to start deploying to test net. The first thing we need to do is update the version. So in your NO NOTML, you need to update this tag to be v4.2.0-tech NR- RC.2.
I know this is a bit of a mouthful, but you can just find and replace command F and then fill it out here. Um, and then the other thing we need to do is we need to make sure that our Aztec version in the CLI matches whatever is in our nargo toml. So, we need to run Aztec-up install. And then you don't want the V here. This is a double L. So, it's 4.2.0- Aztec And then this will install the correct version of Aztec including Node, Python, NGO, Foundry, all the things here. And then to check that you're on the correct
version, just do aztec- version to check which version of the CLI you're using. And this should say the version that we just installed. Okay, now we need to go inside of counter cd counter cuz I have done this in a bit of a funny order. And then we just need to run a tech compile to check if anything has changed in the syntax which it hasn't. And now we compiled with the correct version. So we can go ahead and start deploying. You should use the MPC server and clause to check for any API changes and let it help you update your code to the latest version because it's very good at this.
And then you should also use the migration notes in the documentation if this doesn't work. Remember to always verify whatever AI is say saying to change in your code because you don't want it to change it to something that doesn't work properly. And then obviously of course if you're testing your code properly then you should have no issues because the test will just fail if it changes it incorrectly. although it might then just go and change the tests or comment them out or something stupid. So just make sure you're checking everything that AI does. So we need to do two things to deploy to test net. First of all, we need an RPC endpoint to connect to the network. And then secondly, we need to create a wallet and we need to deploy that wallet to manage and use our keys and be able to deploy the smart contract from and
also manage our Pixie for private data. So the first thing we need to do is create an RPC endpoint. You can go to the docs to see a tutorial for how to deploy to test net. It's going to be slightly different just and I'll explain a little bit about how it's different shortly, but you'll get all of these commands from there. Export node URL. This is just setting as an environment variable equals https colon/.testnet. Let's just check this correct. labs.com.
Now, we need to create our wallet. And to do this, we're going to use a handy little CLI wallet, the Aztec wallet. There are also other wallets like browser and moment bell wallets being developed, but this is going to be the easiest for your development for right now. So, we're going to create an [clears throat] account, but we don't actually want to deploy it. We want to create the keys and calculate the address, but we don't actually have any fee juice yet in the wallet, so we can't actually deploy it. We could use a sponsored fee paying contract. This is the easiest way and actually that is the main difference with the docs. So in the docs the testnet tutorial is going to show you how to use the sponsored FPC.
But I want to show you how to use a bridge application to get Aztec bridged to Aztec. So Aztec tokens bridge to Aztec so that you can get feed. And this is more similar to what you would do on alpha or eventually mainet. Alpha being the latest iteration of mainet. Anyway, so we need to create the wallet but not deploy it. So, we can use the command Aztec-wallet create dash account dash A for alias and we're going to call this one course or something like that. Or you could call this main if this is your main account or test or something, whatever you want. Then we're going to use the flag dashregister dash only. And this is saying we don't
want to deploy it. We only want to register it in our pixie. D- node dash URL. Suddenly thought, did I hit record? I did. That's good. Is it recording the correct thing? Whoa. Yeah, it is. And then we can put in the environment variable dollar node URL. And this will create a wallet for us. Now, you want to once you created all of these things, you want to sort of save this output somewhere safe just in case you lose it. but it is stored in your database securely. So let's now copy this address because we are going to be using this in our bridge. So now that we've created a wallet and made a note of the output, we need to get some fees and we're going to
to do this we're going to use Greg Juice which is a bridge created by one of the engineers purely for test net purely for getting fees from Sapoleia onto Aztec testnet. So if you head over to bridge.greguan Greggo Juice dot another coffee for me then you'll be able to bridge yourself some Aztec. Now you don't actually need to get any Aztec tokens on Sapoleia because this application will automatically mint them to you like a a faucet but then you need to connect your wallet. I am connected to my test Metam Mask and that's why I've had this first step already done.
And then here it says do you have an Aztec wallet? And you might be thinking here's where I can use the one that I just deployed. This is a little bit confusing and I'm going to kind of try and see if we can make this a bit easier when you get to it. But when you bridge the tokens, you need to claim them. And this is going to be the wallet. It's like a temporary wallet almost that is doing the claim on behalf of your account. Don't worry about it at all. Literally just click use an embedded wallet. Don't click I have a wallet. This is just basically like a temporary burner account. Then the recipient is going to be the address that you just created cuz this is the one who we want to receive the feed juice. And then we can click continue. Then there is a
fixed amount. That's the faucet amount to bridge and claim. And we click mint and bridge. And this will send a transaction from the L1 to bridge to the L2. So you need to click confirm the transaction and then [clears throat] wait for it to confirm on L1 and then confirm on L2. And then once that's all done, I'll come back in a second. Now you can see that we have successfully bridged and claimed our Feed Juice. So now we have Feed Juice to pay for transactions and we can now deploy our account. So to do that we use the command Aztec-wallet deploy- accounts to specify which account we want to deploy. And then this is the alias that we used before. And I said this was
course I think I called this wallet. Oh dear. Payment. How are we going to pay for this transaction? We're going to use the method equal to fee juice. I'm going to put all these commands in the GitHub repo associated with this course because there's quite a few of them. D- node URL dollar node ur and that's it. That should deploy our account now using the feed juice that we just brided and claimed. Okay, the transaction sent and it all worked. So now we can start deploying our contract, right? So now type out the command aztec-wallet deploy node url node URL from accounts colon course. This is just the account that you want to actually we could even do
that at the end there. We could do accounts colon course to get that address. So we're saying from this address the payment is going to be the method feed juice. The alias for this this contract that we're going to deploy is going to be counter. We call it like I don't know counter. We could call it my counter if you wanted to. You can literally call that whatever you want. Then we've got the package and then the name of the contract. Then init to say this is what the signature is of the initializer that I want to call because you could have multiple initializer. This defaults to just constructor by the way d- args and then we have a head start of 10 and then we're saying this is going to be the
owner or deploy deployer address. Hit enter. And this will now deploy your contract. Okay. Amazing. Now we have stored our contract at the alias my counter and it has successfully deployed. You have made your first actually no you haven't. You made your freaking third transaction to test net but you've actually deployed your very first smart contract. No you haven't. You deployed your second smart contract because your first one was your account. Now we can interact with that contract. Aztec-wallet. Actually I'm just going to show you the command at the end again. Okay. Aztec wallet send to send a transaction increment public- node URL node URL from this account contract address my counter and then we can hit it. We're sending a transaction to
interact with our smart contract that has been deployed on chain. And this is going to be of course a public execution which is going to execute on the sequencer which is pretty freaking cool, right? Like how cool is this? Now because we had a initial balance a head start of 10, we should expect the counter to now be 11. So we can in in a minute send a transaction to read the value. Amazing. The transaction has been proposed and so we have now incremented. So now we can call or simulate actually because we don't actually need to change any state. Get public counter. So we can use the command as tech-wallet
simulate. We want this function node URL node URL from accounts course contract address my counter logs accounts counts course but I haven't spelled anything incorrect that should be correct now and you see that the output is 11 which is exactly what we expect and now you have not only created your very first smart contract but you have also just deployed your very first smart contract and interacted with it which is really cool. So now in the next lesson we just need to do a little overview of what we have learned.
In this section we learned how to create our very first Aztec smart contract using the Aztec macro. We learned how to use dependencies and import them into our smart contract. We learned about the storage and how to declare both public and private storage. And we learned about the different state variable types and also about the different data types. We learned about owned state variables in private land. We learned about initializers, public initializers and private initializers and how to use the initializer macro. We learned how to write to private state. We learned how to write to public state. We learned how to read public state and we learned how to read private state. And we learned how to create different functions both in public, private and utility to read
private states. We then learned how to test in AztecNr and how to yeah create these unit tests and then finally we learned how to deploy a smart contract to test net. So now you have pretty much everything you need to know to get started. The next section we are going to go through how to create a token smart contract because things are a little bit more interesting and I think you're going to quite enjoy yourself because there's something called orwits which are like the uh approve and transfer from sort of pattern except a lot more general and a lot more cool and we're going to learn lots of different intricacies of how Aztec works. So buckle in. I probably would go take a break if I were you. Have some sleep, get some water, go to the gym, all the
usual stuff. Give yourself a nice little treat. I just had some chocolate. It was very tasty. And now I'm probably going to go for a walk and talk to some family. So I recommend that you do the same. And then once all the information is marinated, like I always say, then you can come back and continue learning. Okie dokie. Let's now move on to the final section of this course so far. I might add more sections in the future, but at the moment, this is the final section. In this section, we are going to be creating a token contract, and it's not going to be any token contract. It's not going to be an ERC20 like we're on Ethereum because of course, we are
Writing a private token contract
working with Noir and Aztec NR, and those standards no longer exist. But instead, it's going to be a private token. So, we're going to have private token balances and we're also going to have some functionality to have a public balance and then transfer that balance to a private balance. So, we're going to be able to have balances in both public and private land. And I just want to do this to show you the flexibility. And it also gets you thinking a little bit about optimizations and circuits and how you design contracts differently when you're talking and thinking in terms of circuits because obviously when we create private functions a circuit will be created for that private function.
Anyway, I am going to stop rambling and we're going to get started with creating our project. So we're going to use the Aztec CLI again. Aztec new d--c contract and then the name is going to be private token. Actually I am going to yeah that's the correct place in recording inside this folder. So underneath counter which we created in the previous section ah it doesn't have d- contract anymore. All right well let's delete d- contract. I think it's just automatically a contract now. Maybe you don't need the d-contract anymore. So, it's just Aztec new and then the name of your contract. That has changed since I last looked at it. You'll also see here that it added the Aztec dependency at this version. When I finally get the
stable test net version, this should just be v4.1.0, I believe. But I will probably add in a lesson in a second to show you how to install the correct version. And the syntax might be slightly different inside the contract, but I will try and just show you what's going on as and when it happens. Right. So in here we have our source folder with our main.nr which has our contract. We're going to rename this to token. It's already imported our macro, the Aztec macro which we've added to the top of the contract. And inside the nargo toml, we have the name of the contract, the type of the crate which is a contract, the author which you could add your name here, Kira Nightingale, and then we have the dependency. Again,
this tag will be a different version for you guys. And that's it. I'm going to clear that. And I'm going to close that. So let's go back inside of our token contract. So of course, first we'll have our imports so that we can use them inside our contracts. Then we will have our state variables, which in here we're probably going to need Oh, let's think about this. When we look at an ERC20 implementation, we'll have decimals. We'll have a symbol, a name, total supply balances. we won't have allowances and that's because things when we want to do an action for someone else in Aztec NR it's actually pretty cool in Aztec land because we don't just
have allowances which are specific to token contracts we have something called or thwits which we're going to be exploring at the end of this section then we will have the functions we will have a constructor we will need some read functions so reading all of these state variables We will want a mint function both public and private so we can have tokens in both of those lands. And then we'll also want transfer in public and private. We will also want the ability to transfer public to public, public to private, public to public, private to private, public to private, and private to public. If that makes sense. So I want to be able to transfer my tokens to you. My private
tokens to give you private tokens. I want to give my private tokens and give you public tokens and all of the different combinations. So, there's going to be a lot more functions than what you saw in an just a regular ERC20 because obviously we're dealing with public and private variations. So, let's get started. Let's create our state variable strruct. Let's create our storage. So, first of all, we're going to need to import it. So use Aztec colon colon to go inside of this macro curly braces cuz we're probably going to need multiple things. Storage colon colon storage. Same as before. Need a semicolon on the end of there. Oh, and this is inside of macros. My bad. Macros
curly braces. And then put this inside here. Oh, and it's a lowerase s. My bad again. Right now we need to create our state variable strct. So we got the macro storage strruct storage storage. This is the same as before and takes the generic parameter context so that each of the state variables knows which what context it has access to and therefore what functions are available. Now we're going to have a name which is going to be a public immutable. We haven't used this state variable yet, but this is just a public variable, which is not allowed to change. And the type that we want this to be is actually going to be
a field compressed string. So, we would just make it a string, but instead it's going to be a field compressed string, which is just a special version of a string to just make it better for practical use. You don't need to worry too much about what this is. Field compressed string, which we'll need to import. And then also, we need to pass in context. And this is from a separate package from Aztec. So we're going to use the use keyword again. Compressed string build print compressed string. And then we need to add that in our nar toml. So we'll add compressed string oops string equals curly braces.
Get equals quotes. And then it's exactly the same. Path tag equals just use the same tag again. This will be whatever the tag version I showed you at the start was. And then the directory equals now if we go in here then it's just compressed string. Compressed string like that. And we also need to import public immutable state bars colon colon. We're going to have multiple public immutable. We need a comma here. Right now we have the name. We need a symbol which is going to be exactly the same. Public immutable field compressed string context. And we need decimals which as copilot is helpfully suggesting should be a u8. So
like a uint 8. Now the rest of the state variables I'm going to add as we add in these functions because I think it's going to make it a little bit easier to just do it progressively. But we know we do need a name a symbol and the decimals. Let's create our constructor. Let's do some nspec. So at notice we're going to initialize the token and we're going to do this with the deployer being added as a mter because we're going to add these mint functions and we're going to make them permissioned so that only the deployer can call them and this is going to take a param token symbol and decimals. So we've got function constructor it's going to take these parameters. So it's going to be a name
which is going to be of type compressed string. Oops. Symbol which is compressed string and a and decimals which is U8. Then we need to set those things. So we need to access self dots storage dot name and then we look at public mut immutable. We check in a public context. Ah we haven't said what the haven't added the macros to this function. So first of course is going to be an initializer and you can choose whether you want this to be called from public or private. I am going to make this a public initializer. So I'm going to make this external public like this. Now I need to import those. So this is inside the macros. We've got functions
colon multiple here initializer and external like this. We need a comma here. Okay. So we've got storage.name name dot public immutable which we're looking inside has this function initialize which sets the initial value. So we can we can set the value using initialize and the value is going to be name and then we're going to do the same for the other variables. So symbol and decimals. Then we need to set the message sender as the mter. So we're going to create a new storage variable which is going to be mter. And again, I'm going to make this a public immutable. This is just the decision that I'm making that I don't want anyone
to be able to change who the mentor is. You could add a function to change who the mentor is and make it a public mutable or you could make it a private variable. It's completely up to you. And then this is going to be of type Aztec address. And we need to pass in the context. So then we also need to import Aztec address which is again inside of Aztec and the path is protocol colon colon we're going to need multiple from in here so I'm just using curly braces address colon colon AZtec address it's not addresses it's just address right so now that we have the mter we can set the m inside of the constructor self dots storage dominitialize mter this oh message sender self dossage
sender so her whoever calls this constructor function we'll set themselves as the mentor and that's it that's our constructor done okay let's now create a function to transfer from public to public shouldn't be too difficult function transfer public to public this is going to be an external public function because of course we want to be able to call it externally and we want it to be public because it's interacting with public state. Did I spell that all correctly? Let's also add some nspec notice.
Oops, that's not how you spell notice. Transfer tokens from a public balance to a public balance. Shouldn't be too challenging. And then in here this needs to take a from which is going to be of type Aztec address a two of type Aztec address an amount which is going to be a U1 128. Now we are going to allow anyone to call this function and say I want to transfer from this address to this address. And to do that you're going to be thinking they're going to need some kind of approval. Now rather than using approvals we are going to use something called orwitz and orwits are a little
bit confusing when you first get to hear about them but they're basic basically just you can think of them a bit like a signature which is added to the transaction information. So obviously you have like what function you're going to call, who's calling the function, all of your parameters, all of that information and then the signature to sign off the transaction. Now there's another piece of information which can optionally be added and that is an authorit which is basically like a little signature. It doesn't actually have to be a signature. It could be a password any kind of thing which says this action performed by this person. I allow them to do that on my account. I authorize them to do that on my account.
Now for this we need something called an orwit nons which just makes sure that we are able to authorize the same action on the same account for the same person more than once without them replaying. So an orwit can only be used once. Actually do you know what let's segue into the orwit video and I'm going to explain to you a little bit more about orwits and then you can come back once you understand that. In this video, we are going to cover one of the most important and the coolest mechanisms on Aztec authentication witnesses, otherwise known as Orwitz. If you've worked with ERC20 tokens on Ethereum and you're familiar with the approve and transfer from patterns,
where you can allow another address to spend your tokens, Orwits are Aztec's generalized version of that concept, but way cooler. An orwit is just a thing usually a signature but we call it a witness because it can be anything which says I allow this person or this account to do this action on behalf of my account. You can think of a witness as like a proof of authorization and we don't call it a signature because it can be anything like a password. It can be a signature of course or another mechanism. And there are two different types of orwits. Private Orwits which of course work in private and public orwits which of course work in public. They are a scheme for authenticating actions on Aztec. Let's just go through the problem
with the approve/transfer from pattern with ERC20s on Ethereum. So on Ethereum, if you have tokens and you want a smart contract to be able to move those tokens, let's say it's some D5 protocol, you'd have to first call approve to set the smart contract as a spender with a certain amount. And this writes an allowance into public storage. Later the spender, so the smart contract can call transfer from to spend tokens from your account and the contract will check that allowance mapping. This pattern is specific to the ERC20 standard. However, there's no generalized mechanism that allows you to authorize actions for third parties.
Orwitz are this generalized protocol specific for Aztec. Any contract is able to ask is this account able to perform this action for this account. Let's go into a little bit about how orwits work. So, as we said, there are two different types of Orwits. Private Orwitz and public orwits. And they both work a little bit differently, but the message which is authorized has the same structure regardless. Let's go into that hash structure. The message is made up of two nested hashes. We have the inner hash which as you can see here is a hash of the caller which is the third party which we want to be able to do the action. The selector which is the function selector for the function that we want them to be able to call. And then the args hash is the hash of the arguments to that function call. And
then lastly we have an orwit inner separator just to define that this hash is meant to be an inner orwit hash. We then take that inner hash and create an outer hash which is the message that will be signed. So this is like the message hash effectively. And that is a bidon 2 hash of the consumer. This is for example the token contract. It's just the the contract address that we want to be able to authorize the actions on on behalf of the third party. The chain ID, the version, these two are just to prevent replay and the inner hash. And then we also have an outer separator. And this is what gets signed or authorized. Let's assume for now on in this lesson that the witness is actually a signature just to make things
a little simpler. If I authorize Pat to be able to perform actions on my account, I would sign this outer hash. And then when someone actually calls the token contract using this orwit, my account contract will verify that that orwit is valid in the case of private orwits. In the case of public authorwits, it will actually check a public registry. Let's go into those two different types of orwitz. Orwits work differently whether the authorization is being consumed in public or private. Private orwits are verified through a static call to the account contract which actually created the authorit.
Let's say I am doing a deposit into a defy contract. That defy contract will need to transfer funds tokens out of my account for me. So I need to authorize the D5 contract to actually do so. Let's walk through the flow of how private auxits work in this example. So first I call the deposit function on the D5 contract. The D5 contract will then call transfer in private to move funds from my account to itself. The token contract will see that the message sender is the D5 contract rather than myself account.
So the from is not equal to the message sender. So it needs to check that my account has authorized this action. So the token contract will call a function assert current call valid orwit which takes the private context and then the account. So my account it will then recomputee the inner hash from the call context and then on my account contract it will call the function verify private orwit. In this function it computes the outer hash so the message hash. It then calls is valid impulses the orth witness from the pixie via an oracle call which is essentially just a call out of the execution into the pixie to get some data. We've seen this function before because this is the function which actually verifies that a transaction
signature or authorization is valid. So it goes through the same pipeline, but instead now it's taking an orwit and it verifies that this signature is the one that's expected in the smart contract or whatever verification mechanism you choose to check that the signature is from the actual account itself. If the signature or authorization is valid, it will return the magic value is valid selector. It'll do this rather than simply just returning true just to make sure that the contract knows that this is an orwit verification signal. This will then bubble back up to the token contract and the token contract will emit a nullifier to prevent the orwit being reused and the transfer will proceed. So there's a couple of key properties of private orwits that we
should just cover quickly. The first is that the authorit is never stored on chain. The signature of course was provided at execution time by the pixie via an oracle call. Secondly, the nullifier which is emitted after the orwit is used prevents replay and this works in exactly the same way as notes. And this nullifier is also stored in the nullifier Merkel tree. And so privacy is preserved because the only thing that's stored on chain is the nullifier once the author has been consumed which reveals nothing about the itself. It doesn't reveal what was authorized, who was authorized, who authorized it, nothing. I also just wanted to note the use case of private orwits. Private Orwitz because they are verified via the account contract. The transaction needs to be initiated by that same account.
And what do I mean by that? So you can't create a private author authorit. That's not what private author for. You could use public authits like this, but not private orwits. Private orits instead are used when you want to allow a smart contract to perform an action for you. Let's say it's a DeFi smart contract like a AMM that needs to transfer tokens from yourself to itself. In this instance, you'd still be the one sending the transaction. Maybe it was swap in this D5 contract like we were talking about before and then the DeFi contract needs to transfer the tokens from you to itself. This way the verification can still happen via your account contract because it's all
happening in your pixie. Anyway, I hope this makes sense. But while we are writing the token contract and using orwits, we will talk about this multiple times. So hopefully it will begin to make sense. Let's now have a look at public orwits. Public orwits use an orth registry. Here's how the flow looks a little bit different. First, let's say I want to authorize a token contract. Again, I would just call it the canonical orwit registry which exists on chain at a well- definfined address. And on this contract, there's a function called set authorization where you pass in the message hash and then a boolean say whether you want to authorize it or not authorize it. So the message hash will as we know say who we want to authorize and what we want to authorize them to do on which function. So which function with which arguments on which
contract later the consuming contract. So our D5 contract can call assert current call public authorit pass in the context and then pass in on behalf of which would be my account address. This function call will then call the authorit registry and the function consume which takes the on behalf of address which would be my account address and then the inner hash. The authorit registry will then check if that authorit exists. So that message hash for that specific account contract and if it's true it will return the is valid selector and then set the value to false so it can't be reconsumed and prevent replay. Let's go through some key properties again. So in this case the approval is of course stored in public in the orth registry contract so
it's visible to everyone. Replay protection is handled rather than using a nullifier but with a boolean which we flip from true to false. There is no signature verification during consumption because we're trusting the account that wants to authorize the action on their behalf is only going to add valid orwits to the registry. Additionally, the authorit registry has a reject all flag for each account which is like an emergency kill switch. So you can block all authorizations from your account. The orth registry also has a set authorize private function which allows you to add public authorit from a private setting and this is useful when you want to authorize an action in public without revealing the approval
until it's consumed. Now how do you actually do all of this in practice? Now most of the complexity is actually handled by Aztec NR which is really helpful and all you need to do to enable a function to be able to handle orwits is to use the authorize once macro. So as you can see here we've got a transfer in private function which uses this authorize once macro which generates this check at the start of the function. So if the from is not equal to the message sender then we call that assert current call valid orwit. Otherwise we need to check that the orwit nons is not equal to zero otherwise we revert. If the from is equal to the message sender the user is acting on their own behalf and no or is needed but the nons must be zero. How do you cancel an northw? Well,
in public, of course, you just flip that boolean from true to false in the mapping. And in private, you would just emit the nullifier associated with that orwit so that you can mark it as consumed before it's even been consumed. Let's also have a little look at how we actually create orwitz. So, let's say we want to perform some action on a token contract transfer in private and then on the wallet SDK there is this function create orwit and then this is actually included in the transaction execution request. So when another account tries to execute an action on behalf of your account, it will look in your pixie and try and find that valid orwit if you have the macro present at the top of the function. So in this lesson, we did a little whistle stop tour on Orwitz.
Authentication witnesses are just Aztec's generalized authentication mechanism. They replace and improve Ethereum's approve and transfer from pattern. There are two different types of Orwits. We've got private orwits in which the orwit is actually verified in the account contract itself via verify private orwit the function and then when an orwit is consumed the nullifier for that orwit is emitted and stored in the nullifier Merkel tree to prevent it being reused. We also have public orwits in which the orwits are stored in a registry where each account has a mapping of orwit to a boolean true or false. And to mark a public or as consumed, we just flip that boolean from true to false. And to enable a function in Aztec NR to be able to use orwitz, we
just use the authorize once macro. And if you'd like to learn more about Orwitz and how to use them, I encourage you to watch the tutorial on writing a token contract in which we are going to include Orwitz to be able to transfer from other accounts both in private and in public. And we'll also be testing it. So you'll be able to see how to actually create these orwits. Thank you so much for watching and I'll see you in the Okay. So now that you understand about orwitz, you can see that of course we could only use an orwit once. But what if you want to execute the same action, so the same function call on the same contract for the same account and you want to authorize them to be able to do that more than once. Well, we have to have some way to make the orwits
distinguished from each other. And the way we do that is using an orwit nons which we're going to pass in as another parameter. This is of type field. The reason it needs to be a field. You don't really need to worry about what a field type is. If you are curious, please go look in the noir documentation. In order to understand field types, you need to understand modular arithmetic. And I kind of don't really want you to get bogged down on that. So in order to use the orwit nons in the message hash, it needs to be the same type as what is needed for that message hash calculation. And that is of type field and we don't want to have to do that conversion later. So instead we just make the input of type field. And now we need to add the orwit macro
which is called authorize once. Now inside parenthesis we're going to say what we are authorizing from. So it's the from parameter which we're saying this needs to have performed the authorization and then we're also going to pass in the orwit and this is the parameter name that contains the nons for replay protection. So you need to pass in the parameter that needs to have authorization that must have authorized the call. Oh and this is actually correct. This needs to be outside here. So this is the parameter which needs to have done the authorization. This is the parameter name for the authit nons which we've called authorit. So if we called this like just nons then then we could just say this was nons like this. Oops not
bon nons but I'm going to call it orwit nons to make sure that someone reading this contract will know what it is. Now we need to import this orwit nons from the Aztec library. And this is inside functions because it's a function macro. It's called authorize one. And that is it. We've now learned how to use macros. Have I spelled this wrong? Yes, I have. And that is it. That will handle all of the orwit logic for you. So adding to the registry, removing from the registry, all of that business, it will handle it for you, which is pretty cool, right? Like you don't need to worry about whether it will see that this is a public function and then use
the public registry. If this was a private function, it would use the private way of doing orwits going through your account contract, but it will automatically take the orwit from the transaction object itself and then inject that to allow you to perform the action. All right. So now we just need to implement the logic. So the first thing we need to do is decrease the amount from the from address. Then number two, we need to increase the amount from wait increase the amount to the two address. Then finally, we need to emit a transfer event to say that we finished all of this. So we need to
decrease the amount from the from address. So this is going to be exactly the same as what we did with increasing. So let new_balance equals self.public public addresses dot at from read minus the amount. So we're taking this storage variable which is of type map. We're getting from the mapping this from address which will return a let's just escape that because it's giving me a suggestion which will give me a public mutable. So we're going to read that public mutable which is of type U1 128 and then we're going to minus this amount and set it to new balance. Then we need to set our storage variable. So in the public balances at this key write this new value in here we need to
increase the amount of the two address but we've already done that logic here. So we're actually just going to create an internal function because we want to reuse the logic. So this is going to be something new for you. We're going to use a new macro and that is internal. And internal again takes public or private because we need to know whether this is being executed on the sequencer or in the pixie. And we're going to create a function and we're going to use leading underscores to show that this is internal. We're going to call it decrease public balance. And then we're also going to create another one to increase public balance. So I'm actually just going to copy and paste that. Copy paste. And this is going to be increase.
So I'm going to take that logic that we just wrote to decrease the balance and chuck it in here. Oh, and we also need these values. So we need the from address and the amount and then for increase we need to take the logic. Oh we also need to where was that transfer decrease. So now we can call the internal function self.in internal this is how you call internal functions decrease public balance and we need to take the logic for increase. Then this is going to be self dot internal increase public balance to an amount. So two is going to be of type as stick address amount U128 and then paste that other logic in there and then we can use
increase public balance in transfer to public again. Now the problem is we don't have internal installed. We don't have that macro. So need to install that. And then finally we need to emit the transfer event which we've done from before. And that is it. We've now learned this new type of function visibility which is internal. And now there are only a couple more types of function visibility and they are the only four types of function visibility. We have internal and external. We have no public and we have no private because it kind of doesn't really make sense in this context. And also they kind of really don't do anything extra in Solidity. Anyway, they're kind of
useless because you should be more granular and specific about where you want your functions to be used. And we don't have inheritance in contracts in Aztec NR. So private doesn't make sense because private is just it can't be seen by inheriting contracts. It's just an internal function only for that one contract. So that's kind of redundant as well. So we have internal public, internal private, external public, and external private. And they are the four function visibilities. And that's it. You've now met them all. Now there is something interesting here. So if you'll be hopefully thinking here, what if my balance isn't enough? Well, it will revert automatically because there is underflow protection. There is no
underflow. Doesn't happen. We could add a little check here just to have a helpful error message, but we don't actually need to do that. Now, there is actually one other piece of logic which we're probably going to need to reuse, which we're definitely going to need to reuse when we're minting from private, and that is increasing the total supply. So, I'm going to actually put this into a new function, an internal public function again. Internal public increase total supply. And I'm going to put that logic in there. And then up where we had increase total supply, we can now use self.in internal to call the internal function on this contract.
Increase total supply the amount. So, we've actually just made things a lot cleaner as well. So, now we can start writing a private function. We are going to create a private mint function. So it's going to be external private. We're going to call this function function mint to private. And this will need to take a two Aztec address and amount U128. And then inside here we want to modify a private state variable. So we're going to need a private balance. Now private balances. Now the state variable type that we're going to be using here is going to be called a balance set. So before inside counter if we have a look oh what am I doing inside counter inside
source we had main.nr an R and our private counter was just an owned private mutable because we only had one value per person. But now we are dealing with tokens and because in private land we are dealing with nodes, we're going to have multiple different values associated with one person. Unlike with public balances where we'll just have a mapping of an Azri address to their total balance, in private they'll have a set of notes, a bit like how you have notes in your purse or wallet in physical cash. You might have a $5 note, a $20 note, a $50 note, whatever. And then your balance is adding up all of those notes together
introduction. So because we are going to have multiple different notes with different values inside, we instead need to use balance set. And this wraps another state variable type called a private set. So it's a set of different notes all collated together associated with one owner. Also, by the way, an owned state variable because each set of notes needs to have an owner. So we need to pass context to that. So this is how we declare it in storage. But I'm just going to in the next lesson just have a little look at balance sets so that we can understand it a bit more because it wraps another private state variable type called balance set. But then adds some helpful functions to be able to easily increment and decrement our balance when we're dealing with multiple
different notes. We don't have the whole balance stored anywhere. We don't have one number that represents the balance. We just have a collection of notes and we need to be able to handle them easily. Okie dokie. Let's have a look at NR and then we go balance set. So we're going to need to import it because it's outside of Aztec balance set source balance set. And so if we scroll down, we'll see that a balance set is just a strct wrapping private set. And this is a private state variable type. It's like a collection of this note type. So rather than just having one of this note type, there's a collection of them. A bit like a map, but there's no index or anything. It's just a collection of
these notes. Like a list of notes. And actually we could just have a very quick look at private set. So if we go inside Aztec source state rails private set and we scroll down. Well I mean there's a long explanation here. So I do recommend that you read it. So it's an owned state variable type but which means you enables you to read mutate and write private state and it means that you must write wrap it inside the state variable when storing it which we already know. So you'd use it a bit like this. The current value of the private set is the collection of all not yet nullified notes in accounts and notes in the set.
So you could use this for a balance. The reason we don't is because balance set is basically a private set. It's got some extra helpful functions to make things easier for you. So we can see it's an owned state variable which is why you need to wrap it in owned. And then yeah, so we can see here we've got the note type which means that it can be any note type you want. But a private set enables many notes of this type collectively form the private set at a given storage slot. So for one storage slot, there'll be many many notes stored there. So if we go back to balance set, we'll see that this specifically is a private set of UI notes. But if we scroll down, this is again an owned state variable. So it needs to have an owner. So each owner will have their own
collection of notes. But it has these helpful functions. Add, which enables you to easily add to your balance and deal with all of the note business for you, nullifying notes, creating new notes, and also sub, which allows you to, of course, subtract from your balance without worrying about notes. So, if we go back to private set and scroll down, the functions we have available in private, let's just check this in private context. Private context. Yep, there we go. Then we have insert to insert a note.
Pop notes, remove to destroy a single note, get notes, and we have view notes in utility. So, a little bit more complicated than just add sub and try sub. So, try sub is a little bit different. And we're going to be looking at tricub shortly because it allows you to do some optimization stuff by allowing a max number of notes that you want to consume to restrict your gate count which you can just think of as like restrict the size of the circuit. But don't worry about this. We're going to come back to this. We now have a balance set. Ah actually I created a nice little diagram for this. So here you can see actually this would be balance set. But balance set is private set just with those extra functions. So
you don't really need to worry too much about it. They're kind of the same thing you can think of. It's a specific type of private set that uses a UI note and has those extra functions. That's it. Anyway, so in public land, we had a map of an Aztec address to a public mutable of data type U. This is this shouldn't be U in this should be U1 128 because we're in Noir now. This is just literally saying this address is associated with this balance. This address is associated with this balance. We have the data type and it's wrapped in this public mutable to say what the state variable type is. But in private land we have the note type which is a uint note with a value. These are different owners. These four different block a bit like how we have these four different dresses over here with
associated balances. You don't have a total balance but you could have a collection of different notes and they make up your personal private set or balance set. It's not completely technically accurate. I showed it to one of the Aztec NNR engineers and he was like that's not completely accurate but it's a good way to have a mental model of how things are working differently. Rather than having one balance associated with your address, you have a collection of notes and they form your personal private set for your specific address and that's why we use that to get the address and then inside there you'll have your list of different notes. So if we go back to the code now, we are ready to start writing our mint to private function.
So let's start writing this mint to private function. So the first thing we want to do, of course, let's just make some comments. Actually, let's put on that spec in first. 1 2 3 at notice mints tokens to um oops to an address privately. So increment that private balance and then we need at param to the address to mint tokens to and the amount. So the first thing we need to do is we need to check that the caller is the mter. That's number one. Then we need to increment the private balance of the two by
amount. Please ignore all of the code suggestions. It's giving me three. Then we need to increase the total supply by amount. And crucially, this is a public variable of course. So here I'm going to put underscores around that just to make it extra sure because this is going to be a little bit differently because we're going to have to call our internal public function to increase the total supply. So it's going to be a little bit different. Why is it now complaining at me? Did I miss a semicolon? Oh, and I didn't import owned. That's what the problem is. So that is a state variable type and then we also need a balance set but balance set is outside of so we need balance
set equals I'm going to copy this whole thing cuz it's exactly the same except the directory is balance dash set go back in here and then we can use balance set use balance or set colon balance set hopefully now this will stop shouting at me okay so Now we need to check that the caller is not is the mter. So we can assert and this is going to be exactly the same as before. In fact we can just copy it. Now we need to increment the private balance self dot storage.private_balances private_balances at and then we're just going to take the two add oops to add the amount and then of course we need to deliver the note
and because this sender is not the same as the recipient we are going to do deliver with the method message delivery message delivery dot onchain chain constraint because as we learned about before we need to be sure that in the proof it's actually verifying that the recipient is going to be able to decrypt the note and that the note itself is in the message. Did we call it private balances? Oh, we missed the s balances. Okay. And also because we have these internal functions for increasing balances in public and decreasing balances, we're going to actually do the same thing but for private because
obviously when we do our transferrals, we're probably going to be reusing that functionality. So we're going to make internal now private function and we're going to put all of that logic inside there. And then instead of doing this logic straight here, I'm going to do self.in internal and then call that function and pass into an amount. Then we need to increase the public total supply by amount. And if I did exactly what copilot is saying here, now of course we don't need to emit an event. It was just suggesting I emit an event.
don't want to do that because we don't want anyone to know that I called this function. The problem here is that this function is public and I can't execute a public function while I'm executing my private function because this is function is going to be executed inside my wallet in my pixie. This function should be executed on the sequencer. So I need to make sure that all of the private stuff that I want to do has been executed before I do any public stuff. And so what we can do is we can actually incue a public function. We can say after you've done all of these private functions and you've accumulated all of the side effects, so all of the things you need to add to the Merkel trees by creating and nullifying notes, then here are the function calls that you need to execute in public. So rather than
internal, it's inq. Now the problem with inq self is it needs to be calling an external function because you're on the sequencer now and you need an external function visibility to be able to call cuz the sequencer can't just call an internal function that can only be called from inside the contract itself. Internal functions are essentially inlined into external functions. To be called they have to be called inside of an external function. They cannot be called from the sequencer in the case of public functions and they cannot be called in the pixie in the case of private functions unless they're called as a consequence of calling an external function as I said because
they're inlined. So internal private functions can only be inlined in external private functions and internal public functions can only be inlined or called in external public functions. I hope that makes sense. So we need to expose an external function which calls this internal function. Sounds a little bit confusing but don't worry about it too much. But I hope that makes some sense. So because we can't call this increase total supply from in private because it's a public function even though it's internal it's public and we can incue public functions but the incued functions need to be external because of course they are called by the
sequencer. It can't enter this contract first. It needs to have an entry point to be able to enter this contract. So we're going to create an external public function which will expose the logic inside that internal function. It's going to be called function increase total supply and I'm going to call this internal just to say that this is exposing the logic for an internal function amount u 128 and then inside the body I'm going to self.in internal increase total supply by the amount.
However, at the moment now, anyone can come along and increase the total supply by anything they like. So, instead of doing that, we are going to add another macro which is called hash only self, which means that only a call initiated by this contract can actually call this function. And this is literally just because we are in quueing this function call. So, an incued call cannot be an internal function. It has to be an external function but the message sender because this is incued from the contract itself. The message sender will still be this contract which means that this only self macro will confirm and check whether the message
sender is the contract that this function is defined in. So we need to add that macro here only self. Now again the documentation on this is actually really really good. It's I believe in the API reference. So if you go to macros, I know this is a little bit hard to read, but just bear with me. So macros, Aztec, oh no, I don't think it's there. Functions, sorry. And then we need only self. Then the use cases for this are sort of explained here. So public functions can only be externally called by both private and public functions. Publicto public only self calls are rare and not very useful. So
that's not what we're doing. A private function that needs to perform subpublic check or follow-up public action which is what we're doing. We're increasing the total supply can do so by incuing a call to external only self-public functions. A classic example is a private mint function in a token that would require an incued public call in which the total supply is incremented. And when we incue a call, we can't just use internal functions. So you might be thinking, hey, if you could only be called by yourself and it's external, surely that's an internal call. No, it's not because an incued call will always be external. Therefore, we need to restrict the message sender to be only this contract. I really hope that makes sense. So that's it. We've now created the mint to private function. Now we
need to create a function like we did in public to transfer a public balance to another public balance. We want to create a function to transfer a private balance to another private balance before we finally do public to private and private to public and then we will be done apart from obviously writing all of our getter functions. And the next lesson is going to be a little bit more complicated because we are going to be dealing with circuit restrictions and some recursion to make the code more optimized for the fact that private functions are of course transformed into circuits.
Okay, let's now create our function to transfer from private to private. And things aren't will not be quite as they seem. Let's first create some nasspec. So we got a notice transfers tokens from a private balance to a private balance and then we'll have a param. The from the address to transfer tokens from to the address address to transfer tokens to and the amount of tokens we want to transfer. And then additionally we are also going to enable or switch again. So as copilot is helpfully suggesting we will use the authorize once macro and we're going to make it external private.
And then we are going to call the function transfer from private to private from Aztec address to an Aztec address amount U128 orwit nons of type field. The first thing we want to do is decrement the private balance of amount. And then number two, we want to increment the balance of the two a bit like we did in transfer public to public except we're not going to omit an event of course. So number one we will do self dot storage.private_balances.
This is owned. So we need the address of which we're going to access which is from. And then we can do dot sub. You might be thinking we do dot sub anyway and then that will be the value amount dot deliver message delivery onchain constrained to make sure that they receive their message and then we do add in the same way and in fact we already have that increase private balances function which we can use instead self.in internal dot increase private balance to an amount. Now the there is a problem with this. Well, this is balances anyway. So that's the first
problem. The second problem is that this is not optimized because what happens if let's just go over to our little drawing board. We have a load of notes inside of our balance set. Let's say this is of value, I don't know, one. And then we've got another one of those. And we've got another one of those. And we got another one of those. And we got another one of those and so on. And let's say if I can zoom out a bit. Try and zoom out. Let's say I've got absolutely tons of these. But let's say I'm doing trying to do a transfer of 100 tokens. I will need to see whether my entire balance adds up to 100 tokens. So I'll need to go through
all of these notes and access the value. And if we go back to balance set, wherever that was, we've got sub, which actually already uses this try sub under the hood. And this takes the max number of notes. Let's look at what it says. Max notes is max notes parameter is used to fine-tune the number of constraints created by the function. The gate count scales relatively linearly with max notes, but a lower max notes parameter increases the likelihood of try subtracting us an amount smaller than the target. So effectively what's happening here is we're saying try and subtract 100 from this, but you might not be able to. And we're basically just saying I want to consume a maximum
number of notes. So let's say it's three. So I'll take my three largest notes. So let's say that you know some of these values were higher than one three largest notes and see if that adds up to the amount that I want to subtract and if not then tell me how much I was able to subtract. So this try sub function returns how much was actually subtracted. So the sum of the value of the nullified notes but this subtracted amount may be more or less than the target amount. So let's look at an example. If we go back to our set of notes let's say this one is a value 10.
This one is of value, I don't know, nine. This one's of value two. And then we have a load of other notes. Let's say we were trying to subtract an amount equal to 20. Now, what happens is it will order these notes in terms of size. So, it will take So, if we make this red, what it'll do is it'll take the first note and it will say, okay, so we've got 10. That's now not equal to the amount. So let's take the next biggest one which is nine. That's 19. That's still not enough. And then it will nullify this note and consume all three notes because we still if we take these first two it's still less than 20 the amount. So it will nullify this one
as well. So the total amount that it nullified amounts to 21. So actually the amount that was nullified in total even though we could make up the balance with all of our notes in general because it orders them in terms in order of size it will nullify a value of 21 even though the amount was actually 20. And if we look at try sub again obviously it takes this max notes let's say max notes was equal to two. If I just undo all of this let's now say max notes was equal to two. So it will first nullify this 10 note and then it will be like okay so I'm still under two notes nullified. So
I can look at the next biggest note and I can nullify this one. The amount that I've nullified is now 19. The value the total value. However, I've already consumed my two notes. So try sub is going to return 19. I hope this makes sense. So under the hood sub is using this tri sub. So depending on what this max note hash read requests per call is, there are instances in which we're not going to nullify the entire amount. We're not going to subtract it. Even though we have enough notes to be able to do so, we're not going to do it because otherwise this is like an infinite loop. Let's say we had an infinite number of notes. You would create an infinite number of gates. This
the circuit would become infinitely large and this is bad. So we can't do that. you have to have loops be bounded in no. So rather than doing sub which is using try sub underneath the under the hood which may in fact fail we kind of want to implement that ourselves but then also we want to recursively call try sub so that we can effectively create a new circuit each time. This sounds kind of complicated but I have done a little diagram to explain this to you. Effectively what we want is we want the decrease private balance function because we could take this logic and then put it into a function called decrease private balance exactly like increase private balance but the logic is going to be different. We want to
initially set the number of Magnus notes to two and the reason we want to do that is because the first call to try sub is the most expensive. It creates the biggest proof. Then in try sub we want to take the result. So the number of subtracted and we want to check is it larger than or equal to the amount. If so then two notes were sufficient and if subtracted is equal to amount we don't need any change. If subtracted is greater than amount then we need to do this equation. So subtracted minus amount to get their change and then we need to mint them that change because tric sub doesn't automatically mint the change. If subtracted is less than amount. So i.e. We haven't necessarily gone through all the notes the person
owns, but two wasn't sufficient. Their two largest notes didn't make up the total amount or at least the total amount. So we need to call try sub again. But we need to do this in such a way that it creates a new circuit. We can't just call it again. We need to exit the function call and we need to create a new function call. So we need to exit circuit and create a new one. So that's why so this means this call needs to be an external call and of course it needs to be only self because we don't want anyone else to be able to call that and these subsequent calls we can increase the max notes to 8. Now, I know this sounds super complicated. It becomes simpler as we write the logic.
This is why it's important to get used to writing this logic yourself because if you just got AI to write this, you would see the recursive calls and you'd be like, what are you talking about? Effectively, what we want to be doing is recursively calling try sub until the total value of consumed notes exceeds amount. Then we want to mint the person the change and we want to recursively call this function as an external call. And the reason we want it to be an external call is because we want to create a new circuit. And each function call, so each external function call is a new circuit. It's a new constrained piece of code. We don't want to just arbitrarily increase the number of gates, increase the size of the circuit
for one call. Instead, we want to exit and create a new one. And this stops things getting bloated and it is much much more efficient. So in the next lesson we are going to be implementing this algorithm ourselves. So now we understand why we need recursion. Let's actually implement it. So we know this line is wrong. Now for the sake of just making things a bit cleaner, let's actually make our internal function decrease private balance. So let's make a new function at the bottom here. It's going to be internal private and we're going to call it decrease private balance. It's going to have a from as address amount u128 self. And this is just the line from
before. I'm not going to read it out because it's utterly incorrect. And then here rather than doing this line, we can instead do self.in internal decrease private balance from an amount. But we know that we want decrease private balance to recursively call try sub. And then if there are any extra to mint as change we also need to handle that. So let's do that. Let's instead use try sub. So we can do self.s storage.private balances. And let's do try sub. And initially we want to try sub with the max amount of two like this because if we look at try sub I think my command click's not working right now but try sub takes an amount and then a max notes. I forgot to remove the deliver.
The return value of tricub is not the same as sub. Sub returns the note to be delivered. Tric sub does not. We talk about what tricub returns shortly. But you do need to remove that dot deliver. I do come back to this later on and remind you to remove that because I did forget and it did cause a compilation issue. But then after we've done that first call, we want to recursively call try sub until we get outside of this loop. And then with the output, we need to handle whether we need to mint any change or not. So this whole loop section we should actually put in its own internal function so that we can call tric sub again and again and pass in the max number of notes that we want to be calling try sub with. So actually we need this whole thing to be variable
max notes and if we allow someone to pass into here max notes we'll make it a u 32 and then we can take this and put it into its own internal function so that we can call it recursively. We're going to call this function subtract balance and this is going to only contain the logic which we're going to recursively call. So it won't handle any of the change and it won't handle the initial call. So in subtract balance we can put that try sub line. The output of try sub is the actual value that was consumed. If we just look inside balance set.Nr which is inside of Aztec NR and then we scroll down to sub you'll see that this is maybe note message. though
it's not actually a note but it's like the note message to be delivered whereas try sub returns a U128 as it says here try sub returns the how much was actually subtracted i.e the sum of the value of the nullified notes. This may be more or less than the target amount, but it's a best attempt effort to consume as few notes as possible that add up to the target amount. It returns how much value was actually consumed like we were talking about. This consumed amount is the output of try sub in this loop in this algorithm that we're trying to create. So we can say let consumed_amount equal to this. Now we need to check whether the output was more or less than the amount that we wanted to consume in
the first place. So we need to have this take a from of course Aztec address but also an amount and a max of notes. So let's do that check. So we can say if if consumed amount is more than or equal to amount then we know we've achieved what we wanted and we want to return the excess like how much did we consume extra than the amount that we wanted to and then we need to return that. So we can do consumed amount minus amount. So this is just the change calculation and then that means that we can handle that.
So we need to return it. So that's why we haven't done a let. We haven't got a semicolon. So we need to do a return U128. Else we need to do the thing again. We need to calculate how much we have remaining. How much of the amount do we have that hasn't been consumed? So we can say let remaining amount equal amount minus the consumed amount. So we can go back into the loop. We're now we're in this one. The consumed amount is less than amount which means we need to call tri sub again and see if there are any remaining notes in our set of notes. And we need to exit the circuit and create a new one. So we need to do an external call to our self. We need to do a self call self. And what this will do is it will call a private external
function. And we're going to call this function I'm going to create in a second recurse_ubtract balance internal. We're going to pass in the from the remaining amount and that's it. The only other thing you need to consider right now is if the amount that was actually consumed, if the value of our leftover notes is zero. So we haven't consumed any notes at all aka consumed amounts this returned amount is zero. Then we haven't got sufficient balance and we need to revert. So we can do an assertion that consumed amount is greater than zero. Otherwise, let's return with balance too low because try
sub returns the value that was actually consumed, which of course we know from before is going to be either less than, equal to or greater than our amount. But if it doesn't consume anything at all, it means we have no more balance left to consume and that would be bad. It means our balance is too low and we need to revert. Okay, so let's now create this external function. So let's put it by our external public function. So we'll make an external private function. We'll call the function recurse subtract balance internal to remind ourselves it's only going to be used internally. It needs to take the from which is an Aztec address the amount which is a U1 128 and then inside here
we want to call the recursion function again. We want to call that logic to enter this loop again. We want to call try sub again. The reason we wouldn't want to call decrease private balance is because in there we need to be able to handle change and deliver the note. So we don't want to be recursively calling this. And you'll kind of see that at the end when we handle the change, but you need to have the logic for just the bit that you're recursively calling to be in its own function. So that when we are calling it in here, we don't do any change handling until the end. So we can do self dot internal_ubtract balance from amount. And now the amount of max tokens could be up to eight.
Rather than having this as a floating number, I am actually just going to add that at the top as some. And I think we've got a floating two somewhere. Uh we will when we call it inside transfer private to private. This now needs to take a max notes like this, which would be two here. But instead, we're going to call it something different. We're going to call this initial transfer max notes. And then when we call it recursively, we're going to call this recursive transfer max notes. And then we're just going to set them at the top of the contract. Global initial transfer max notes, which is going to be two. And that is this value, the initial time,
the first time we call tricub. And then we want recursive transfer max notes to be equal to 8. You also here need to remove const. I don't know where the clip of me removing const went, but that's a solidity thing. You don't need it here. Global is the keyword. This is not a state variable. Instead, it's a compile time constant. Global in noir declares a value that must be known at compile time and it gets inlined whenever it's used. You don't really need to worry about that. Doesn't exist in storage and it doesn't actually cost anything cuz it's just a constant. Okay, where were we? We were down in recursive subtract in balance internal. Okay. So now it is recursively calling this subtract balance where the max notes is equal to 8. And in subtract balance we
are recursively now calling this subtract balance. We will call try sub with the max notes specified in the function call and we will check the output of this function call whether is more than or equal to or less than the amount and then kind of handle that. we will either recall the function again with the remaining amount left to consume or we will bubble back up or we need to bubble back up the change. So let's do that. So we need to go into decrease private balance wherever that is decrease private balance. Oh, we're not even calling subtract balance yet. So the first thing we want to do is we want to call self.in internal subtract balance from amount max notes [clears throat] because initially when this is called the only time this is
called max notes will be equal to two because it is called inside of transfer private to private here we're calling decrease private balance with initial transfer max notes equal to two. So that's how this function will be called with max notes equal to two and then subtract balance will recursively call itself via recurse subtract balance internal which will set the max notes equal to 8. Then we need to set this the output of this once it's all gone through the recursion and it has finally gone to this part of the loop and we need to get our change. We need to set it to a variable. So we'll say let change equal to the output of that. Then we need to handle the change actually
add it to their balance and deliver it. We can do self.ts storage.privatebalances at the from this is the from address add the change and then deliver the note. Oh, and I've got a spelling error. Thanks co-pilot. And that's it. Let's just step through what's happening again so that we can make sure we understand. First of all, transfer private to private calls the internal function decrease private balance and it will set the initial the max notes equal to the initial transfer max notes which is equal to two. Decrease private balance will then call subtract balance which will call try sub to attempt to consume the amount of tokens by only consuming up to max notes which initially is two
and it will get the consumed amount. If the consumed amount is more than or equal to the amount that we actually want to consume, then it's amazing. We have successfully done the decrease. But we do need to calculate whether there's any change. If we've consumed more than the amount, then we need to mint ourselves the change. The consumed amount is less than the amount, then we need to check that the consumed amount is not equal to zero. So we need to check the consumed amount is greater than zero. If it's not greater than zero, aka it's equal to zero, then we haven't consumed anything and our balance is too low. There was nothing to consume. If the consumed amount is a value but it's less than the amount then we need to calculate the remaining amount which is going to be the amount minus the consumed amount and then we
need to call this function again but to exit the circuit and create a new circuit we need to do that via an external function which was called recurse subtract balance internal. However, at the moment anyone can just call this function and subtract a balance from anyone. So we need to make this function have the macro only self and this means that this function can only be initiated by itself which it is. It's being initiated as part of the subtract balance function. This will then call back into subtract balance where max notes is now 8 and it will enter this function again and it will do this again and again until it goes inside of this part of the if loop and
then it will return the change and that's it. So now we've seen an instance of an external public function up here which was only self and this is when we needed to incue a public function from private to modify some public state and now we have a private function which is only self and this is used primarily when we need to exit the function call and create a new function call so that we have a new circuit so the circuit doesn't get too big and bloated. Let's now just quickly run Aztec compile to check for any errors. Okay, there are two errors. The first is that when we call try sub, we don't want to be calling deliver because this doesn't handle any note delivery at all. It
doesn't return any notes. It doesn't calculate the change or work out how much to return. It literally just returns what value was consumed, the consumed amount. So you can't call deliver here. Instead, we were handling the change delivery inside of decrease private balance and then doing dot deliver here. So that dot deliver might have been a little bit confusing. Sorry if it was. Of course, we don't do deliver there. That was probably just a copilot error. This is why you should be really careful with using copilot. And then the other thing that we did wrong is inside of the recurse subtract balance internal. This of course needs to return the change so that it can bubble back up. So we want this to return U1 128. So that both parts of the
if else loop do return a U128. And since subtract balance returns a U128, recurse subtract balance internal calls subtract balance. Therefore, it also needs to return a U128. Oh, one little small error here. We of course don't need const because global is handling this for us. This is just my solidity brain messing up. You just need the global keyword. Okay, compilation was complete. So that means that everything compiled correctly, which is nice. Okay, let's now create the next transfer function. So, we've done transfer public to public and transfer private to this function. Mint above transfer public to uh actually no cuz we want to keep the public functions together. So, that's private function. This is a private
function. Let's now do transfer private to public. So, we're going to put some nats back in at notice. Transfer tokens from a private balance to public balance. param the address to transfer the tokens from to the address to transfer the tokens to the amount of tokens to transfer and an orwit nons because obviously we're going to do orwits same authorize once I'm not going to go through this because we've done this a couple of times now external private function transfer private to public from address to address U1 128 author the first thing we want to do is decrement the private balance of the from by the value of amount and then number two we want to increment the public balance of the two by amount.
Now, and again, because this is in private, we of course do not want to emit an event because we don't want to let anyone know where you did anything. So, the first bit is easy because we've just implemented it. We do exactly the same thing as we did before. We do self. Decrease private balance the from amount oops initial transfer max notes. This will use again recursion. This is the lesson that we did just previously. Now, we need to increment the public balance. Now you might be thinking we can do self.in internal increase public balance to an amount. Now the problem with this is increase public balance is a public function and we can only call public internal functions from external public functions. We cannot call a internal public function from inside of an
external private function which this is. So instead we need to use inq. However, as we said oops I spelled that wrong. incue. However, because this is of course an internal public function, we also can't incue it because this incued call is going to accumulate as part of the side effects. So when we are executing our private functions in the pixie, the pixie will come across this line and it'll be like ah okay so I need to add this public call to the list of calls for the sequencer to execute. We cannot inque internal functions.
Internal functions are just inlined. So the logic is like almost copy pasted inside of an external function. So we cannot just call in or incq a call to a public function that is internal because the sequencer can't see those internal functions. It can only see external functions. There's no way to do this. So instead we need to create a external public only self function almost exactly the same as we did before. So let's create an external public function which will again need to be have the macro only self because otherwise anyone will be able to increase a public balance with no access control. So this is saying this one needs to be an external function that only this contract can
initiate function increase public balance internal to an amount and then we can paste that self internal increase public balance at the semicolon. So now we can inq self increase public balance internal and pass through the two an amount and that's it. Now we have created this transfer private to public function. Let's finally create a transfer public to private function. Let's create our final function. Well our final external function that's not a read function before we create all of our getters. So exactly the same as before. I'm not going to go through it.
We're going to create our natspec notice transfers tokens from a public balance to a private balance. That's exactly it. From to amount or non authorize once macro. Now you might be thinking okay so we're going from our public balance to our private balance. Therefore this is going to be an external public function. Okay like this. And then we'll call it function transfer public to private takes as check address to amount orwit nons. First we need to decrement the public balance of the from and the amount. Then we can do self.in internal doc decrease public balance then increment the private balance inq self private balance. And as you're writing
this, you might be thinking, okay, but like I'm inquing myself, but that doesn't really make sense because how can I incue a private function that really doesn't ma make sense and that would be the great a great intuition because we of course cannot go from public land to private land. Once we have passed all the information over to the sequencer and execution is happening in public, we cannot go back to private. So if state needs to change in both public and private, we first need to stay in private and execute everything in there and modify all of the state in private. Then we need to incue functions to public and interact with public state. So we can't do it the other way around. So this has to be a private
function with an incued call to modify the public state. I'm actually just going to remove this whole thing. So number one, increment the private balance of the two by amount. So we can call self.in internal increase private balance. Then number two, we need to decrement the public balance of the from by amount. So we can call self.nq self dot and it's saying I'm going to do increase total supply. We of course don't want to do that. We want to call an external function increase public balance oops public balance internal.
And this is exactly the same reason as we had to create a external function to increase the total supply from an amount because we can't incue a an internal function. We have to incue an external function cuz otherwise the sequencer isn't able to see it. So now let's create that function. It's going to be external public. Again we need the only self macro function. Oh, this needs to be decreased because of course we're decreasing the balance. I don't know why I said increase there. Decrease. This needs to be decrease public balance internal and then we can call the internal decrease public balance internal function. So we can only incue functions in public that are external.
So that the sequencer is able to see these functions on the interface and be like okay yeah this contract wants to call itself and the function that it wants to call on itself is this external function. And that is it. We have now written all of our external functions. We just now need to write some getter functions both for public and private state. Okay, let's create some getter functions. I'm also just going to put in some comments so that we can sort of organize our code a bit better. So let's above all of these transfer functions mint and mint public. Okay, so let's put above the constructor. Let's go like this. This is all of our public
functions. Then below that actually is that all of our public functions I'll check below. So then we got private functions. Private private private private private uh only self-public. So let's move that up above private. Okay. Private. Private. Private private. And then we've got internal internal. Okay. Let's now create some view functions. Losing my voice a little bit here so bear with. Let's just create a little header for them. I'll put them above my internal functions like this.
Now, let's get make create some getters for all of our public state. So, we need to do it for our public balance getter. We need a total supply getter. We need a name uh symbol and decimals getters. Okay, let's write all of those. They're all going to be exactly the same. So, I'm going to run through this pretty quick. It's going to be an external public function. We need the view macro to say that we're not going to be changing any state. We're just going to be reading it. We'll create a function called balance of public which takes an owner which is an Aztec address and returns a U128 which will be the public balance. And then we're using self.sto.public balances at this owner. So access this key from the
map do read. And that is ex going to be exactly the same for the total supply pretty much except we don't need to take any parameters total supply read and then name symbol and decimals. So name external public view name actually this is going to return a field compressed string self.s storage.name read external public view symbol and then finally decimals external public view decimals is of type U8 self.sto storage. Okay. Now, we need to do the same thing for our private functions. And when we're accessing our private data, we can use utility functions. Utility functions
just don't create any circuits. They are unconstrained and they run outside of the circuit. So, they aren't proven. And that's fine because we're just getting some state. We're not we're not modifying it. We're not performing any computation on any state. So, let's just add another header which says unconstrained. This is exactly the same as we did in counter by the way. So let's now create a function to read our private balance. It's going to be external but it's going to be utility because we don't need it to be private. We don't need it to be constrained. Then we can use unconstrained function balance of private. It needs to take an owner because of course we don't have message sender when we're in private land. It's going to return a U128 which is going to be the balance. And then we use self.sto.private balances. at the
owner view balance is not the correct function. Instead, because this is a balance set, the function that is built in to the balance set, which is nice and helpful, is called balance of. So, we can use a utility function in private to read our private balance. And this can of course be unconstrained because we don't want to create a circuit for this function. It doesn't need to be part of the proof. And that's it. that we've created all of our getters. Righty tighty. I'm going to hit commandB just to bring up my sidebar. And I am going to go inside of source and I'm going to create a new folder which we're going to call test. And we're going to put all of our test files for each
function inside of test. Exactly like before. Then we're going to create a new file inside of src source called test.nr. Again, this is exactly the same as before. And here is where we're going to collect together all of our modules. So, each test will be a separate file. This is a module. And in order for the tests to actually be compiled, we of course need to add it at the top of our main.nr file, which is going to be mod test like this. We need a semicolon to make sure that this is included in the compilation cuz otherwise anything outside of main.nr NR is ignored. This means that it's included in main.nr. So then in
test.nr, we will use the mod keyword again to include it. And then any of the file names inside of test. So it's going to be things like mint to private. This is going to be the name of one of the tests, etc. Mint to public blah blah blah mod utils. Then inside test, we're going to make utils.n. And we're going to take make mint to [snorts] private. NR. And we're also going to create mint to public.nr. And then inside utils, we're going to create our setup function. And then in each of these, we're going to have the unit tests associated with each function. And I'm going to create more files to for each function. So again, in testnr, I'm going to include mod
to public like this. And so on and so forth. So that is our test setup. Let's now write our logic inside utils.n. So in here we want to essentially deploy our smart contract and the way we do that is first of all we need to of course have our smart contract available in utils.nr so that the file knows what the contract looks like and this is a crate because anything with main.nr is a crate. What name of the function the contract sorry is a token. Now we're going to create some functions and these need to have the pub keyword so that they can be used from outside of this file. We can make all functions
inside of test unconstrained because we don't need to create proofs for them. Fn because it's a function and we're going to call it setup. And then this is going to not take any par parameters for now. And it's going to return probably the test environment as before. And this is just our testing environment as it sounds like for testing in Aztec NR. So it has things like a local node or a local network and a pixie that will be spun up to handle all of the notes and be able to handle the private function execution and public function execution. It's just the environment in which all of this is run. And then it's
also probably going to return an Aztec address which will be the contract itself which we are deploying. Number one, we need to create the test environment environment. I can't spell let mute and we make this a mutable variable which is what mute stands for. We can change the variable because we are going to be modifying in things inside of the test environment because we're going to be modifying state. And we're going to call this env. We're going to set it equal to test environment which is if I click here to complete it should add that at the top it does and which comes from Aztec test helpers test environment test
environment and then the function we need to call is new to create a new test environment then we need to number two create some accounts a deployer slash owner slash Mter, whatever you want to call it, and a user. So, we're going to say let deployer equals env. And then we had those helpful functions to be able to easily create accounts. We can create light account or we can create contract account. If we use create contract account, it will make the functions take longer to run. If we create light account, it means we can't do anything with the functions on the contract itself because it's just it's a light
account. It doesn't mean it's not an actual smart contract that needs to be deployed in the test environment. For now, we're just going to use create light account and then we can modify that if we end up needing a contract account which maybe you can have a little think about in what instances we might need a a contract account and then we could do the same thing for the user and set that to a user variable. Okay, now we need to actually deploy the contract and the remember first of all we need to create the initializer called interface or constructor whatever you want to call it. So this just creates an object which shows us what the initializer will look like, what are the parameters and then we can use that to actually deploy and call that function on the token contract. So we can say let
oops initializer call interface equals and then this function is on the token contract if I spell that correctly. And the function is called I think we just called it constructor, didn't we? Let's have a little look. main. Scroll down. We did just call it constructor, but we can't just get the function. First of all, we need to actually get the interface from the contract itself, which will just get all of the functions and the function signatures. And then we can call all of the different functions on that token. Well, we're not actually calling them.
We're just like getting what the function looks like. And our function is going to be constructor. And it takes these three parameters, name, symbol, and decimals. Now, you might think we'll just call this like my token like this. Call it my token. Oh, I don't know why it's a K. My token. And we'll say it has 18 decimals. And this would be wrong. And the reason for this is as it says here, expect type field compressed string, but found type string of length seven. So what we need to do is pad these out with zeros because the field compressed string essentially means it field element which is exactly 31 bytes.
So initially we're going to create a string of 31 bytes and then we're going to convert it to a field and I'll show you that in a second. The field compressed string is just a wrapper type that packs a 31 byt string into a single field and that just means it's efficiently stored on chain in storage. It's padded. So we pad this with zeros just to fill the full 31 bytes. And again with my token and actually I'm going to call this test token. Now we need to pad it with zeros. So we've got 31 characters here. Make it 31 bytes. Now we have a problem and that is that currently it's taking it as a string of 31 bytes and trying to set that to a field compressed string. So instead of doing this, we need to convert these to
field compressed strings. And so that we can allow people to pass in strings rather than field compressed strings, we're going to modify the constructor to convert strings to field compressed strings just to make things a bit simpler. Hope that makes sense. So we've done initialize and just passing in name, but instead we're going to do a conversion from a string to a field compressed string. And helpfully field compress string has a helpful function called from string to do this conversion. So we can do that inside the smart contract which is nice and helpful. And then we need to convert these to strings of length 31. And that's it. Then hopefully that'll make all the errors go away. They don't.
Command A, command X, command V, command S. And you'll see if there's actually any errors still. H. This isn't a dot. This needs to be a colon. This is a function inside and then hopefully this will be fine. Okay, so now we need to import Aztec address and test environment but otherwise it's working and these things are installed from inside of Aztec. So now we need to put all of this inside some curly braces to make it easy to add lots of different things. And then we need protocol address as tech address. And then also we need Oh, we've got test environment.
Oh, we're not returning them yet, which makes sense. We haven't got to that bit yet. So, okay. So, now we've created the initializer call interface and we've got all the types correct. We can actually deploy and call the initializer. So, we can say let token contract address. This is exactly the same as we did in the first section equals env which has the function deploy. I'm also just going to go away from those which are just from the noir lsp. And this takes a path. So that's going to be at token_contract slashto token. And the reason it's like this is because this is the name of the package and then this is the name of the
contract. So if we go nargo toml, we've got private token. We're going to rename that token contract. This is the name of the package and this is the name of the contract. Then we can call with public initializer because our initializer is of course public. And then inside here we need to pass the initializer call interface and the person who is deploying the contract. Then we can return from this function the test environment and the token contract address. Now we also probably need to return the deployer and the user so that they can be used in other functions. So we're going to return those as well as tech address and then we're going to pass them here which is going to be
and user. Now I'm gonna Oops. Why is this complaining? Did I spell it wrong? I did. Oh, the problem is this is the wrong way around. So if we look at with public initializer, this takes first the person who is going to call it and then it takes the initializer call interface as the little labels there are saying. So we can just switch them around and then hopefully we won't have any more issues. So yeah, so now we have deployed our smart contract and we have created some accounts. So now we can use the setup function to test our contract Okay, let's create our first test file and that is going to be oop got counter open mint to public and this is going to
be extremely similar to what we saw when we created our tests for the counter. So, I'm going to run through this pretty quickly and I'm also just going to in some instances just show you the solution because I don't think it's useful to just like keep coding from scratch when I would like to test that you could do these things yourself. I'm going to sort of handhold you through the first test and then hopefully you can just go and implement the rest of them yourself. I'm going to show you what the names of them are, the functionality that I think you should be testing and then hopefully you will feel confident to go and do this implementation yourself and then hopefully you can go and implement from GitHub. So the first thing we need to do is we need to get the utils and the
token contract from utils as well. So we can use create test colon colon utils and then we're also going to need the token create. So this is exactly the same as we use create token but we're also additionally using test colon utils which is going inside of this test and then saying we want this utils file. Okay. Now we're going to create our first test and of course we label that with the test macro to say that this is going to be a test. It's going to be And what are we going to test? We're going to test that we can in fact m to public and that it increments our balance as expected. Test mint to public. Now, the first thing we're going to want to do is set up the environment.
Oops. Environment and get the accounts and account contract. So, we're going to do a let got multiple variables. We've got our mutable environment. We've got our token contract address. I think that's the fourth parameter. We've got our deployer. And we've got our user. I'm pretty sure that is the correct order. Deploy a user token contract address. Set it up. Set up. Did we not call the function setup?
Set up. Set up. There you go. Okay, now it's not complaining anymore. Now number two, we need to call the mint to public function. So on the environment, we have the call public function which takes the from parameter. Uh, and because we can only deploy, we can only mint using the deployer as we asserted in our mint function, we're going to make the deployer the caller. And then we get the token interface, the token crate. And then inside here, we're going to say at this address, which is going to be the token contract address, we want to call the function
mint to public, which takes a two, and we're going to say mint to this user. And the amount, let's say we want to mint them a,000 tokens or 10,000 tokens. It doesn't really matter. In fact, let's just make that into a variable. We'll say let mint amount because I'm sure we're going to be using it later in our assertions. Lint mint amount equals oops 10,000. Instead of this, we'll call this mint_amount. However, this is probably going to complain at us because this needs to be a U128. So, I'm going to be specific and I'm going to say this is of type U128.
Oops. And I spelled this wrong. Mint amount like that. Okay. So now we've done the public call. Let's now number three check the user's public balance. And then number four, we need to check that the total public well I mean it's just the total supply isn't it has been updated. Okay. So the first thing we need to do is check the users balance. So we can just say let user balance equals env dot view public because it is a view function which is public and then the function that we want to call is going to be on the token contract again and then we need to use colon colon at again to say that this
has been deployed at this token contract address and then the function is called balance balance of public I believe. Yes. And the owner we are passing in is the user. So this is going to be the user balance. And then we need to assert that the use balance is equal to the mint amount. And then actually copilot has just added the same code but for the total supply which is nice and help. Okay. Let's run this test. So the first thing we need to do is we need to compile our contract because we haven't done that since we made changes. And every time we make changes we need to Aztec compile. doesn't automatically recompile when we run aztec test. Now we need to run aztec test to run our test.
Got one function and it passed which is amazing. Okay. So now that we have tested mint to public, we need to add some other tests in mint to public which is going to be number two. I mean this is number one. test that minting in public increases the public balance and total supply by expected amount. Don't know why I've added some square brackets there. Number two, I want to create a function to test the mint public reverts if called by non-mter. So, you're going to need to Oh, it's giving you a helpful hint there. Remember how
to do test failures and then I also want you to create a test checking for overflow. And actually, I might do that one with you. Now, we will expect this test to fail. So, we need to use the test macro. And then in parentheses, we say should fail with and it's not going to be overflow error. We are checking for overflow. But instead, I'm just going to copy and paste what the error is because it's it's pretty long and it's just the standard overflow error, which is contract execution is reverted, assertion failed, attempts to add overflow to add with overflow. And then it gives you the thing which made it overflow, which if we go back to our
main.nr, it was in increase public balance. We set the new balance to this whole thing and then it attempted to write that big whole thing. So this is what actually made it revert. So we go back to mint to public. This is what it should revert with. You can just sort of accept that for now. And then we're going to make the test unconstrained function test mint to public failure overflow. We're going to do the setup again. I'm going to go through this real real quick. Let the mint amount is going to be the max number of a U1 128 which is going to be equal to how do we get the maximum number of a U 128 in Noir?
Well, actually what we can do is we can take 0 and then subtract from 0 one. So we can do like 0 minus one. Let me do this in a comment just to show you what's going on. What we can do is we can do 0 minus one and let it wrap around to the max u 128. So we can like override the default behavior which doesn't which will revert on subtracting from zero. It has almost like a safe math in already built in as we know. We're going to like override that behavior. Actually maybe it does have a U128 max. Does it? This is going to be news to me. No, it doesn't. Okay. So this is definitely what we need to We need to let it wrap around to go below
zero minus one to the maximum value of U128 exactly like solidity. Now this default behavior doesn't happen by default. So we need to say zero as U 128. We're saying this is the value zero and then we can do a dot and then get the function wrapping sub. So we're saying we're doing this subtraction and we're going to allow wrapping the value one. Then we need to actually import this function which comes from the standard library. So, std which we haven't used it yet actually. This is the standard library and inside the standard library we have some operation function ops. And in here you'll see we have this wrapping sub. This is what we need to be able to wrap around. This just allows us to do this math which
allows wrapping because obviously wrapping isn't allowed by default which obviously makes sense in a smart contract. Right. So now we have the maximum value of a U128. We can call mint to public. Did I not close out my So we can do an env.all_public. The person uses calling it is going to be the deployer because we don't want it to revert on that. And then we're going to do token at this aton contract address mint public to the user the maximum value of U128. Then we're going to check that their balance is equal to that max value just to check that that worked correctly. Max U 128 U 128. We don't need a message here. Just need to
assert that. And then we're going to do a call again to mint to public and add just one. We're going to do another mint. This time adding one token one which would by default be a field type. So we can use as U128 to convert the type to U128. And then this will overflow and hopefully revert with that expected revert message. Let's run these tests. And the test passed. Amazing. So now you can add in this other test as homework. And then also I want you to add the tests for mint private. I'm not going to write them for you or with you because I think that is good homework because it's pretty much the same except
rather than doing call public, you're going to do call private. So it's pretty much exactly the same. You're obviously going to use different view [clears throat] functions and maybe you want to add your view functions and your assertions about checking the get adding to the user balance. So minting and then checking that user balance. Maybe you want to add that into a utils function which you can import. You could do some funky stuff like that. And then in the next lesson we are going to do the tests for transfer public to public and then we're going to do transfer private to private and then I'm going to leave the last two functions as homework view as well because they introduce orwits and we need to test those orwits. So that should be pretty interesting and I'm not going to do all of the function all of
the tests in the for those functions either because I just want to show you the things which are a little bit different. Okay. Now we are going to create the test for transfer public to public. So we need to make sure we add this file inside of test.nr mod transfer to it's transfer public to public. Sometimes copilot is not quite correct. So this file is going to include a lot of tests. We want to test obviously just a normal public transfer. We also want to test just transferring tokens doing it ourselves.
But then we also want to test doing it on behalf of others. The first two that I just said are a little bit trivial as in you should definitely do them do the tests but actually like what needs to happen is like how we actually write the test is just very similar to what we've already done. So the first thing I'm going to do of course is just import test kon utils and we also need the token and we could also do it like this actually like this and then we can delete that line right and then we're going to create a test but this test I want to show you how to use orwitz in test because the way you test orwitz in
public is different from how you test orwitz in private And I want to show you those two pipelines. And then that's kind of mostly the end of the sort of specific differences I want to show you. There's another sort of small thing, but I'm not going to code it from scratch with you. Anyway, that's bye. I'm going to show you how to test on behalf of others. I also want you to test the sort of failure path of that. So what if they try and do a transfer without a valid ORWIT but we're going to do the path where they have a valid ORT. The other thing you obviously need to do is if the person doing the transfer doesn't have enough value but you can look at the GitHub repo to sort of check your work for all of those tests and see if you
you know missed a test that you thought should be added that kind of thing. I think that's actually a really good exercise. I would hate to just sit here and code it all for you and with you from from scratch. I just don't think that's great for your learning. But what I would like to show you is yeah how to use public authorwitz and then private authorwitz in the next lesson. So we're going to do a test. So it's going to be unconstrained because it's a test function and we're going to call it test public transfer on behalf of another. So what we really need to do is of course we need to set up the environment. Then we need to create the orwit. Then we need to execute the call and then
finally we need to assert the balances are as expected. Now as I was kind of saying in the orwit lesson with public orwits you can use them to just you know let Bob call transfer on my account. With private orwits things are a little different and that is because the person who calls the function still needs to be the account that authorized the orwit. They can call uh an a contract which has authorization and then can perform a call for them but the entry point still needs to come from the account contract of the user who created the orwit and that is because the validation pathway goes through the owner their actual
account. Uh I hope that made sense from the orwit lesson. It's just more of a recap. We're going to recap that again in the next lesson when we do transfer private to public or I think we're going to do transfer private to private. I can't remember. Anyh who, in terms of public, we can have it be just, you know, a random other user. Okay. So, let's set up the environment. This is the same as usual. Let mutation environment deployer user token contract s equals utils setup. Now, we need to create the orwit. And to do this, we are going to use a cheat code which we've not used yet. And the cheat code is inside orwet cheat codes. So inside this crate the function is called add public or thwit
from call. And then we need to import this. So this is from Aztec. So use Aztec test colon helpers orwit. And then we're going to rename this as orwit cheat codes just to remind ourselves of you know what this is called. It's a cheat code. So this is just an alias for orwit. We could have just said orwit here and then remove this as. But to make it more obvious, we're going to call it orwit cheat codes. So this will enable us to add a public orwit from a call. And then this takes first the environment. Then we need the on behalf of address which is saying this is the person who's doing the actual authorization. I'm going to
make this the deployer. So the person who owns the actual tokens is going to be the contract deployer just because I'm going to reuse them. Then we've got the caller which is going to be the user. So this is the person who the deployer is creating the orwit for who's actually performing the call. Then we need the actual call itself which is going to be on the token contract at the token contract address which is token contract address. Then we can execute the call which is going to be transfer public to public. This needs to take a from which is going to be from the deployer to this can be anything but we're going to make it the user. The amount is going to be let's say it's 10,000. Let's actually just put that
into a variable transfer amount. Let's make a variable. Yeah. Okay, fine. A,000. It doesn't really matter. And then we need the orwit nons which we're just going to say is one cuz it's the first time. Now I'm actually going to set this to a variable because we're going to be using it again when we actually execute the call. So we're going to say let public cuz it's a public function transfer private to private call interface equals and then set it to that with that whole thing which we just wrote semicolon and then in here we can add the call interface is going to be the public transfer private to private call interface like this.
Shouldn't actually be screaming at me anymore. Okay, now we need to execute the call which is going to be env. call public and the from is now going to be the user. They're actually executing the call and then the call. Then we need to do our assertions. We need to check the balance of the user and we need to check the balance of the deployer. However, there's a problem. We can't actually do this transfer yet because the deployer at the start will not have any tokens. We actually need to do a mint which we can copy from before. We can just copy this line here. We could actually make this into a little helper function. I encourage you to do that because we'll be doing this all the time. You don't want to be copy and pasting this line everywhere. Instead, you want to call a
function on the utils file. So before we actually do the transfer, we're going to do a mint. Going to check the mint is to the correct person. It's not deployer. Oops. And I'm just going to say first we need to mint the some tokens so that they have a balance to transfer. Yep. Okay. Now we need to actually Oh, this is called public transfer. Public. Oh, this is not private to private. This should be public to public. Let's find and replace that. Okay, this needs to go there. Okay, much better. Now, we need to do the assertions. So, we get the deployer balance. Oops. Then we need to get the user balance. And we need to assert that the deployer balance equals the mint amount minus the transfer
amount. Mint amount. We're going to make let's say it's I don't know, let mint amount equal 100,000. And we also need to make this a U128. just be explicit so it doesn't convert to a field. And then we need to assert that the user balance is equal to the transfer amount. Let's run this test. Running three tests now. And they all passed. So I hope this made a bit of sense. And now you can go ahead and add all of the other tests for transfer public to public. In the next lesson, I am going to show you how to do the same thing but with private orits because they work a little bit differently because of course we need to instead of this being another user, another like
account contract, this would instead be a smart contract which we are authorizing but we're actually executing the transaction ourself. It's originating from our own account contract and then the validation of the authorit is going through our account. So it works a bit differently. Let's now create transfer private to private. Let's create a new file inside test transfer private to private dot and inside here we need to forget how to do this use rate colon colon test colon colon utils.
Let's actually wrap that in some curly braces token. Now we need to use private authorit. So we're going to create a test. And again I would love for you to just create the sort of generic I say generic but just like you know rinse repeat logic of all of the other pathways but I'm just going to show you how to use private orwits. So I'm going to create an unconstrained function test. We're almost there people. test transfer private to private on behalf of another. Okay. Then in here we need to do the setup
like normal. Mint some tokens to the account being transferred from. We need to create an orwet. But actually this is going to be the next step because the first thing we need to do is we actually need to number three create a dummy contract. It's not an account contract. Create a dummy contract. What is going on with my typing? Not an account contract which is calling the transfer function on behalf of the from address. Then we need to create the or using all of this information. Then finally after creating the orwit we need to number five
execute the function on the dummy contract which actually calls the transfer function on the token contract if you understand what I'm saying. So like let's say this dummy contract is some kind of defy contract that contract function will then call the transfer private to private function and then we need to do all of our assertions. Number six, do the assertions. So the flow is pretty similar to transfer public to public. So it's not too confusing and we do need to again use the orwit cheat codes, but we have this sort of extra step that we need to create the dummy contract. So first of all, we need to do the setup as per usual. Then we need to mint some
tokens. Let's say our mint amount is yeah 10,000. Call public. going to mint some tokens to yeah, let's say the deployer. We could do it to the user. It doesn't really matter. Then we need to create a dummy contract. Now, I'm going to be a little bit sort of cheeky here and just say you can copy this from the GitHub repo associated with this course. We're going to call this the caller contract, but this is of course going to be a separate crate. So, you're going to have to go into this crate, compile it separately, and then make sure it is included inside of your target folder when you are compiling private token.
That will happen separately because this is a completely separate crate. So, I'm going to create a new file. Oh, actually no. Uh yeah, called nargo.toml. You can again obviously just clone this from the GitHub repo if you would like. New folder called src. And inside here, we're going to create a new file called main.nr. And then in here you can copy the code from the GitHub repo. So in here you'll see that I've got this caller contract and it's essentially just got some functions already written. We don't really need to go through them and they're just different. They call different functions with different numbers of arguments. So if the function you want to call has one argument, you need to use forward private one and so
on. And they call private functions. These are all external private. So you could pass in the target contract that you want to be calling inside your function and then the selector and then obviously any arguments. So if we go back to transfer private to private, we need to create our dummy contract. Now you again might want to be doing this inside of utils because if we're reusing this functionality where we create our and deploy our dummy contract. Yeah, you're probably going to want to do that in utils. In fact, let's just do that in utils.
pub unconstrained function. Let's call it something like deploy caller contract. Ah, that's the other thing. We also need to check that this was deploying mint public. No, this needs to be mint to private. So then this needs to be call private. Good job if you noticed that. Okay, let's continue back inside of utils deploy caller contract. And this is going to return an Aztec address, which is going to be that caller contract. And inside here, we should actually cuz this needs to have the environment. Actually, we could just pass in here, couldn't we? We could have I mean, this it depends how you want to set this up. Enest environment or we could just combine
these two functions and just have a function which calls the other one. And I'm actually going to do that. I think that's a bit better. So this is going to return all of this stuff. Copy paste plus another Aztec address which is going to be the caller contract. So here we have now called the setup function created the caller contract address by taking the test environment calling deploy. This should not be token contract. This should be caller contract. And then this will be called caller contract because that is the the name of my I go inside caller contract that is the name
of my contract and then oh I also need to add the nargo toml which again you can copy for the git from the github repo associated with this course the name of the package is call contract which matches here and then with public initializer we've got we'll take the deployer as the deployer and then creating oh this shouldn't be token This should be so this is why you need to always check whatever co-pilot is doing whatever AI is doing because it doesn't always work. So deploy we actually don't want any of this stuff because this is all incorrect and instead we're going to do it without because there is not an initializer on this smart contract. It is purely a
private contract that exists only with private functions without an initializer which is pretty cool. It's just it's basically just logic offchain and then we will return all of those things. So all of the four things from before and then additionally this caller contract address. So then back in transfer private to private rather than doing this setup we will do deploy what do we call it? I'll call this setup and deploy caller contract might be slightly different on the GitHub repo associated with this course just to make it a bit more similar to what Wonderland um implemented but the logic is exactly the same. So here rather than this function we'll do deploy and then additionally we need that caller contract address. Okay
amazing. So we have created the dummy contract so we can remove that. So now we need to create the orwit execute the function and then do the assertions. Okay, so we're almost there and this is exactly the same as what you did in transfer public to public. The other thing we need to change is deployer and user addresses to use create contract account rather than create light account. And this is because the authorit verification needs to go through an actual contract account that has the verify private authorit function. So that's why we need to use these functions instead. Light account is of course faster. Create contract account is slower. But we need to use it now. So we need to change those two. In the GitHub repo associated with this
course, you'll see that in the code there is like an if else check that uses light account in one instance and then create contract account in another instance. You could implement that yourself. That is a better way of doing it so that the tests run faster. I just wanted to keep things nice and simple for this tutorial. So we've done the mint. Now we need to create the orwit. We're also going to sort of create a let transfer amount equal a th00and. We're then going to create the private transfer private to private call interface transfer to private wait actually it's called transfer private to private and then the arguments in here are going to be the deployer that's
the from two I mean yeah sure let's make it a user doesn't really matter who the two is then we're going to make the amount the transfer amount and the nons one then we need to create the orwit. So we need the orwit cheat codes add private orwit from call. This takes the environment. It takes the on behalf of which is the deployer. Then we have the caller and this is actually the caller contract address. It's not going to be the user because this needs to be a smart contract not an account contract. And then we've got this the call interface which is the action which we are authorizing to be called. Then we need to execute the function on the dummy contract. So we're not executing
transfer private to private because that is actually what is being executed in the caller contract. So we can again use env.call_private. But now the parameters are [snorts] going to be the person who's calling it is actually the deployer. They are initiating this whole flow. This is what's verifying that orwit in private. And then the function which is being called is going to be the caller contract at the caller contract address. And then the function that we actually want to be calling is called forward_private_4 because there are four arguments to this function that we want to call which is transfer private to private. This is
what is actually being called by the caller contract and we've got 1 2 3 four parameters. This is what we're verifying in our orwit. The orwit will be used when we actually execute this call. We pass in here initially the target which is going to be the token contract. Then we need the selector and then we need the arguments and luckily all of that information is actually all already on the call interface. So if we take this we can actually do dot target contract and then a same with for the selector. This is actually not called function selector. This just called selector. And then finally take the args and pass them in. And they are just already existing on this type which is a call interface
which is pretty lucky. Now we need to just finally do the assertions now that we've done that execution. So we need to check that the deployer's balance was decreased by the transfer amount and we also need to check that the users balance was increased by the transfer amount. So let's check the deployer balance and the user balance. Oops. And then we check that the deployer balance is equal to the mint amount minus the transfer amount and that the user balance is equal to the transfer amount. So let's now run that test and we didn't import some things which I think actually we didn't do up here. Oh maybe we did. What didn't we import? Ah we haven't compiled the contract. So I'm going to cd do slash cd caller contract aztec compile. Then this
will automatically go into target here. So I'm going to select these two files and then just oops drag and drop them into my target folder for my private token package. Then we're going to cd do/private token to go back into private token. Then we're going to run the tests again. Aste error. So that wasn't the problem. Problem is somewhere in utils.nr. It's saying whoa. What is the problem? Did I not import caller contract? No, I didn't. Okay, so then we need to add into utils use caller contract caller contract. Let's run the test again.
Could not. Oh, maybe I've spelled it wrong. I have spelled it wrong. Yeah, there you go. Could not resolve caller contract in path. Of course. Why can we import something that we haven't even said where it is? Okay, so in our nargo toml, we need to add that in. and it's called caller_contract. And this is going to be something different for you. And it's going to instead of a GitHub repo, it's going to be a path equals quotes/caller contract. So it knows where to look for the package. There are a couple of small things to fix. So first of all, we need to add in some semicolons that we're missing. One here, one here, and then also we're using caller contract. So we
need to make sure at the top we are also importing caller contract into transfer private to private. Then additionally at the bottom rather than I think it was call private or something like that it was like view private which doesn't exist obviously we don't have view functions in private instead we have utility functions which are able to access private data. So we have execute utility instead. Now we also need to inside test.nr NR add the mod or our final test suite because if you can see before there's only three tests running which is those first three tests that we created. So we need to add test private to private because otherwise this file is not included in our compilation and is also not included in our testing. So
if we now run as tech test we should now see more tests. Should be four running four test functions and all four tests passed. If I scroll up above all of these logs, you'll see the four tests passed. So that is amazing. And now we can move on to the final part. In the final lesson, I'm going to teach you a little bit about testing recursion. I do not expect you to understand this fully. It is enough to kind of just test that all the balances work correctly, but we can go a bit more granular. Basically, on a note level, get each individual note and check that that recursion loop is working as expected. I'm going to show you how that is done. I do not expect you to know how that works fully.
I don't fully expect you to gro it. I just kind of want you to see it so that you can see some slightly more advanced testing capabilities, things that you can do. I do expect this to become a little bit easier in the future just as the Aztec NR team improve things. So, if you do think some things a little bit clunky, please do let us know because you know this is a very early framework. So you guys are in a really cool position where you are able to influence what happens. So anyway, so I'm going to show you that and then we're going to do a very quick recap before that's kind of it to be honest. you're you know everything you need to know really to start developing on Aztec and creating smart contracts which is really really cool to think that only a few hours ago
you barely knew well I mean assuming you didn't really know anything about development on Aztec and now you know how to make a very simple counter contract but also like a pretty technical private token contract. Anyway, I'm getting ahead of myself because got one more lesson left. I'll do my recap in a second and get all sloppy on you but one more lesson. Right. So, as I was saying, I would love for you to implement all of the tests for all of the functions on the smart contract. Please use the GitHub repo associated with this course to obviously go through all of these tests, see what you're missing, cross-check it with yourself, really test your knowledge.
I'm also absolutely happy for you guys to use Claude or whatever to you know shorten that process because ultimately like as AI is improving we are using AI more and more and it is important for that to become part of your workflow. Of course we don't want to be relying on it too much especially at the start of the learning journey and that is because if you just use AI for everything then how are you supposed to know what's correct? You can't just blindly trust everything that it does. Anyway, there's one last thing I want you to look at. Just going to hit command B to close out that sidebar and I'm going to scroll to okay so in transfer private multiple tokens recursively I mean we're kind of doing the same thing as normal but then if you scroll down a bit so first of all
obviously we set up the environment and get the relevant accounts and stuff we get all these different things but you'll see that these aren't balances these are note amounts note counts so this total amount of notes in the pixie total amount equals notes amount times note count. So this is the value of each note and this is the number of notes. So then this would be the total balance. And then four we're going through each of these note counts and then we're doing this mint to private. Obviously this is a utils function that does the mint to private. We're minting the owner these notes. So we're going through and we're being like okay mint 12 notes of value a thousand. This is not labeled particularly well. This could be like
note value or something instead. I think that would be a little clearer. Then we're creating a transfer amount to do the transfer which is going to be not a whole sort of note value increment. So this means that the amount that we want to transfer won't evenly be transferred in like an even like a whole number. That's what I mean a whole number of notes. The amount we want to send is less than the amount in one note. You can kind of see the explanation here. Then it's creating the transfer private call interface as normal and then doing the transfer. Then we're checking the recipient balance as normal. But we also want to check that the actual notes themselves
are correct. And this is a little bit convoluted and it uses this get private balance notes utils function. So let's have a little look at that. So if we go inside utils and you'll see I've added some really extensive comments. Well, Claude has actually get private balance notes has all of these comments. I recommend that you do read through this if you really want to understand it. We're querying the actual Pixie database. So, we're saying go into utility con context where we can actually read the notes and we're creating an offset and saying what type of note we expect it to be. These are like how we want to be able to view the note. We're getting the storage slot so that we know where to find them. Then
we're getting the set of notes which as we said balance set is just a private set with some extra functions. So this is going to be a balance set for this particular owner and then we're getting all of the notes. So we can do this set doview notes. Don't worry too much about this. Again read the comments if you would like to understand a little bit more. So this will actually obviously return the notes. We can actually check the number of notes and then the value of that note. So the confusing parts really are the offset which we're setting to zero anyway. Um, and that's if they already have some notes inside of their pixie, which they don't. And then also the sort of note viewer options, which basically just says what note type we should expect. But yeah, I don't expect you to understand that at
all really. If you do, amazing. And it just allows us to have a little bit more granular control over our test and actually test this vector of notes and be like, this is how many we expect them there to be. This is what we expect the value of this first note to be. this is what we expect expect the value of the second note to be and so on and then that's kind of it. So that is the end of this section. We have learned a heck of a lot in this section. I'm really proud of you guys for getting to the end of this. I am really excited to see what you build with this knowledge. Maybe you could build some special token contract.
I really recommend that you do go and look at the DeFi wonderland token contract because it has some other functions to make it more applicable for AMM contracts and actually in Aztec packages there is an AMM implementation. So you could also go and have a little read through that. The more you more this is going to really get ingrained. But let's just have a very quick overview of what we learned. Okay, before we do a recap, I want to upgrade to the latest version like we did before in counter. So, the first thing we need to do is go inside of caller contract and find and replace this 4.1 version with this 4.2 version, which I've actually already done. And
then do the same inside of private token. Find and replace. Save. Make sure they're both saved. Okay. Now, we need to go into caller contract. Uh let's just delete these target folders actually just so that nothing goes wrong and then we can run Aztec compile. Okay, so none of the syntax changed again. So in this target we'll just kind of move this over to private token because that's where we're going to be using it. And then we're going to go cd doprivate token and then we can run Aztec compile again and everything compiled correctly.
You need to make sure that your Aztec version in your CLI when you run Aztec up matches whatever you're using in your nargo toml at all times. So make sure you run Aztec up aztec-up install and then the version that you want to use. And by the way in there you don't want to put the V. Whereas in the Nagl you need to put a V. It's a bit confusing in the CLI. No V's. That's the rule because otherwise you're going to have some compilation errors and they're going to be very confusing. So just make sure that your CLI version matches whatever is in your nargo toml. Now we've compiled both of those. Why have we got something going wrong in our test? I don't think this should be a problem, but let's just run an Aztec test just to check. Could be a problem with something
else. Okay, all the tests passed. So I don't think there's any problem with the tests. Okay, so now we want to pull up this so that we can actually deploy our token contract. We can use the wallet that we used before, which we've already minted tokens to. We can just continue with that. So, we can use Aztec-wallet uh deploy. And I'm actually just going to sort of copy and paste this over because there's no point you watching me type this whole thing out. D- node URL node URL dash from Oh, this is actually different for me. Now, the command we're going to run is Aztec wallet deploy node URL. Specify the node URL. specify from what account you want to use. The payment method which is going to be feed juice alias that we're going to give the to the contract is going to be token.
The name of the contract is token. You might need token at here private token to say what the name of the package is. I think this is fine. I don't know why it's working this time. D- args for the constructor args because our constructor is called constructor. So we don't need dash dash in it. The args are going to be the our address, the name token, the ticker top, and 18 decimals. And now we can deploy our contract. Woo! We have now deployed our smart contract to Aztec. That is crazy. Okay, let's now mint some tokens in public. Why not?
Let's call a public function. Okie dos. Aztec wallet send to send a transaction. The function we want to call is mint to public. All the normal stuff. and then get the token address which is going to be in the alias token. The arguments to the function are just the account that we want to mint to and the amount that we want to mint. And luckily because we are the deployer, we can mint tokens. So let's send this transaction. Oh, cooler is not mter. Ah, that's because we need it to be the account. Oh, this is a different account. This needs to be course. So we can see our contract is working as expected because I used the wrong account and therefore I could not mint to myself. Okay. Now I have minted to myself and it's working expectedly.
Simulation completed. We've created the proof. We've got the transaction hash. We've now sent the transaction. And then finally we need to simulate a fun a function call to get our public balance. We need to simulate it because it's just a read. We're not modifying any state. So we don't need to send a transaction. This is the last thing we need to do. Amazing. So now first lastly we need to call simulate public balance of public. I think that's what we called the function. Let's actually just double check that. Go into main pull down this terminal f public. Yeah, it is. And then the account is called course.
And we're going to send the send it from also from course. And then hopefully we should see a balance of 100 which we do. Woohoo. Okay, we have now successfully deployed our token smart contract and interacted with it on a live network test net. And this is exactly the same as what you would do on mainet or alpha or whatever. In the final few lessons, we're just going to do a little recap and a little sign off. But this is pretty much it. So I'm not going to ramble now because I ramble in the next lesson a lot. Bye. So we learned in this section about Orwitz and how to use them in public and private. We learned that public orwits allow so Bob to transfer tokens from my account or do any action we want. It's
not limited to tokens because we don't have an ERC20. It's not just allowances anymore. It's a generalized way to allow someone else to perform an action on your account. In the terms of private authorit this means that you can authorize another contract to do perform an action on behalf of your account. So if I'm calling some AMM contract I can then allow the AMM to deposit tokens from my account to itself but the actual pipeline of the transaction needs to originate from my contract so that my contract itself can verify that orit.
What else did we learn in this section? ah we learned about recursion and why recursion is needed because when we are subtracting value we need to limit the number of notes that we're iterating through. So when we did this decrease private balance function when we implemented it we realized we're going to need to create a function which is going to be recursively called to create new circuits consuming a fixed number of notes each time that it is called decrementing the balance with how much was subtracted so far and then calling it again. But we also knew that this needed to be an external call because otherwise it wasn't a new circuit. The functionality was just going to be inlined if it was just another call to an internal function. We learned about in quueing calls. Let's see if we can
find an example. When we were transferring from public to private to decrease the public balance because we first need to start in private land and then go to public land. If we need to modify any state in public, we needed to perform that as an ncqed call to the contract itself in public where this function that we are in quueing. Let's look it up. Needs to be an external public function with the only self modifier which again was added that to that recursion external function. Let's find that. Where's the external private?
Yeah, here we go. external private only self recurse subtract balance internal. This was the the function which allowed for recursion and we needed to add the only self modifier even though it was external to say that only this contract itself can call this function. And I think that was the rest of the interesting things in the smart contract itself. We learned how to get the address zero. That was kind of interesting. And then we set up the tests. And this was interesting because we learned how to use Orwitz using this Orwit helper cheat code in both public and in private. And we showed how to create some dummy contract to do it to test private or thwitz in private. So as I said that is it that is pretty much
everything you need to know. There are some other sort of interesting things that you can learn including creating your own note types. So let's say you wanted some custom information in the strct of the note. To do that, you would just need to create a strct with some functions that has the note macro. You can see some examples of this of course in the Aztecr repo and then also in the documentation. So if you'd like to learn anything else, the documentation is your best place to look and then also in Aztec packages in those noir contracts. And you can also have a look on the awesome Aztec repo because that is a great place to see other examples of how to do things in the DeFi wonderland Aztec standards repo. That is another
great place to look for examples. And I am so excited to see what you build next. So massive congratulations. Please go give yourself a little break and a reward. I know I'm going to. In fact, I'm going to go to the gym and go on the stairmaster and listen to some throwback tunes. namely Cody Simpson if you are if you're curious. And in the next lesson, which is going to be the final lesson, I promise I'm just going to give give you a little outro to sort of give you some encouragement of where to go next. So, thank you so much for watching this section. It has been honestly an absolute honor to be able to teach you guys Aztec and Aztec NR. I love it very much and I hope you guys start to love it as much as I do. I know things seem a
little bit convoluted at times, but bear with and if you have any feedback, please let us know because we want to improve it and we want to make it as easy and comfortable for you as possible, but I don't know. I'm really starting to like it myself, even its little quirks and nuances. Um, but yeah, I'm going to stop rambling and I will see you in the outro. Bye. Congratulations. You got to the end of Aztec Fundamentals. Woohoo. A massive, massive well done to you. So give yourselves a big pat on the back because that was a big old course. I know it wasn't necessarily the longest course, but there is a lot of new information here, a lot of new things to think about. It's not just a new language or framework as Aztec NR is. It's a also a whole new ecosystem. We're dealing with
Thank you and what's next!
privacy. We're dealing with notes. Everything that we think about has been changed a bit. So there was a lot of new information there. So please go give yourself a little reward of some description. And I know that I'm going to be going having a sweet treat shortly. So maybe get yourself an ice cream, take a break doing something creative, whatever else it is that you like to do. Just let this information marinade. Now, what should you do next? So, it kind of depends. If you feel like you understood a lot of what is going on in this course, then maybe you could build something. I would love to see what all of you guys build after watching this course. Maybe you take the token contract and you build a slightly more advanced or specialized token contract. Maybe you have a look in Aztec packages at the AMM implementation.
Maybe you want to build a private raffle. Maybe you want to build private battleships or a private social media platform like LinkedIn. The opportunities are endless. And currently it is the best time to start learning Aztec Noir, AztecNr, and building on Aztec because this pool of builders is still so small compared to Ethereum. So, you're going to be one of the first developers on Aztec, which is really, really awesome. But, as I said, just take your time. So, you can also ask your favorite AI agent any questions that you have. I'm sure there'll be things I didn't explain properly and that you don't quite understand. So, just have a little back and forth with chat with GBT or Claude. And then also, you can of course leave questions on the or join the Aztec Discord. So you can
ask questions there or ask for some examples of things that you could build. Ask for some ideas. There's a lot of really really big minds working on Aztec at the moment who would love to give ideas for you to build things. But you can just start small. Any building with Aztec, refamiliarizing yourself with how to do so using claw code to help you write smart contracts. You can lean on them and then just check the code or you can write it all from scratch yourself. It's completely up to you. If you did enjoy this course, I would love to hear about it on X or Twitter, whatever you want to call it. Please tag me in your learning journey. Whether that's content, things that you've built, things you'd like to build, anything about this course, I would love to hear it. I will also leave a link in the
for you to be able to share a tweet to say that you enjoyed watching this course as well because that just helps me know that you enjoy watching content like this and that I should create more of it. And finally, if there is any content associated with Aztec that you would like to watch, maybe that's more the AztecJS side of things that we didn't touch so much, which by the way, Claw Code is actually really, really good at. So, if you want to build a front end for your smart contracts, or you want to build something with the wallet SDK, integrate with wallets, then it is also really good at doing that. And we also have some really good tutorials on the docs themselves. But if you would like to see maybe some videos around AztecJS or some more complex application ideas and walking through
how to build that, we can do so. But I think in the age of AI, you have most of the primitives you need now to be able to go and just build whatever you like and build on top of that knowledge and use the documentation, use AI to build your knowledge, to unblock yourself, to think of new applications which will inevitably have problems and then you can think of solutions for. But with all of that rambling, I'm going to leave you be now and say thank you so so so much for watching. Please let me know if you enjoyed this. Please tag me in all of your tweets and GitHub repos and anything that you build and let me know what else you want to see. Thank you for watching. Bye.