2. Why Solana Rebuilds Finance
Explains the inefficiencies of traditional financial infrastructure and how Solana offers fast, cheap, global settlement. The speaker compares legacy systems with Solana's ledger and real-world adoption.
Every country has its own financial system, banks, stock exchanges, payments network, all built decades ago, all operating on their own rules, their own hours and their own fees.If you want to send money from Tokyo to Sao Paulo, it passes through multiple banks.It takes days and it costs a percentage.If you buy shares in a German company from Indonesia, you Need brokers, currency conversion, settlement periods.The global financial system isn't actually global.
It's a patchwork of local systems held together with duct tape and Swift messages.Solana is what happens when you build financial infrastructure from scratch for the Internet age.Let's talk about what settlement actually means.When you buy a stock, you sit in your account immediately.But the actual transfer of ownership, that takes two business days.
During those two days, multiple parties are reconciling records, moving money around accounts and verifying that everything matches.This is called T2 settlement.Trade date, two days.It exists because the underlying system were not designed for instant anything.They were designed for paper certificates and phone calls.
Now multiply this inefficiency across every financial transaction on Earth.Trillions of dollars locked up in settlement limbo.Billions paid to intermediaries who purely reconcile records between system and don't even talk to each other.
Payment networks charge 2 to 3% because they can.International transfers cost $2550 because the correspondent banking system requires it.Markets close at 4pm because the humans are running back to their home.None of these limitations are laws of physics.They're artifacts of infrastructure built before the Internet existed.
Solana is a global ledger that anyone can read, anyone can write to, and everybody agrees on.Think about what a ledger does.It records who owns what.Your bank has a ledger.The stock exchange has a ledger, Visa has one.
They're all separate.They all need to be reconciled.And accessing them requires permission from whoever runs them.Well, Solana is a single ledger that settles transactions in around 400 milliseconds.Not two days, less than half a second.
It processes thousands of transactions per second.It runs 24, 7 or the whole year.And it costs fractions of a cent, not 2 or 3% fractions of a cent.Here's the key.It's not controlled by any single government or company.
It's run by a network of independent validators spread across the world, all running the same software, agreeing on the same state of the ledger through a consensus mechanism.This is not theoretical.Right now, over $150 million moves to Solana Daily in stablecoin payments alone.And this is going up.Daily Trading platforms built on Solana execute millions of trades without traditional market makers.
People in Argentina use it to save dollars without opening a US bank account.The infrastructure exists.It's operating at scale.The question isn't whether it works, it's how quickly industries are adopting it.
4. Setting Up the Solana Environment
Walks through installing Rust, the Solana CLI, Anchor, and Surfpool, then verifies the setup. It also covers creating a local wallet, requesting devnet SOL, and using the block explorer.
Before we write a single line of Solana code, we need to get your machine set up properly.So let's go over what we'll be installing first.Rust Solana programs are written in Rust.So we need the Rust compiler and Cargo, which is Rust package manager.If you've never written with Rust before, don't worry.We'll cover everything you need to know, but the tool chain needs to be there.Second is the Solana cli.This is your command line interface for interacting with Solana networks.You'll use it to create wallets, deploy programs, check balances, airdrop yourself, Devnet, Sol, and a lot more.It's essential.
Third is Anchor.Anchor is a framework that makes Solana development significantly easier.It handles a lot of the boilerplate and security checks that you'd otherwise have to write manually.Most production Solana programs today are built with Anchor, and it's what we'll be using throughout this bootcamp.And fourth is Surfool.This is a local test validator that's extremely fast.Instead of waiting for transactions to confirm on DevNet, you can test locally and get instant feedback on your code.It's a huge productivity boost when you are iterating quickly.Now, I'll be showing this on a Mac, but I'll include instructions for other operating systems.The process is pretty similar across platforms.
Now let's get started.So we're going to go straight to the solana docs, so solana.com docs and there is going to be an installation section and this is going to go over everything you need to set up your environment.And it's pretty simple because everything's been wrapped into this curl command to install all the dependencies that you need.So this is going to cover Rust, the Solana cli, Anchor cli, Surf Pool, Node JS and Yarn.One thing to note though is if you are running Windows or Linux, there are prereqs that you're going to have to meet before actually running this Chrome command in your terminal.
I am running a Mac, so I can't show you how to do it, but if you just go to the documentation, everything is very explicitly stated here for you to follow along.And once that's all set up, then we can get to our install.So here we're just going to very simply copy this command and we're going to paste it into our terminal and we'll let this run.It's going to take some time to run, but what it's going to do is it's going to make sure all of the dependencies are installed on your machine.And once they are, then it's going to move on to the next one.If you already have things installed, it won't reinstall it.So you can see mine ran pretty fast just because I already did have everything installed on my machine.And then it's also going to check versions for you.
And then once it's all installed, you'll see that it says installation complete.Please restart your terminal to apply all changes.
So I'm just going to close this out and then we're going to reopen the terminal.I'll zoom in a bit for you there.And then all we're going to do is check to make sure everything was installed correctly.So that command is also explained here in the docs.And all it is is checking the version of each dependency that was installed on your computer.
So we're just going to copy that and paste it into our terminal.And you can see I have Rust, Solana, cli, Anker CLI and Surf Pool all installed.So we're all good to go there.Now that that's set up, if you are curious to dive deeper into each of these CLIs and all the basics that go along with it, there are docs for each of them.So you can go into these pages here and just take a look and figure everything out on how you can use these CLIs.
These will be used throughout the rest of the bootcamp, so you don't have to read through this documentation.You can follow along with the rest of our courses.But if you're curious now, take a look and then we're just going to go over a few more basics for getting your computer ready, so then you can follow along for the rest of our projects.So the next thing I'm going to look at is setting up your local wallet on your computer.So if I go to terminal, if I type in Solana address, it's using the CLI to check what wallet is connected as your local key pair on your machine.
So I'm going to type this in and you can see I have no signer found right now.Most of the time it's going to show that unless you've already set it up.So if you're just installing for the first time, this is the output that you're going to get and you're going to be able to run Solana key gen new and what this is.So the command actually tells you what to run, which makes it easy.But what this is doing is it's generating a new key pair and storing that on your machine.
Now that's created, you're going to have your seed phrase displayed on the screen.Mine's going to be blurred out so you can't see it, but make sure you save that.It's going to be how you're going to access your wallet in the future and then you're going to be able to see what that wallet address is.So here, this is your public key that's returned.If I type in Solana address now, it should return the same public key.So you can see that these match up.And that is my local key pair on my machine.So now if I'm going to want to test anything out locally, it's going to use this wallet.And for me to be able to test, I'm going to need some devnet solo on my wallet.So how are we going to get that?
Well, there is a faucet.So I can take this address and we're going to go to the Solana Devnet faucet and we're just going to paste that in, choose an amount and confirm the airdrop.And this does require you to connect your GitHub.I don't want to connect mine currently just because that'll have all of my login.But we'll go back, there is another method that you can use.
This is going to have a rate limit on it, so it's a lot more efficient to use the faucet.But if you go straight into the Solana cli and you type in Solana Airdrop and I just type in two, it's going to request to Seoul to my wallet.And you can see that this rate limit was reached because I'm using WI fi that a lot of other people are using right now and probably already requested some Sol.So I'm going to just go to another browser where my GitHub is actually logged in.One second, open that and we're going to go to the devnet faucet.
I'll connect my GitHub here and then I'll paste in the wallet address and then we're going to choose an amount and we'll confirm the airdrop.So we'll verify that.
And you can see the airdrop was successful.So now we have funds on our wallet and we're going to be able to use it for testing purposes.Now we can confirm that this actually happened by using the Explorer.So the Explorer is Solana's block explorer.And what this does is it shows you recent transactions, token account, mint account, wallet accounts, just activity that happens on chain and it's also going to connect to different clusters.
So here you can see we're connected to mainnet Beta, which is Solana's mainnet.It's where all of the actual real traffic happens.There's other clusters so you have LocalNet when you're running a validator locally on your machine.There's testnet, where mainly the network team works on testing Things, and then DevNet which is where you'll be testing things for just when you're building.So here you can see I airdropped Devnet Sol to my wallet.So we're going to check that if I paste in my wallet address here and I'm now connected to DevNet, we'll click over and you can see my balance on My wallet is now 5 Sol because I just requested 5 Sol from the AirDrop.So that's an overview of pretty much all the basics you need to be set up on your computer.We went over installing all of the dependencies on your machine, how to set up your wallet locally in your terminal, how to request funds to your wallet and then how to use the Solana Block Explorer.
So now you're all good to go.You have your machine set up to start working through the tutorials in the boot camp.
5. Starting Projects with Templates
Shows how to begin new Solana projects using the templates marketplace or the create-solana-dapp CLI. The speaker demonstrates how the starter template wires up a full-stack app automatically.
Now that you've installed your local environment, we will be sharing different ways how you can get started with creating your Solana projects.The first way will be using our Templates marketplace where you can find different applications pre built for you.You'll be able to filter by frameworks, by use cases and we'll also share how you can use the Create Solana app CLI to get started with the simplest options where everything is pre wired for you and is the best way to make a simple hello world and get started quickly on these projects.When you want to start a new Solana project you will have two options.The first option will be to clone one of our pre made templates for you to get started with a specific text tag that you're looking for.
Let's go in and observe those options.I'm on the Solana website here.I will go to this developer dropdown and I will click the template section.The template section is a marketplace of different templates that are ready to start with a specific tech stack and there's a lot of variety that you can choose from.If you're looking for something which you know, it's pretty precise, you have an idea where you want to get started.
You can filter with this menu on the left side here.For example, if I want to filter and I want to work with React Native to do a mobile application, I can see all of the options for React Native and If you want to consume one of these applications directly, one of these templates directly, we'll simply select it, which will take us to the template details page.You will be able to read the readme of that project with all the information about how to use it, the different technologies used in composing that template, and once you have selected it, you're ready to use it.You will go ahead and click Use this template.You will be presented with options from npm, Yarn, bun, pnpm, we cover all of it and you will simply choose your favorite package manager.
If you're not sure NPM is the standard one, I would advise to use this and you will copy the command to clone it to your computer.So I have this command copied, I will go over to my terminal and I can just paste it enter and here I can enter my project name and it will clone everything for me.But what I want to show you first, this is the use case.If you know exactly what you want to do.Specifically if you have like a I want to build like a mobile application or you want to do something with a phantom embedded wallet, this would be this use case.
If you're not sure, what we will do instead will be to just use the beginner templates, the starter templates, which are the most neutral, the most flexible ones.So for this you will do inside your terminal NPX space create Solana Dapp at latest and here it will guide you in an interactive tutorial.It will ask you your project name.I will say Boot Camp take two and once you enter you'll be presented with the group of templates that you want to use here.These groups are the most general ones.
For example Kit framework are the simplest one.They use our new JavaScript SDKs, SolanaKit and Solana React hooks.So I will pick this one and you have a choice to simply use the next js which is just a frontend Next JS with an anchor program.Or you can have the same options using the React Vite framework.I will go ahead and select the next JS anchor1.
Once I select it, what happens is that the creates a land adapt CLI will clone this code locally to your own folder.It will run the NPM installation steps for the JavaScript part.These steps should take I would say 2 to 4 minutes depending on the templates because we are setting everything up for you.So for example, here I've set up the front end, data is completed and I'm progressing now to setting up the actual anchor program and generating the connection.All the PIPING all the little things that need to make your full stack application come to life, which we'll see later in the tutorials in this bootcamp.
All of this is getting pre wired for you.So it does take a little bit of time, I would say two to four minutes at the most.But once this is all ready to go, you'll have a working running application ready for you to code directly.And these applications, by the way, there's a lot of documentation so they are fully ready for your AI.Agents like Cloud Code or OpenAI codecs will work out of the box simply because the links to the documentation, the readmes are well detailed and easy to get started with.
So let's give it a bit of time to see how this plays out.
So now the setup is almost complete.We're running the last step, the INIT script.Once you're done, you will get this installation successful message.You will want to navigate with your terminal inside this project.
So I will CD into boot camp take two and you will want to open this in your favorite code editor.In my case, I will be using VS code as it is what I consider pretty much the most open, the most standard one these days.So code to open.All right.Once you're in there, the basic next JS anchor project will be split along two ways.
So if you go and open the package JSON here you will see that we have a bunch of commands or anchor build anchor test which will take care of your Solana program.And the other commands are mostly related to the web application of your program.And we've seen that it's also split in a similar way.So the anchor folder here will have your code for your Solana program.Let me go ahead and close a few windows here and let's close this anchor program.
If we open the app folder we will be in the next JS application.I'll go ahead and actually start this full stack application.I will do NPM run dev back in my terminal.This will open up on Localhost 3000, our dev server.And once we open it up you will have instructions, link to the DOCS wallet connection and a vault program deployed to DevNet PRE Wired for you.
So obviously one of the reasons we've set it up this way is that you will have an easy time if you simply pass this to a coding agent like to a coding AI.You'll have an easy time because everything is documented and the code is pretty simple.A lot of the time AI can trip over in here in this starter template, everything is Pre wired.So it should normally do really well.Alright, so that was starting with the Solana Templates Marketplace and using Create solanadap cli.
6. AI Workflows and Best Practices
Discusses how AI tools fit into modern Solana development and why context matters when using them. It emphasizes documentation, Solana AI resources, and responsible use of coding agents.
So one of the hardest things in producing a bootcamp which will last for six months to one year, is that coding is changing quickly these days.I used to write all of my code by hand.These days I write most of my code with agents like Claude Code or GitHub Copilot.So in setting up these resources, we want to acknowledge that we're going to share what we think is in January 2026, a good set of tools to get started.But just take note, these tools could be changing the next week, the next month, next year.We don't know what things are going to look like.So we are keeping all of these resources up to date for you.So obviously one of the best places to start with if you are working with an AI agent is to always include more context to your agent than you think that it needs.
So an example here.This Solana documentation is kept up to date by our professional team of many people.
So we always are keeping all our best practices, all our best coding resources up to date.On the documentation website, you should always be referring to this documentation in all the projects that you're building and you should point your AI agents to using this documentation.This is a good resource, but there's also more AI tailored resources that you can use.So here on screen I'm going to share with you the awesome Solana AI list, which is an ever growing list of AI resources that you can use for AI and Solana together.So for example, we have a list to a bunch of dev skills of how you can consume different frameworks on Solana.
You also have links to different AI agents and to different developer tools.This list is brand new as of the recording, we're expecting this to grow exponentially.So please do take the time to read what are these resources so that you can adjust your workflows.We're giving you mostly knowledge in this bootcamp about how you can work on Solana.So the trick when you're working with AI is to have the knowledge yourself and be the free thinker.
You don't want to outsource your thinking to the AI, you want to guide it.You want to be the architect of how the AI is working.So read these resources, stay up to date with documentation and you'll already be ahead of the curve when it comes to shipping good code and fast.And as of January 2026, one of the most popular Coding tool out there is Claude code.So as part of this list, one of the links, which I consider it the most important link is on that list is the Solana dev skills, which is meant to be consumed inside cloud code for you to simply get started faster on your project.
So these skills are essentially an opinionated way of working on Solana.This will also be updated.So these skills are a collection of MD files of how to work with idls, how to work with payments, anchor, Pinocchio, all of these frameworks that you're currently learning, I would still highly encourage you to learn them, even if you're gonna have the code be done by an AI agent, because you'll get faster, you'll get better as you do these.So let's see how I can consume these AI dev skills.So here I will copy this, repo this, this Link, sorry, from GitHub, I will head over to my terminal.
In my terminal, I have a project here which I clone from Create Solana Dapp.And what I will do is I will start cloud code.So cloud code.
All right, let's set it up.Once you're inside cloud code, you can simply say clone and install.I will just say actually install these skills, Install these skills from Solana and you will be pasting the link of the Solana devskill.What will happen is that Claude will ask you for a few permissions.It will ask you, can I fetch this content?
It will say, yes.When you're working with AI, there's different mentalities.You can decide to give full control, which would be referred to as people vibe coding, or you can stay in charge and still approve all the actions.As a beginner, I would encourage you to approve action one by one.Read, understand what's happening.
So now what is happening here is that it wants to install these skills onto cloud code.You will proceed and say, yes.A cloud skill is a skill that Claude is able to locally work and refer to as its own documentation, its own playbook for interacting with these specific activities that it's trying to do.So what you have to understand, I'm giving a demo here and there's a little bug.This happens often when you're dealing with AI.AI is not predictable.They're probabilistic systems.So as you saw, there were a few bugs and I have them installed already locally.But just read what is happening and make sure that these skills get installed.You will have more of an interactive experience.
I cannot script this perfectly in this tutorial, simply given to the fact that AI is not always predictable.So once you have these skills installed, now I am in this vault create Solana DAPP program.I could say please, please use the Solana skills to improve this vault and add SPL tokens.I'm giving you this example and when you're building your projects, you will want to use these skills.Especially because Claude or ChatGPT or Gemini 2.0 might be out of date on the latest documentation.
So that's the reason why if you use these skills, you'll have better access to the most recent documentation that we're curating and maintaining for you.This is not going to be a whole demo of how to use these.It's more of a best practices and how to set up your environment.But what you should be focusing on from this tutorial is remembering that the more information you give to your coding agent, the better it will perform.If you're just hoping for the best, crossing your fingers, you're likely not going to get good resources.
This is what we call context.Engineering context is all the information which you give to your AI agent to get the best result out of your coding session.So by giving all access to the docs, the coding skills, the awesome AI solanalyst, you're narrowing down what the agent should work on instead of just casting a wide net and hoping for the best.So this will be the end of this section on AI best practices.The most important thing that you should be remembering is before going to production, if you're handling people's money, you should always make sure that your program is working well.
I would highly encourage you to also do a security audit with certified auditors.And remember that you are still responsible for the code that AI writes.You still have your name when you're committing this code, when you're writing this code, even if you're not doing line by line.So be responsible and understand what is happening under the hood.You don't have to be an expert in everything, but you still want to keep a good grasp on what you're building.
You want to be the architect, the orchestrator happening instead of outsourcing your full process to AI.Thank you.
8. Writing the Voting Program
Builds the voting program from scratch, defining poll and candidate accounts and the initialize, candidate, and vote instructions. The section explains PDAs, account constraints, timestamps, and core Anchor patterns.
So for this project we are coding the voting application and this is actually the very first anchor program that you're going to be writing throughout the bootcamp.
So for this one we're going to do it from scratch.The rest will be more high level understanding concepts.But here we're going to show exactly how to get started for writing an anchor program.So now I already have my voting directory open in my terminal and all I'm going to do is type in anchor init and this is going to initialize my workspace but I should probably name it so we'll do Anchor init voting.
Now it's generating a workspace for me set up for anchor smart contract development on Solana.And this is able to be generated because you have already downloaded anchor onto your machine earlier in the install video.And with that comes the anchor clique.Okay.So you can see it initialized an empty git repo for me with this anchor init command.
So I'm going to CD into voting and I'm going to open up that code and we're going to talk through what anchor init generated for you and then actually start writing our program.And this will be pretty quick because creating a voting app on chain is actually very simple.So let's zoom in a bit so you can see it.Now here you can see I have a lot of folders generated.This app folder is for when you're ready to build your front end.
This is not going to cover the front end, but it will go into this directory and we have migrations as well and then your node modules.All of this is really for the front end typescript side of things.So we're going to focus on this programs folder.And inside programs, you have voting and source and you have your cargo toml.Now, this is actually set up for a lot more complex of a program than what we need.
You can see that there's an instructions folder, and that's going to hold all of the separate instructions for your project.There's constants, error instructions, librs and state.And this is the most common best practice for writing smart contracts.As you see, when we get to more advanced concepts throughout the bootcamp, like the sableswap amm, it's going to be structured this way.But just for simplicity here, I'm actually going to delete all of this besides the librs.
So your main code with the entry point for the program always lives in your librs file.So just going to delete this as well.Okay, now we just have our librs and we'll get rid of the mods because we don't need them.There's no other files.Okay, great.
Now let's start actually writing the program.So you can see there's already some boilerplate code here.And it's importing anchorlang, which is the crate for the anchor framework.There's this declare id and what this is, it just defines the program's id.And the program ID is this that you see right here.So this is your public key for the program.And once you deploy the program on chain, you're going to be able to identify that program based on this public key.So we'll be able to see that later when we connect to an explorer and we're able to search the explorer with the public key.It's going to show us this program.Now here you have a program macro and all this is doing is basically saying, hey, all of the functions that exist within here are going to be my instructions for the smart contract that I'm writing.
And that is how Anchor kind of helps simplify a lot of the smart contract writing because it's going to cover your entry point and deserializing and serializing data for you all within this macro.So all you have to worry about is the function and the logic behind it.Okay, so now let's figure out what do we actually want to write for voting.So if we think about voting, essentially it's not going to handle tokens on Chain.It's going to be like your most simple smart contract and it's really just handling data.
And for voting you're keeping score of account between different candidates.So how does data work on Chain?Well, one thing that we know is that smart contracts on Solana are stateless, meaning that no data is stored within the smart contract itself.If you're coming from a different chain like Ethereum or Cosmos, this is going to be a very new concept for writing smart contract architecture.Now, where is the state stored?It's stored in accounts.So what we're going to have to do is define what we want our state to be and how those accounts are going to look like.So whenever I'm designing a smart contract, I always like to start with the state and then move on to the logic after.So how do we actually do that?Well, we're going to define an account because that's where State is stored.
Whenever we want to create an account with Anchor, all we're going to do is just use the account macro that Anchor has.So I'll type out account here and that is doing all of the hard behind the scenes work of generating an account and storing data.Now all I have to do is define what data structure I want to store what fields into my account state.So I'm going to name this struct poll account and I'm going to enter all of the fields that I want.So let's think, if I'm creating a poll on Jane, what do I want to store?Well, I'll probably want the name of what the poll is and a description.So let's add that first.So we'll do pull, we'll do pub poll name and we'll just define that it's going to be a string.Now there's something that you're going to have to know with anchor is whenever you have a string variable, you're going to have to define a max length.
And the reason is it is calculating the space that it needs to take up on chain to be able to pay rent for that account.
So when you calculate the space, if you have a string, you want to know about how long the string will be to be able to accurately calculate space.So I'm going to have to define here the max length macro.
And for the name we'll just do like 32.It doesn't need to be that long, it's just the name.And back to where I was saying, we need to be able to calculate how much space is on chain.There's actually a fun anchor macro that automatically does this.So I remember when I first started building on Solana, you had to manually calculate how much space was on chain and it took forever.
So now you can just do derive init space as the macro underneath your account macro and it's going to automatically calculate that value for you.So now I'm going to define the next variable and that's just going to be the description.And I'll need the max length for this one to be a little bit larger because in a description it's usually longer than the title.And let's make that like 2:80.Okay.
And we'll name that as poll description.
And also going to be a string.
I use the wrong brackets.Little typo.I'm like, why is there an error?That's why.Okay, cool.
So now we have those fields.Now the other thing that's important when you're creating a poll is when is it going to start and when is it going to end?Because you don't want the poll to go on forever.So let's add in those fields as well.Now this is going to introduce the concept of having time in your smart contract.
So we'll talk about how you're actually able to get time as we're writing the logic.But for now we just need to be able to keep those values in our state.And time is going to be a U64 type because we're using a UNIX time timestamp here.Okay.So we're gonna have the poll voting star and poll voting ends and that will also be a U64.
Okay.And then the last thing is how Many options do I want in the poll?So we'll just have an index here that we're going to keep count of.So poll option index.
Okay, great.So that's everything for the poll accounts.Now let's think about if we're creating a poll, what other information do we need to store?Not just the information for the poll itself, but for what are we actually voting on.So we have different candidates in a poll and we're going to need to store that information.
So that's another account that we're going to have to create.And after that, I think that's all of the data that we really have to store for this project.So same thing, we're going to follow adding account and derive a space, because that's what you're going to do whenever you're generating an account.And now we're just going to define what data we want to save for the candidate.So Pub Struct can account.
I'll use the correct curly brackets this time.And now so let's just see what do we want to store?Well, we need to keep track of the votes and then what the actual thing is that we're voting for.So the name.So let's save the candidate name.
And that's going to be a string.And once again, for the string, we'll have to define the max length.So you can actually see here it's generating an error.The rust analyzer is pretty good with this.So it's saying expected max length attribute.
So if you ever forget, you're just reminded because it's going to be bright red and you don't like that.So max length.And for the name, we'll do the same as the poll name.We'll do 32.Okay.And if I save that again and the rust analyzer regenerates now the error went away.And the last thing we're saving is just the votes.So pub candidate votes, and that's a number.So we're just going to do U64 there and we're good to go.So now we have our two states.
And usually whenever I'm writing smart contracts, I always start with the state to make sure that I know where my data is being stored and what data I have to work with throughout the logic.So throughout the rest of the Bootcamp videos, whenever we're explaining a concept, I'm always going to be starting with explaining what the state looks like.But this is the only time I'm actually manually typing out how to write the account state in your smart Contracts.So if in the future you ever get confused, come back to this video video and go over this section.Now, now that I have the account, how do I actually create it?Because all I've done so far is defined what the data looks like that's going to be stored within that account state, but I haven't actually generated the account.So let's figure out how that works.
Well, there's this one concept with anchor, and that's this context.So let me rewrite what I want this to actually look like.And this is going to be initializing my poll.
And if I initialize the poll, I'm going to basically just be storing all of these data fields into the account and initializing it on chain.So every time you write an anchor program, this first parameter of your function is always going to be your context field.And what this context field does is it maps you to a custom data structure that you write within your anchor program that's using this Derive Accounts macro.And it' basically telling the function, these are all of the accounts that you're going to interact with when executing this function and how you're going to interact with those accounts.So we're going to write the data structure for our Derive Accounts struct to tell this function that it's going to need to initialize a new account on chain.
So let me name that, I'm going to name it initpol and we're just going to write, we're going to write out our Derive Accounts macro.
And now all you're doing is writing a struct that's going to hold every single account that you want to interact with for its corresponding instruction.So for this instruction, all we're doing is initializing a poll account.So all we have to worry about right now is three accounts.So the first account is always going to be the signer.Whenever you execute an instruction, you need to have a signer that's signed signing for that transaction.
So we're going to do pub signer and that's going to be a signer type with a lifetime.
And I forgot to name my struct.So this is a nitpole.Okay, there we go.I love how errors just show up right away and then you know how to fix it.Okay, now the next thing that we're missing is whenever we have what the account is, we also need to use the account macro to just declare that it's an account.
Now that we have that, it's going to be showing that the signer is an account.And one thing that we're going to have to add here is we're going to have to add a constraint called mute.And what that does is it makes this variable mutable.So in Rust, all variables are mutable by default.So whenever you are defining an account here, and you know that the account is going to change in some manner, you're going to have to mark that account as mutable.So whether the data in the account is going to change here for the signer, the signer is going to have their balance change because they're going to be paying for the transaction.So we do have to mark that as mutable.Okay, now that's the first account.The second account is the pull account, because we know that we're initializing that.
So let me define an account again and we're going to add some constraints on this as well.
But we'll come back to that first.Let's just type out, I want this to be a poll account, and this is going to be an account type, and we'll need the lifetime specifier.And this is going to be the poll account data.So this is just mapping back to this poll account here that we've defined.Now, I left the constraints blank.
So whenever you mark this account function, there's several constraints in anchor that are telling you what you want to do to that account during the logic of the corresponding instruction.So here what I want to do, this account doesn't exist yet, so I'm going to initialize it.So let's type in init.That's our first instruction.Now, whenever you initialize a new account, there's going to have to be a payer.And the reason is accounts have to pay to store data on chain, you're paying rent.And that's what we talked about earlier, where you have to calculate how much space an account takes up based on its data.The reason you have to calculate that space is the amount of money that you're paying for rent correlates to how much space you're taking up on chain.And because of that, we do have to have a payer assigned to the account that's going to be paying for that rent.
So the pair is the signer.
And then we are just going to define the space.So we have eight, which is the anchor discriminator.And we're going to add in this fun little function we talked about earlier that just automatically calculates the space for us, and that's this INIT space.
Okay?And then the next thing.So we're going to make this poll account a pda.And a PDA is a program derived address.It's essentially a address that doesn't have a public key and it is created based on seeds and a bump and the seeds are input fields that we're going to specify as we're creating the accounts.
And later when you sign a transaction based on a pda, you're using those seeds rather than a private key.So you can pick whatever seeds you want, whatever you think is the most efficient for the program that you're creating.So here we're just going to name it, we'll do poll and then as ref.
And then I'm also going to want to store the poll ID just so I'm able to identify each of the polls and we'll just do as ref there as well.
And now you can see again getting an error.And the reason we're getting an error is because every time you have seeds you're going to need a bump because a PDA is generated based on your input of seeds and a bump.So all we have to do here is specify a bump, which I think if I delete this it should instruct you.Yeah, bump must be provided with seeds.So again, if you forget, it'll just remind you.
Okay, so there is everything we need to generate this poll account.Now you can see we're still getting some errors.And the reason is whenever you are creating a new account on chain or interacting with an account, you're going to also need to interact with the system program.And as we know, we're going to need to specify every single account that this instruction is going to have to interact with.So we're just going to add in the system program.
And there we go.So we're getting some errors with the lifetime, but that'll go away in a second once we finish the logic.But another error that is now generated is this poll id.So you can see here, if I hover over, it says it can't find the value of the poll id and what I'm doing is I'm pulling a value from the poll accounts state.However, this pull ID has not been stored yet in the state because the account hasn't been created and it's not able to find that value anywhere.
So where we're going to need to get the poll ID is going to be as an input parameter in the instruction.And whenever we're pulling variables from the instruction we have to tell anchor that so it knows where to look for it.So all we're doing here is just adding another instruction, and that's going to be the instruction macro.And inside you're just going to specify what variable you're pulling from the instruction.So we'll just do the poll ID is what I'm going to be taking from that instruction, and that's going to be a U64 variable because it's a number.
Okay, so that's good to go.Now we have all of the accounts that are associated with the instruction.So the very last thing to do is just write the instruction logic.So if I'm initializing the poll, all I'm doing here is just saving all of the data that I need to the poll account.And a lot of this is just going to be user input information because I'm generating the poll and I want to be able to specify all of the fields to be able to create it.
So as always, context is your first field.And then we're just going to add all the other input parameters.So we have poll id and I could store the poll ID in the poll account state, but I don't really need to because it's already stored in my pda.And I wanted to introduce another rust concept here.So I'll explain in one second.
But this is going to be the only field that's not actually being stored into the state.So pull ID we're using for our pda and then the rest of these input parameters are going to be everything that we need here that's being stored.So we have poll id, we have start time, end time, name and description.So start is a U64, end is also a U64, and then name is a string, and then description will also be a string.
So those are all of our input parameters, and all we have to do is store that to our state.And it's very easy with anchor, because I can just take this context and load it in.So I can do context accounts, and that's going to be able to access all of these accounts that I've specified here.So context accounts, poll account.And now I'm just going to access every field, but I can make this a variable just so it's easier to write out.
So we'll make this poll is equal to to our context poll account.Okay, now I can just do poll.
Poll description is our description, and we're just going to fill in all of these fields.And what it's doing is taking each field in the account state and saving the information that's been an input to the function into our account state.So poll description, we have poll name and then we have poll voting start and voting in.
And I've used the wrong comma for all of these because I can't see that far.Okay, cool.So now that this was generated, all that we have to do here is return an okay value because our result is an empty value here.So, okay, and now we're good to go.And now two Rust concepts that I wanted to introduce here.
One is you can see we're getting an error here for poll, and that's because I created a variable and I didn't make it mutable because Rust, all variables are immutable by default.So if I add this mute keyword now, all of those errors go away.Okay, so one other thing that I wanted to talk about, which it should be showing a little yellow squiggly line here.But sometimes Rust Analyzer is a bit funky.If ever you pass a variable as a parameter into a function, but that variable is not actually used in the logic of the function, you're going to get a warning because why are you passing the variable if you're not actually using it?
So whenever that happens, because it does happen quite a lot when you're writing smart contracts.Because as you can see here, we're needing the poll ID for our pda, but we don't actually need it in the logic because I'm not storing it to my poll account account state.So basically what you're doing is you're telling Rust, hey, I know this isn't being used in the logic of the function, but I do actually need to accept it as an input parameter.Okay, so that's your first instruction.
9. Testing Voting with LightSVM
Shows how to test the voting program using LightSVM and generated clients from the IDL. It covers setup, PDA helpers, success cases, and error-case testing for full coverage.
Now the second one is pretty much identical to what I just explained because all you're doing is creating an account, initializing it, pulling in input data and storing it to that account.
So we've already initialized the poll and now we're going to initialize the candidate account.Since both of these functions are really just initializing a new account on chain and saving data to it, it is very repetitive.So what I'm going to do is just copy and paste this section of code so I'm not repeating everything that I just explained.So we're just going to copy in our struct.So again, whenever we start a function, we always start with the derive.Accounts want to define every single account that that logic for the instruction is going to interact with.So I'm just going to copy this over pre wrote this section so you don't have to watch me type it all out, and we'll just paste that in.So this is our initialized candidate section, and it basically looks identical to the initialized poll except for one difference.
And that difference is I'm passing through a fourth account.And that fourth account that I'm passing through is actually the poll account that we created.
The reason that I'm passing this through is because I need to access and update data from the poll account.Whenever we have a new candidate.We do have that field in the poll account called poll index.And what that index is keeping track of is how many candidates are actually in the poll.So every time we do initialize a new candidate, it's going to increment that value.
So because of that, we do have to make the account mutable to be able to update the account state in the poll.And as you can see here, I'm once again passing through the seeds in the bump.And the reason that I'm doing this is because the poll account is a PDA account, and to be able to access the correct pda, you're always going to have to pass through its seeds and bump.Okay, so now that we have that, we can just go over to the actual logic for writing the initialize candidate function.Now we have both of our accounts initialized on chain, and we have an instruction for each.
So the very last thing that we have to do here is have an instruction that's actually doing the vote.So let's start with that.But before we actually write the logic there, there's one thing that I want to point out.Back to a few fun little rust things.So you can see, earlier we wrote our poll here and we made it mutable, but now we're getting an error and it's saying it cannot move because it's behind a mutable reference.
And the reason here is because now we have this mutable reference for the poll account.So the way to fix this, I'm going to remove this and make a reference to it and to make it mutable.
And now we have our error go away.Okay, so if that ever comes up, that's how you'll fix it.So two different ways to write variables with rust.
Okay, now the very last function is just our vote.So we have pub FN vote and we're going to pass through our context.I'm just going to copy this over so I'm not writing everything out completely.Okay, and we'll update it.
Okay, now we have our outline of our function.And first thing is creating that derive account struct so I'm going to name this vote and I'll go down here and make the actual struct so I can copy this.I do a lot of copy and pasting here with anchor because again, it does get repetitive and then we'll just edit it as we need to.So here is now going to be our vote, always going to have the signer as our first account.We still do need the poll account to access all of the information for the poll that we're voting on.
And then we also need the candidate accounts and we're going to need to update this.So I copied this over from the candidate accounts derive account struct.And that was when we were initializing the account, but now it's already initialized.So we can get rid of that.That and everything you need for initializing are these three constraints.
And now we're just going to make it mutable because we will be updating the account state by adding a vote to that candidate.Okay, and that is good to go.And we can actually get rid of this system program for this one because we're not initializing any new accounts on chain.So there you go.These are the three accounts that we're going to interact with for voting.
Now we can just write the logic and then we're good to go.So everything that we need to pass through for this vote instruction, there are a few different fields.One is going to be the poll ID and the other is going to be the candidate.And these are actually not using in the logic.But the reason we need to pass them through is because we're using them for our PDAs down here.
So if we look back again at this vote struct, we have a poll ID that we're using for our poll account PDA and we have a candidate that we're using for our candidate pda.So the way this actually works is if I'm voting for candidate A and there's A, B and C as my candidates, every PDA is going to be generated based on the candidate's name.So there'll be a PDA generated based on A, B and C. And when I'm passing through what candidate I want to vote for, I'm going to be passing through A as my candidate parameter, and it's going to pull that value from the input of the instruction and it's going to use the it here when it's calculating the pda.So then it's generating the account that corresponds with candidate A.So that's why we're not actually using candidate in our logic for the instruction, it's all mapping back to the PDA.
So it's always best to optimize how you're using PDAs.And this is one example for that.Okay, so now that we have that taken care of, let's add those two sections.So we'll do the poll ID and the candidate.
Now the logic.Okay, so what am I doing?Well, I'm going to be saving the vote to the candidate account.So let's load in the candidate account.Let's.
So we're going to load in.Let candidate equal and mutes.We're going to load in our context and pull in that candidate account.CTX accounts candidateaccount.Now we have all the information related to the account and we can update it in our logic.
Now, first check.We want to make sure that someone is voting while the poll is actually active.So we need to check the current time and make sure that it falls in between the start time and the end time for the poll.So let's load in what the current time is.So let current time equal to.
And we're actually going to be able to use clock.And clock is from a Solana crate that you can import and it represents the network time from the blockchain chain.So clock is.We're going to do git and we'll unwrap that.And one field within clock is the UNIX timestamp.And that's what we're going to be using to keep track of what time it currently is when someone processes a vote.Okay, so now we have that.And again, UNIX timestamp is a U64 type, which is why we saved our start time and end time as U64.Now that we have that, we can do if we'll just do some very basic IF statements here.So if the current time is greater than the voting end, so we'll do the context and we'll grab the voting end time from the poll account, which is why we needed to pass that poll account through here.
So pollaccount.poll voting ends.
And I'm going to have to cast this as an i64 because actually Unix timestamps can't be negative.So it's an i64, not a u64.And we'll put this in brackets because I'm in parentheses because I'm casting it.
Okay, so if that happens, we're going to return an error and I can make errors by using the.
And I can write errors in anchor by using the error code macro.So I'm just pasting this in so I don't have to type it all out.But we have an error code macro and you're able to write all of the errors that you want for your instruction and then a message that you want to emit whenever that error does occur.So let's go back and then specify our error.
So it's just the error code.And then we're going to do voting end ended.
Now we need another if statement for essentially the opposite.So let me copy this, paste it in.And now if the current time is less than or equal to the poll, voting start.
Okay, once again, casting as an i64.And then we're going to return a different error which is voting not started.
So so far in our vote instruction, we have checked the current time and made sure that the current time falls within the voting start and end time.So it's actually an active poll.We've loaded in the candidate information.So the very last thing we have to do is just save the vote to the corresponding candidate account.So since we already know the right candidate account is being loaded in, because it's taking this input parameter and generating the PDA based off of it, all we have to do is take candidate and then to set the candidate votes field to increment by one.
Okay, and we're good to go.Now back to the rust warning for having variables that are unused in your logic.That will just put our little underscore here because we are using it in the account struct.Okay.And that is everything that you need for your program.And we can just build it to make sure everything runs.But just a quick overview.Overview.We have our program macro here.All of our instructions live within that program macro.So we have initializing our poll.Our poll accounts is the account that's holding all of the data for the new poll that we're generating.It's also keeping an index of how many candidates are corresponding to that poll.We have initialized candidate, which is the instruction that will be ran every single time you want a new candidate added to the poll.And then we have vote.And vote is when anyone wants to come in and make a vote.
They're going to specify the candidate they're voting for and it's going to update the candidate's account state with another vote in their state.And that's everything.So whenever you're writing smart contracts, start with your account, what state do I want to store?Then you write out your instruction.
You're going to write all of the corresponding accounts that the instruction needs to interact with, and then you can finish out your instruction logic.So the last thing we're going to do here is just build our instruction.If I go to my terminal and I'm going to, let's see, making sure I'm in the right directory here.I'm just going to type in anchor build.And once again, we're using that anchor CLI that we've installed earlier.
It's going to compile my program and make sure everything works.The great thing about Rust code is if your code compiles, you're pretty much good to go.It has a really great compiler.It just takes a long time to compile.So we'll let this load, probably speed it up for the video and make sure everything looks good.
All right, so our code compiled, everything's good to go.Now, one more thing that I want to show you is just the pulling, because I did mention earlier that this public key maps back to your address and I just want to showcase this.That.So in our install script, we already created our Solana Wallet address and we funded it with Devnet Sol.So I should just be able to deploy.
So if you run the command anchor deploy and you have the flag provider cluster, and then I type in devnet, it's going to deploy my compiled program onto Devnet with that local key pair that we generated earlier.
And I spelled it wrong.So one second provider.So you can see it's deploying to the Solana Devnet cluster.The upgrade authority is my key pair that's saved on my local machine.It's deploying the program.
Here is the path.So this path is taking you to the SO file that was generated when you ran anchor build.So that is over here.If we go to target it and deploy, you have this voting.So this file is what is being deployed onto the Solana blockchain, in our case, the Devnet chain.
Great.So it's saying I need to install a package.So seems like I'm a bit out of date in my cli.We'll just let this install and then let it finish deploying.Then we're going to go to the Solana Explorer and then just check out our program.
Okay, great.So we're good to go.Now I'm going to open up a Browser.Type in explorer.solana.com so explorer, that's our block explorer currently connected to the Mainnet cluster.I'm going to switch over to Devnet and let's go back over to our VS code and we're going to go back to that declare ID function that I talked about in the very beginning.
So copying this public key, I'll go back to Firefox and I'm going to paste this in.
There we go.So here's my program that was deployed on chain and you can see my deployment happened just a few minutes ago.And this is how you're able to access all of the information that's actually running on the network for what you're building now.We finished writing our first Solana Smart contract and now we're going to talk about how to actually test it.So contract is written, but when you're testing a program, you want to be able to actually test against the Solana virtual machine.
So how can we do that?Well, there is a framework called LightSVM.So LightSVM, it's a lightweight library for testing Solana programs.It works by creating an in process Solana virtual machine that's optimized specifically for program developers.So we're going to be writing rustic tests that correspond with our Rust Smart contract that we just wrote.
Here is all of the latest VM documentation.So there's several different helper crates that are available.And then if we click into the documentation, it talks about how to get started, why you would Want to use LightSVM for your testing, how to test your program, some examples.So examples, this is just some very common copy and paste test for certain ways that you would want to set up your environment.And then we have our API reference and then just additional documentation for each helper crate that's available for you now.
Great reference.As you're writing custom lidocum tests for the programs that you're writing.But we're going to go to the voting app that we just wrote.Now I don't want to take the time to actually manually write out all of this code on the video, so I already wrote it beforehand.And we're just going to go over all of the important concepts that you need to know to be able to write latest VM tests.
So this is for an anchor program that we wrote.So we're going to be using some additional helper crates for Light svm.So we import Anchor and then we also import the Anchor Lightest VM helper crate.And by doing that, it's going to help generate clients from the IDL that you generated when you ran Anchor Build.So whenever you're writing your latest VM tests, you want to make sure that you ran Anchor Build beforehand and you have your target directory generated.
And within your target directory you do have an IDL here because it's going to be accessing this IDL to generate clients for you.And this keeps all of your tests type safe in Rust.Now going back to this file, here are all the important concepts that you need to understand to be able to write your own latest VM tests.First is declare program.You're going to be using this and calling in what your IDL is for the application.
And this is going to be able to generate the types for you.So here are the paths to be able to grab each type that you have.So you can see when I hover over this, I have the type that we wrote for our candidate account data structure and then the type that we wrote for our poll account.So now we can easily access all of the data for each of these types of and then this is pulling in the SO file that was generated when you ran the anchor build command.Now when you're writing latest VM test, you do have a little bit of a setup before you can actually test your program.
And the reason is you're generating an in process vm.So you're going to have to create all of the token accounts or deploy the program and get everything set up to be able to actually use it.So how we're setting this up up, when you use the anchor context from your anchor lightest VM test, you'll be able to now set up your vm.And because we're using the Solana clock, which you can see here, we're using this for testing because we do have to get accurate timestamps to know if the poll is a valid time or not.And all of that is just setting up your environment.
Now we have a few helper functions and this is just for generating PDAs so we know when we're writing a PDA we have to define our seeds and then we define our bump.And the PDA is generated by running your input seeds and the bump through a SHA256 hash function and it returns your public key for your PDA.So how do we now get that?We're going to need to get the PDA account for each of these addresses to be able to accurately test it.So you can do that with this helper function, that's find, program, address, address.
And all you have to do here is just write out all of the seeds that are associated with the pda.So we have these two helper functions to be able to get the PDA for our poll account and the PDA for our candidate account.Now that all that setup is done, typically it's a bit more set up because you'll have to initialize tokens on chain, create mint accounts, create token accounts, things like that.But here we're just handling data where you didn't introduce tokens yet.Yet that'll come with our escrow program, which is the next project.
So here we're just worrying about data.That's all of our setup.Now we can write our first test.So to write the test we're just using our setup function.Now we do need to fund an account.And the reason we need to fund an account is because we need to have an account that's going to be the signer for processing the transactions.So this is creating an account for us, we're funding it with, with Lamports.And then we're going to unwrap it.Now we have all of these values defined because these are all of our input parameters for our instructions that we're testing.So little bit more set up there.
Now all you have to do is call the instruction from your anchor context, because the anchor lightest VM crate is now generating this anchor context for you.So when I call this, I can now call the program and I can call the accounts that are associated with the instruction that I want to execute and then all of the arguments that have to be passed through for the instruction.So here you can see for initializing the poll, we did specify three accounts in our derive account struct, which was the signer, the poll account and the system program.All of that we do have to specify in the accounts section when we're calling this instruction for testing.Then the next section is your arguments.
And for initializepoll, the arguments that we accept are the poll id, the start time, the end time, the name and the description.So this is just pulling in those parameters for the function.Now all you do after that is specify that it's an instruction and then unwrap it.Then you're able to get the result by running the execute instruction function from LightSVM.So you can see this execute instruction here.
So what that's gonna do is execute the instruction through the in process Solana VM that's running with LightSVM.And it's going to basically be like you're testing it on chain.So here we're just making sure that the result actually was successful and then we are asserting that the is the accurate answer that we want.So here we're Asserting that the result is actually the answer that we want.We're pulling in the poll account data after the transaction took place, and then we're asserting that each field in the poll account has been correctly updated to the input parameters that we've already defined in our argument section here.
So that's pretty much it.Now, what this function does is if I run it, it's going to test the initializePoll instructor.And you can see down here, one test passed.So we're good to go.Now, you can take on your own time when you clone this code to look through the other instructions, but when you want a 100% test coverage on your code, you just want to go through all of the instructions and then potential edge cases that you can think of.
So here we have testing the initialized candidate, testing a successful vote.We have testing multiple votes, and we have multiple candidates as well.So here we're testing some of our error cases.So we had two errors.One was voting before the vote actually started, and one was voting after the voting window ended.
So this is testing to make sure it returns the correct error.So you can see here the result.We want to assert that it returned an anchor error of voting not started.And this anchor error is from that error code macro that you generated when you were writing the process program.So here's the other error case, and then testing multiple polls as well.
And that covers pretty much 100% test coverage for our voting program.So if I just want to go through and run all of the tests, I can open my terminal and I'm just going to run cargo test, and that's going to pull all the LightSVM tests and run them.And you can see here, it ran eight tests, all the eight that we just went over, and all of them passed.So we're good to go.And that is how you're going to be testing all the programs that you write.
So Lightest VM is an efficient way to test your programs.So here's an example with the voting program that's just handling data, and then as you progress the rest of the projects in the boot camp, it will show the Progression of using LightSVM for more advanced use cases, and that that is testing your voting program.
10. Escrow Program Design and Testing
Explains the escrow pattern and how the program uses PDAs, CPI transfers, and signer seeds to hold assets trustlessly. It then walks through building, deploying, and testing the escrow flow with LightSVM.
In this section, we're building one of the most fundamental patterns in blockchain development, an escrow program.An escrow is a neutral third party that holds assets until certain conditions are met.In traditional finance, this might be a bank or a lawyer, but on the blockchain, we replace that trusted Middleman with code.So a smart contract that enforces the rules automatically.Escrows are everywhere in web3dexs use escrow logic for token swaps.NFT marketplaces use it to hold assets until payments clear.Freelance platforms use it to protect both clients and contractors.Once you understand the escrow pattern, you'll recognize it in almost every defi protocol you interact with.So in this program, we'll have three instructions make which allows someone to create an escrow, deposit their tokens into a vault and specify what they want in return.Take lets another party accept those terms.
They send the requested tokens and receive what's in the vault.The whole swap happens automatically, and refund lets the original creator cancel and reclaim their tokens if they change their mind.Beyond the escrow logic, we'll learn how to test anchor programs using LightSVM, which is a fast, lightweight alternative to running a local validator.By the end, you'll have a reusable pattern that you can apply to countless blockchain applications.Now, let's get started.
So, I'm going to link a repo of the code, and we're going to go over just the concepts and code snippets, rather than code from scratch, line by line, just to stay more engaging and avoid a lot of repetitive code.Now, we already talked about how most things in defi are an escrow, but let's chat about why.So, let's say I'm making a thousand dollars bet on the super bowl of my friend, and I'm betting on the Eagles, and he's betting on the Chiefs.But how do we know the winner is actually going to get paid?Well, in traditional finance, either I'd have to trust my friend that he'd actually pay me when the Eagles win.But this is an emotional time and I don't know if he will.So we could hire a third party to hold our money and give it to the correct person once the bet is over.
But again, this involves a lot of trust.So blockchain fixes this.It creates a fully trustless third party because this third party is just code.And that's what we're going to write today.And it's an escrow, so it holds funds and releases them to the correct person when the specified conditions are met.So if we made a bet on chain instead, the code would query the outcome of the super bowl at the specified time and immediately release the funds to the correct person.And it's as simple as that.So how do we get started?
Well, whenever I'm Writing smart contracts, I always like to start with the state.And as we now know, Solana programs are stateless.So when we want custom data stored, we're going to need to create an account in the smart contract.So let's start with where we're going to define that account.So first let's open up the code.
If I go to GitHub, I'm going to link this repo, we're going to Copy this GitHub CLI command, open up our terminal, and I'll just zoom in a bit so you can see.I'll paste in the clone command, and we're going to first CD into the directory of the project that we just cloned.
And then we're going to take the code that was already written for us and we're just going to go through it.So first we said we're starting with the stack state.Now let's see where that actually is.
If I go into programs, anchor, escrow, source, and then there's a file called state rs, and this is where you're going to define every account that you create, the custom state for that account.So accounts are where all of your data is stored.Here we're just going to have one account and that's going to be our escrow accounts.So you can see here we have this struct, that is our escrow, and it's going to have all the fields of the data that I want to store for my escrow.And here all I'm storing is the seed, the maker.The maker is going to be the person who makes a new escrow contract, a new bet, a new whatever logic that they want to have.And then we have these two mint public keys, and that is going to be allowing for two different tokens.Tokens.
So if I want a mint account for one and a mint account for another token, and then the receive is going to be the taker, whoever is going to receive the funds that the maker is locking into this vault of the escrow.And then we store a bump.
So for best practice, when you're writing anchor programs, you always want to store the bump, and that is just going to make things more efficient.We'll go through exactly why once we get a little deeper into the code.But there you have it.You have your custom data structure with all of the information that you want stored.Now you can see that there's these two macros, and we talked about before how anchor simplifies a lot of smart contract design, and they do that with these macros.That are created.So here you can see this derive init space.And what this does is it automatically calculates the space needed to store the struct on chain.So anchor adds up all of the bite sizes of all of the fields that you've stated here in your struct and then it calculates how much space it's going to store.
So you don't have to do that manually.
Now there's also this other macro and that is defining what the discriminator is.So this is setting a custom discriminator that is a one byte discriminator.Normally anchor uses the first eight bytes of the SHA256 as a discriminator.This overrides that with just a simple one.So the discriminator is written at the start of the account data to identify a specific account type.
So everything for your state is good to go.But all this is doing is defining what is in your state.So how do we actually create the account that's going to hold this state?Well, let's go to these instructions.You have a folder of three different instructions.Our make, refund and take.And we know that the escrow starts at the make instruction.You're having a maker create a new escrow.If you go to the librs, you're going to see this program macro.The program macro is part of Anchor, defining exactly all of the instructions that are going to exist within the smart contract that you're writing.
So you can see we have have those three instructions that were defined in this instruction folder.And we're going to start with make.So make is where you're actually initializing a new escrow account.And we're just going to click into this make context.So if you're familiar with Anchor, this is a very familiar format that you'll see.
It always starts with this context as your first parameter.And the reason why is, is this is where all of the information for the accounts that you need to handle for the instruction is going to be given.So if I click into this, it's going to take me over to the makers file.Now here you can see this derive accounts macro and a data structure inside of it.So this derive accounts macro, what it does is it tells all of the accounts that are needed to be able to process the instruction that you're writing.
And it's going to tell you everything that needs to be done to the account.So what does that mean?Well, for example, let's start with the escrow account.So we already defined what the state looks like but here you can see there's a lot of constraints that were written within this account macro.So what does that mean?
Well, first we have this init constraint and what that's doing is saying this account doesn't exist yet and I need to create it.So instead of writing all of the logic for how to create an account on Solana in anchor, all you have to do is specify the init constraint when you are writing out your derive accounts macro.So here you have the escrow.If I click into this escrow type, it's going to go back to the data that we've already defined.Now let's go back over and you can see we already talked about init payer.
The reason we need a payer here is because we're initializing a new account and accounts need to pay rent on chain.So this is how data is actually paid for.You're going to have the amount of space that is needed on chain chain calculated and then you're going to pay for that space.So someone has to pay for that.So here we're going to define that the maker is paying for the space on chain.
Now this is once the account is closed, able to go back to the maker.So it's not a sunk cost, but kind of just holding their spot as they're using space on chain.Here this is just calculating the space because we need to know how much to pay.But this is where the actual escrow logic comes in.So you can see this specific constraint defined as seeds.And then there's a lot that happens after.So what this actually is is defining a pda.So a PDA is a program derived address.And what that is is it is a public key that falls off the ED25519 curve and it's generated by having this input of seeds plus a bump.And it runs through the SHA 256 hash and generates a public key.
If that public key does land on the ED25519 curve, that means it has a corresponding private key and it's not valid as a pda.So it's going to regenerate the bump.The bump always starts at 255.It will decrement by one every iteration until it actually lands off the ED25519K curve.And when that happens, it's now a public key that does not have a corresponding private key.
And the reason that that's important is you're only going to be able to sign for the transaction with the signer Seeds through a program.So now no one can find your private key and be able to sign for you.So all trust lies within this code and the program itself has to sign with the signers seeds.So what are those seeds?Well, they're exactly what you define here.
So here you can have any type of seeds that you want.All that I'm doing specifically for this program is writing out escrow, having the maker who is going to take the public key of the signer, store that as part of the input seeds for the PDA and then also this, this seed parameter.So this is just an example to show that if you want to have part of your seeds as something that doesn't exist within the struct, how that works is you're able to take it as an input parameter for the instruction.So you can see here there's this instruction macro and what that's saying is, hey, I'm using this variable seeds and I'm using it within my account, but I don't have access to it.So how do I get it?I'm going to get it as an input parameter.So if I go back to the librs where the function is defined, you can see that seed is taken in as a parameter for the function.
So going back here, just to show exactly what I mean, if I remove this macro where I'm not saying this comes as an input parameter of the function.Now you can see seeds is no longer recognized and if I save this I should be getting an error.But yeah, you can see seeds doesn't exist, cannot find the value.
So if I go back and put the instruction macro back, give this a second to rerun the rust analyzer and now seeds can be found again.So this is just an example of if you want to have part of your seeds for your PDA as as a value that's not defined somewhere within the struct, you can just pass it as a parameter in the instruction.Okay, so that's kind of high level overview of having a PDA as your account.And we'll go through exactly how to sign later on when we're going through the code, but let's just go over the rest of the accounts that are passed through.When I'm creating this make function, I not only have the escrow, but I'm going to have to define all the other accounts that are going to need to be interacted with in order to actually execute this instruction.
So you can see I have this mint A mint B.This is allowing for two different tokens to be interacted with when you are actually executing the make function and you can define what that mint account is.So if I wanted to put usdc, I would define that mint account here when I'm creating the instruction and, and any other account that you want.Now you have this maker ata.So what is that?That's an associated token account.So the mint account, which we've gone over a few times within other projects in this bootcamp, but the mint account is kind of the global information of the token and the token account is what actually holds tokens.So if I want the maker to be able to send tokens to the vlog vaults to be stored for this escrow program, it's going to have to have a token account associated with the instruction to be able to send those tokens.
So this is just having whatever the token account for, the maker is of the token they're sending into the vaults.And now the very last section here is going to be the vault itself.
So we did define the escrow, but all this account does is store data.It doesn't actually store tokens.So I want to have an account associated with this escrow that's going to actually hold the tokens so then they can be correctly released when the logic has been executed.So let's see, this is initializing a new token account that's associated with this escrow program and it's defining the payer for the token account is going to be the maker.And it's saying what this token account is is.So every token account is going to have an associated mint account because that is defining the global information of what token you're actually storing into that token account.And now you can see this is kind of the key line.We have associated token.The authority of the account is going to be the escrow.So as we talked about over here, the escrow is not a typical public key with a private key that can be signed for for it's a PDA that needs signer seeds for the program to be able to execute the transaction.
So what actually has authority over this vault to be able to release funds is the program itself.And that's what's making this one line, the authority being the escrow.This is what's making everything here trustless.And then the very last three lines are very typical.Whenever you are defining all of the accounts for instruction, it's going to have your associated token program that's needed whenever you're creating and ATA accounts your token program.Because we're interacting with tokens and then the system program is needed because we're initializing new accounts on chain.So this is just accessing other program information to be able to execute all the commands that we want for this instruction.Now that's how your derive account struct works.All of the accounts that need to be accessed to be able to execute the logic and the instruction that you want.So that defines everything we need for make.
And now let's go back over to the actual instruction.
If I go to librs, you can see that this has been organized with a handler.And if I click into this, it's going to take us back to the make page.And this handler is executing two functions, populate the escrow and deposit tokens.Because if I'm the maker, all I'm doing is creating a new escrow, defining what information needs to be in that escrow and then I'm going to deposit tokens into the vault that I just initialized.So let's look at what the logic actually is.
When you're populating the escrow, all you're doing is defining all of the information that needs to be stored in the escrow account.It's just the data.So if we go back to state here, you see all of the data that's specified, that is the same data that has been defined for our populate escrow and instruction.
Now the other function that's being ran here is deposit tokens.And this is where we're introducing a new concept and that is our cpi.So CPI is a cross program invocation.And what that is is you are calling an instruction from one program for another program.So we're writing our escrow program, but when we're calling a cpi, we're actually calling a transfer instruction from another program onto chain.
Now when you see the cpi, there are specific accounts that are interacted with here.So this transfer checked instruction is what we're calling and that is transferring a token from one account to another.So you can see our from accounts is the makers ata.And we're using the mint of the token that we've specified and it's going to the vault itself.And the authority is going to be the maker in this case because the maker is signing from their own wallet and depositing it into the vaults.
So in this case, the maker's wallet is a public key that does have an associated private key.So it does fall on that ED25519 curve.It's essentially going to be the wallet of the user that's going to be signing the transaction here, so we don't have to worry about the PDA signer seeds, but we'll get into that later.So this is just a very basic CPI for transferring a token from one account to another.And in this case, the account that it's going to be transferred into is going to be the Volt account of the escrow program.
So the escrow is now holding this token.Okay, so that's everything for make.And now the reason we're not actually writing this line by line is it does get pretty repetitive.And you'll see here when I open the take file, actually, I'm just going to lay these side by side so you, you can see how similar they look.We're creating our derive accounts.
We're specifying a new data structure of all the accounts that need to be interacted with for this function to be able to execute.You can see they're pretty much the same accounts.You have your maker.In this case, we're now introducing another account which is the taker who's going to be taking those funds.And then we still have the escrow.It's going to be defined pretty similarly here.The only thing that changes is it's already been initialized, so I don't have to worry about initializing it as a constraint.But I do have to add this mute constraint.And the reason I have to add it is because variables in Rust are immutable by default.And here I'm going to be changing the data in the escrow account.So I have to define it as mutable because something's going to change.So I'm just saying, hey, I am changing the this and I'm just going to let you know that's going to happen.
Now here we have a few additional constraints that have been added.And what.These are just kind of some security checks.
So it's defining that the maker is the correct public key.The Mint A and Mint B for the mint accounts are also the correct public keys.So this is just additional security checks to make sure the correct accounts are interacting with this instruction.And if, if it is an incorrect account trying to interact with the instruction, it's going to return an error and say you don't have access, this is an invalid account.
Otherwise everything else looks pretty much the same.You're defining all of the accounts that need to be interacted with.But now we're just adding the taker into this equation.And now the one thing that is different here is how the CPI is actually executed.And, and the reason is that we now are going to execute using our pda.
So for the taker to be able to now take the funds, they're going to be taking the funds from the vaults, and the vault has to be able to sign for that instruction.And because we now know that the vault is a pda, it's going to have to be signed from the program with the sign receipts.So we can just compare what this looks like.Like we're still transferring a token.So we're making a CPI call to the token program, calling the transfer checked function, and that's still all the same.
And you can see here we're defining once again the token program, the transfer check function, and it has all of the same parameters.However, when we're calling this CPI context, instead of just saying new, we're saying new with signer.And the reason why is we're going to have to sign with signer seeds.So there's an additional field added here which is your signer seeds.And what these seeds are, we're going to have to specify and they're going to be the seeds of the PDA that you defined.
So you can see here we've defined what the signer seeds are, and we can make sure they're correct by going back to our make instruction when we actually initialized the escrow program and defined what the seeds were for the pda.And you can see here, here we have our escrow as our first seed, we have our maker as our second, and then we have the escrow seed as the last.And then all we're doing after is just defining the bump.And the reason you're defining the bump is to just make it easier to actually find what the PDA is.So we're saving the bump of the PDA when we initialize it.You can see here we're saving the the bump.And the bump is that 255 that's being added when you're actually generating the PDA.So just to recap, when you're creating a PDA, it has these input seeds plus a bump starting at 255.
And then every iteration it decrements by one and those values are going to be ran through the SHA256/ and output a public key.That public key is either going to fall on the ED25519 curve or.
Or it's not.If it doesn't, it's a valid PDA address.If it does, it's invalid and it's going to have to Run again.But now it's going to run your input seeds with 254 and so on.So if I save the bump and I know what it is when I'm recalculating my pda, it's going to know immediately by just pooling that bump that I saved.
If I don't save it, it's going to run through that cycle continuously until it finds the correct address.So now we're saving like CU cycles and making the contract more efficient by saving it and just being able to directly call that value.Okay, so that shows the difference between just signing with your regular CPI and signing with a pda.And that's how we know that this is more secure of a program, because the PDA is the authority here for the vaults.Okay, so that goes over pretty much everything that you need for writing an escrow program.
So now that we've gone over the whole program, all that we have left is to build it and then test it.So how we're going to build it is we're going to open up our terminal and just type in anchor build.So anchor build, it's using the anchor CLI to compile all of your rust code and then create this target folder here.So we're going to let this run and let everything compile.And here you can see it finished and compiled successfully.
If you are following along, it's going to take a little bit more time for you because I pre compiled this for this video.So just wait until it completely finishes.Once that's done, you can open your target directory, go to IDL and what we're going to do is copy this IDL file into this IDLS directory up here.And the reason is so our latest VM test can access it.So now that it's compiled, we're going to be able to test.
I will just copy this over.So we'll go to ido, anchor escrow JSON and then paste it into the IDLS folder.Now that, that is good to go.I'll be able to run cargo test and what that's going to do is run our light SVM test and I'll go over how those are working.We'll do cargo test, it's going to compile and then run the tests.
And you can see here, I'll just zoom in a bit so you can see it better.
We have running one test test.The escrow make and take has passed cost.So let's see what that test looks like.I'll zoom out, go into the test folder and it's showing an error.Just because my rust analyzer is a little behind, it's just waiting to be able to read this IDL file.
Once it fully reloads, that error will go away.But.But for now, let's just go over what these tests look like.So this test demonstrates a complete escrow flow using lattice VM and what LightSVM is.It's a fast, lightweight Solana virtual machine that's used for testing.
So here you can see we're importing a couple crates.So one is our Anchor Light SVM crate and our latest VM Utils crate.And what that is doing is using LightsVM but adding some additional helpers to be able to make it a little bit more compatible with the anchor program that you're writing.So you can see here we have this anchor lang declare program for the anchor escrow.And what this does is it generates all the client modules for the program.
So it's kind of like this magic line that helps you keep all of your code type safe.And it's referencing this IDL that we pasted over here, here.So by using this, you don't need any manual serialization.Now we're going to just go through the test.All it's doing is setting up your Light SVM environment.
So you can see right here where it has Anchor Light SVM build with program, and it is referencing your program ID and the program bytes to your SO file.This is where it's able to actually read the program and test against it.And it's using this, running it again against the Solana virtual machine that Light SVM is running, and then being able to test as if it's running in a real blockchain environment, which is why you're able to create test accounts and create token mints and fund those token accounts to be able to fully, like, emulate what you want your environment to look like.So we have everything set up here for our environment.We're also creating token accounts to be able to interact with our escrow program.
And then once all of that is created, you're going to be able to actually call the instructions from your program and execute them.So here we're calling the make instruction and we're passing through all of the accounts that are needed for that instruction.So if you remember when we were writing the program, we have that derive account struct where you're specifying all of the accounts that the instruction is going to need to interact with.And here you're going to specify them Once again, when you're testing out the program, so we're calling the make instruction, defining every account that it interacts with, that you can verify in your derive account, struct that you wrote in the program, and then you're specifying all of the arguments that need to be passed through.And then once you unwrap that function, you're able to execute the instruction on your Lattice VM virtual machine.
And then we'll just asserting to make sure that everything resolves how we want once the instruction is executed.And then we're able to now test the take instruction as well.And this follows the entire escrow flow.We're able to make an escrow, have the logic be defined, and then once it is met, we're able to have the take construction called to be able to finalize that interaction between the maker and the taker.So there's your escrow program, you have your tests, you can see the test pass down here and you're good to go.
So that was your escrow anchor project for the bootcamp.And stay tuned for what's next.
11. Private Transfers and Merkle Roots
Introduces the private transfer project and explains how commitments hide deposit links. The section then adds Merkle tree roots and leaf indexing so withdrawals can prove membership without revealing the deposit.
In this next project, we're building private transfers on Solana.Users deposit Sol into a ship shared pool and then withdraw later.But there's no way to link which deposit belongs to which withdrawal.We're not hiding who deposits and who withdraws because those things are just going to be visible on chain, but we're hiding the connection between them.Without privacy, you've got something similar to the escrow that you built earlier.
Alice deposits, Bob withdraws.Everyone knows that Alice sent funds to Bob in the project we're going to build now, Alice deposits, Bob deposits, deposits, Carol deposits, and someone withdraws to some sort of address.No one knows if that address belongs to Alice, Bob or Carol, or someone else entirely.Now, before we go any further, this is an educational project.It is not recommended to deploy this onto mainnet.
Privacy tools have different legal implications depending on where you are.So check the compliance requirements in your country before building something like this for real users.If you know, you know anyway.The way this works is through zero knowledge proofs.A zero knowledge or ZK proof lets you prove something is true without revealing the underlying data.
Like proving you're over 18 without showing your actual birthday.In our case, you prove that you made a valid deposit into the pool without revealing which one is yours.In this project, when you deposit, you'll generate random secrets on your device, hash them together and send that hash plus your Sol to a pool on Solana.You get back a Deposit note, which you need to save or give to whoever needs to withdraw.Then, when someone wants to withdraw from the pool, they use their deposit note to generate a ZK proof on their device locally, send it to Solana.Solana verifies that it's legit and releases the funds to you.The secrets never ever leave your device.In this project, we're going to start with a public pool and then we'll turn it into a private pool in six steps.One we'll go through step by step so you can get a deep understanding of how it all works together.
All the code used here is linked in the description, so feel free to clone the repo and follow along.
And by the end you'll understand how zero knowledge works on Solana.Let's go.So in this first step, we're just going to see what we have at the moment.Currently, we just have a public pool and we're going to turn that into a private pool.So if you head into the repo here, you can see everything in here.We've got different branches that we're going to work through.So we've got our main branch that's going to have the code that we want to actually have working.It's going to be our private transportation.You can just dive into that now if you want.We also have our starter branch and that's what we're going to be starting with.
And we have these step branches here and we're going to go through all of them to see the different steps involved in turning a public transfer into a private transfer.So let's go ahead and clone this repo git clone, call it private transfers.And then we'll CD into that.That and open it up in our code editor.So I am using Cursor here because I really like Cursor tab.I think it's amazing.But you can use whatever you like, obviously.Just going to zoom in so you can see this a bit better.And I actually need to check out the starter branch.All right, so now we have our starter branch.We've got our Anchor folder.This is going to be storing all of our Solana programs.You've probably heard of Anchor from our previous Bootcamp tutors.We've got backend.This is going to be what's doing all of the ZK proof generation.
You can also do this on the front end at client side.But I just wanted to separate things a little bit.We've got our circuits.This is going to be what's actually running to do the Zk stuff.They are written in a language called Noir.And we're going to get to that in a further step.We've got our front end.This is going to be what's going on, what's displaying and also doing all of the transaction creation for sending to the Solana program.And we've got our instructions, which you can ignore for this demo.So if we have a look at our anchor directory, we've got programs in here.
Private transfers, source, and this is the main file that we're going to be looking at today.Got some steps in it so you can see what we're going to be doing.So this is just our public pool file.If we have a look at it here, we can see we.We've got three different functions.
We've got initialize, we've got deposit, and we've got withdraw.The way down here.These are the functions that we're going to be kind of editing today.So our initialize function just sets up our pool.If we have a look at our initialize accounts, we can see what goes into it here.
So if we have a look at the initialize accounts here, we've got our pool account, which is just our program account.Then we've got our pool vault.This is going to be our PDA that actually holds all of the all.So we've got our seeds here.We're just setting them as vault and then also giving it a seed of the pool that it belongs to.
And we're giving it a bump as well.Bumps are kind of cool.They're needed for PDAs, because PDAs do not have private keys.And if we didn't give it a bump, the math that generates these things together might accidentally give it a private key at some point.So we give it a bump just to kind of throw off that elliptic curve so it doesn't have a private key.
So we're setting that we have our pool vault, and then we're also setting that we have the authority, which is the person who's actually in control of the pool pool.And we need our system program because our system program is going to be handling our SOL transfers.Let's head back up to our initialize function.
So in our initialize function, we are setting the pool authority as whoever we passed in in the accounts.And we're setting our total deposits to zero.Total deposits are pretty useful when it comes to private transfers in a pool because we want to know the anonymity set of the more transfers you have, the bigger anonymity set, the more privacy you have.So it's kind of important to keep track of it.And then we're sending a message out pool initialized and saying that everything is good in our deposit function right at the moment, everything is just public.So we're taking in an amount and we're requiring that it's above a minimum amount.This is another useful thing for zk, because when you're generating a proof and trying to verify it on chain, there's a small computational cost and a little bit of a transaction cost in there.
So we're just setting a minimum deposit amount so that whatever you're depositing is not less than the cost, because that would be kind of pointless.And then we have our CPI here.So what we're doing at the moment is we're CPI' ing into the system program to transfer SOL.
You might have CPI'd into a token program before in your escrow, but token programs can only transfer tokens.And we want to transfer a SOL.So we're CPIing into the system program.Then we are saying that we want to transfer from the deposit into the pool vault and doing that transfer.And we're just a minting event with everything public in it at the moment.
Then in our withdrawal function, it's pretty similar to our deposit function, except it's coming from the pool vault to the person who's withdrawing.So we have to prove that our program is actually an owner of that PDA of the pool vault.And the way that we do that is we get the seeds here.So we already know the seeds are vault and the pool key and then the bump.So we just generate these seeds in here, we set them, and then when we do the cpi, we do new with signer and our signer is going to be the signer seeds here.
So then when we do the system program transfer, it'll work from doing the pool vault to whoever is trying to do the withdrawal.Everything is public in the moment.And we're also just going to run the front end so we can kind of have a look at what we want to be doing.So let's do CD front end.And I'm going to be using BUN here, but you can use like PNPM or whatever you want.
We'll do BUN I and then we can do BUN run dev and we'll run on localhost.
So this is what we're going to be doing.Neither of these buttons are going to work right now, but we'll just fill in a deposit and then this will Give us something that we can copy, which is going to be like our deposit, note our secrets.Then we'll paste that into here, and then we can withdraw from a separate A client.And that's what we're going to be doing over the next few steps.We're going to start with the deposit.The transaction itself will always be on chain.Anyone can see that.Alice called the deposit function.But we don't want our program to store this information in the pool because then when someone withdraws, it's an obvious link.Right now, our deposit event logs the depositor's address and the amount.
Instead, we're going to store and emit a commitment, a cryptographic hash that represents the deposit deposit without revealing who could claim it later.A commitment is just a hash.In the ZK world, we call it a commitment because you're committing to certain values without revealing them.Think of it kind of like a sealed envelope.Everyone can see that the envelope exists, but no one can see what's inside.
And only you know, in our program, we will store a commitment each time someone deposits, and it will be a hash of their nullifier, a secret, and their amount.The nullifier and the secret are random numbers that only the depositor knows, and they're going to be used in order to withdraw.Withdraw.We'll get into what nullifier means a little bit later.When you deposit, you're going to save your nullifier, your secret, and your amount locally.
This is your deposit note, kind of like your receipt.If you lose this note, your funds are gone forever.There's no recovery mechanism because only you know the secrets, they're not on chain.This step is pretty short.In this step, we're just going to update the deposit function to accept a commitment parameter and update the deposit event to show commitment instead of the depositor app address.
Then, even though the onchain record will say Alice called the deposit function when it's actually in the pool, there's no way to see which sol in that pool is deposited by Alice.All right, so in this one, we're just going to update the deposit function to accept a commitment instead of the depositor's address.Instead of storing that deposit, we're going to store a commitment and emit that as the event.So we can just go into our other branch, get checkouts.Step one, hiding deposits.
And all the code will be done for us.Okay, we're aborting for some reason.Let's stash it.Git checkout.Step one.
All right, so in here, let's have A look to see what we have changed.So inside the deposit function we can see that we are now taking a commitment.And the commitment is of a 32 byte hash of uint of 8 bits.This is a Poseidon hash.This is what it looks like.You're going to see this a lot throughout this program, so you don't need to worry too much about it.But if you're doing anything with Poseidon, this is kind of going to be what it's looking like.Then when we go a little bit down further, we can see that we have our deposit event and it doesn't emit the depositor, but instead emits the commitment.This is going to let other people build out their own version of these commitments so that they can actually prove them themselves.
So if we have a look into our backend, we can just see a little bit about what this Poseidon commitment looks like.Let's go to Backend Source Server and I'm just going to search for const commitments in here.So we have our commitment.It's poseidon2hash and we're hashing together nullifier, secret and a mud.We're going to talk a little bit about those in a further step.
And then we're turning it into a hex.So we have a hex of our, of our hash.The reason we're using Poseidon here is we could use a regular hash function like sha256, maybe you're familiar with that one.But they have a lot of constraints and whenever you're using them within zk that will take a lot of compute.And we want regular people to be able to generate proofs on their laptops, on their phones, so they can do private transfers from everywhere.So using something like SHA256 is just too expensive for Z ZK.So Poseidon was built specifically to use within zero knowledge.And it's kind of like the ZK friendly hash.It achieves the same kind of security but far fewer constraints.Poseidon 2 is what we're going to be using a lot here instead of Poseidon.Poseidon 2 is just like another version of Poseidon, slightly faster and slightly better, but you can use Poseidon as well.
Poseidon is also becoming kind of a standard for Solana for anything to do with ck and not just within privacy, but also within scalability.So for example, Light Protocol, they use Poseidon to hash together lots of different accounts and it means that they don't have to store as much information on chain because storing on chain can get expensive.It can it can get a little slow to like kind of fetch that information.So instead of storing all of the different accounts on Solana, they can just store a hash of that of Poseidon hash, and then they can generate a proof of that all those accounts are true.
That's kind of not going to be private because all the accounts need to be public anyway.But it's using the same sort of primitives that we have like Poseidon in to do things for scalability rather than privacy.There's quite a big Poseidon thing happening on Solana, which is exciting.But you might have noticed here, if we have a look at the code again, that our nullifier hash hex is not the same as what our program expects, which expects this 32 byte array.So instead we are turning this.If we actually go down and find the API call that we're going to be doing, which is API deposit, let me just search for it.We will be able to see that we convert our hash into bytes.So yeah, commitment bytes.We do an array which is taking a buffer from our hash that we did here, our hex hash.
We store a byte array in Solana instead of bigint, just because Rust doesn't really have a bigint type natively in it.So it's not great.And it also makes it faster to be able to do computations on.So we have to store everything with with bytearrase.So now we've hidden the deposit.But how do we go on to withdraw the deposit without revealing which one is ours?
We need Merkle trees.So now we've figured out how to deposit privately, let's move on to withdrawals.The first thing we need to do is allow the person who is withdrawing to prove that a certain commitment is exists in the pool.The naive approach would be to store all commitments on chain and check against them.But then the user would have to send their commitment with their withdrawal transaction, instantly revealing which one it is.
They could alternatively send all commitments in a transaction, but this would make a huge transaction which would be too large for Solana to handle.Instead, we can use something called Merkle trees.A Merkle tree is a binary tree where every node is a hash.At the bottom, the leaves are your commitments.Each pair of leaves get hashed together to create their parent node.And then those parents get paired and hashed again.And this continues level by level until you're left with just one hash at the top, the root, AKA the Merkle root.To prove a leaf or a commitment exists, you just need the sibling hashes along the path to the root for 1024 leaves.That's only 10 hashes, which is around 320 bytes instead of 1024 commitments.On Solana, we only store the merkle root, which is a single 32 byte hash, which you've seen on the Poseidon thing.
When somebody wants to prove their deposit exists, they provide a merkle proof.We verify it against the stored root.This saves a lot of storage costs.So in this step we're going to be adding a leaf index to tracks each deposit's position in the tree.Then we're going to add constants just to define our tree depth and our size of that tree.It can be whatever you really like.Like we're going to store the root history so proofs against recent routes will work.And then we're going to update the withdraw function to validate that that route exists.All right, so the goal of this step is to change our pool to hold a merkle tree root.We don't need to store the entire merkle tree on chain.This can be maintained off chain by indexers or backends.It's the reason that we emit deposit events so that people can actually build their own merkle tree and wherever they want to, wherever they want to keep, keep it.
So we only keep track of where we are in the tree and what the merkle root is.So if we go to the next step, we're going to do git checkout step two, proving deposit.And we'll see in our, in our program here that we've got a few more consts.
Let's go through what they are.We've got our tree depth of 10.The depth is just how many levels the merkle tree has.So each level is actually going to double the number of, of leaves that we have in the tree because it's a binary tree.So if we have a tree depth of size 10, this is quite small, you might want to make it bigger for production.
That means we have max leaves of 1024 and we do this rust thing where basically we can do tree depth to the power of two and then we'll get 1024.And then we also want to store our root history.We're going to store the last 10 Merkle routes in here in a ring buffer.Because whenever somebody departs deposits, they, the route changes, they update the route, they create a new commitment which is going to update the merkle route.So if someone is trying to withdraw at the same time as somebody is trying to deposit, they're going to be withdrawing and generating a proof against the merkle route.
So if somebody deposits and it changes the route, that means their withdrawal is just going to fail.So we want to Keep the latest 10 most recent routes so that whenever they withdraw we can check that it matches any of the routes.And if it does does then the withdrawal can go through.And we also have this empty root const in our little Poseidon guy, a little type there.This is just what a zero looks like when it's hashed using Poseidon.
So we need to keep an empty root so that we can actually have a root in the tree when we just initialize the pool.Because if we don't have that, then we can't do any sort of proving against what a merkle root looks like.Like so if we scroll down here all the way to the bottom, we're going to look at what our pool struct looks like now with these new consts and we've got our pool here.So we're still keeping the authority, we're keeping total deposits and we're also keeping our next leaf index.This is the place in the tree where the next commitment is going to be inserted into a merkle tree is append only, so you can only add new commitments, you can't replace replace a previous one.
And we're going to have all these commitments at the bottom here.We need to store where is this next deposit going to go into.Then we're also going to have our current root index, which is pointing to the most recent route in our ring buffer, just so we know that that is the most recent one.And then we store our ring buffer of our root history in here, which is going to be our little Poseidon guy.And we have 10 of them.So we're going to have 10 different routes being stored in our pool.Cool.
And if we go to the initialize function, we can see that we're setting up all of these things to just be zero when we start.And we're setting our rules, our roots to be an empty root here, all these different zeros.So let's have a look at our deposit function.Remember, the full merkle tree lives off chain, doesn't live on Solana.It's maintained by clients, by indexers, by backends.And the program only stores the root when a new deposit is made.The commitment becomes a leaf in the tree, which changes the root hash the whole way up at the top of the tree.So since the program doesn't have the full tree, it can't compute the new route whenever somebody creates a new commitment.So instead the client has to insert the commitment and the new route when they're doing a deposit.So they're going to generate their local tree with their commitment, see what the new route looks like, and then put it into our deposit function.
So each deposit then needs to accept the new Merkle root, which is computed off chain, accept the commitment as well.
So we're accepting these in our deposit.And then whenever we do the sole transfer from our depositor into the pool vault, we need to update the tree state so the next deposit can go into the right place and then emit the event so that everyone who's updating their merkle trees can update it correctly.And then we need to put the new root into our ring buffer and set that it is the current root.So the way that we're gonna do that is we're gonna get the next leaf index from the pool, which is just going to be the leaf index that this current deposit will go into.Then we're gonna calculate the new root index by looking at our ring buffer and seeing what is next in it.
Then we're going to add our new root index to our ring buffer and compute our current root index as the new root index.So we plus one down here as the next leaf index.Then we deposit everything so people can build their Merkle tree the way that it is on chain.And once our merkle tree is full, we can't actually accept any more deposits.So we just have another require statement in here that says that our nextleaf index is less than maxleaf, because every single commitment in the tree is going to have a leaf index and the final one is going to be 1025.
So if that's 1025, we can't accept it because our tree is full.
So now we have updated our deposit function to accept the new route from the client store.That route into our ring buffer, emit the leaf index so that the next deposit knows what to do and check that the tree isn't full before we accept new deposits.So now if we go to our withdraw function, our withdraw function just needs to prove that the commitment exists inside the the tree.And the way to do that is proving that the root that they have here is something that we have seen before.So if we scroll down here, we're taking a root and then we're just going to verify that that root is something that we've seen before.
Oh, it's right there.Our withdrawal function just has to prove that the commitment exists.In the tree.In order to do that, the person will calculate the new route, send the new route into the withdrawal function, and then we will just check that that root is something that we've actually seen before.So we take the new root here, and then we call a function called isknownroot and pass the root into it.
We have a look at this isknownroot function.It is super simple.All it's doing is making sure that the root is the same as something that exists in our root ring buffer in here.And if it is, then we return.Yes.
And we can continue with the withdraw function.So now in this Merkle tree step, our pool stores the ring buffer of the most recent Merkle routes.Each deposit gets a leaf index and it updates the route.And withdrawals must prove that that route is something that is in the recent history.So now we can prove that a commitment exists.But you might have noticed that if we omit every single commitment publicly, then everyone can just create their own Merkle route and therefore be able to withdraw.In the next step, we're going to talk about nullifiers, which accomplish two two things.We prove that we are actually the owner of the commitment that we're trying to withdraw, and we prove that it hasn't been withdrawn already.
12. Nullifiers Prevent Double Spending
Adds nullifier tracking to prove ownership and stop the same deposit from being withdrawn twice. It explains why nullifiers are stored separately and how they are marked used safely.
So now we can deposit into the pool without storing us as a depositor, and then prove that the commitment exists in order to withdraw.But what stops someone from proving the same deposit multiple times or proving that we know some other commitment that isn't yours?
Currently nothing.And that's where nullifiers come in.That's what we're going to be talking about in this step.The nullifier solves both problems.It proves that you own a commitment because only you know the nullifier you use when creating it.And it prevents double spending because we can track which nullifiers have been used.The problem is that we can't just mark commitment X was spent because when we do that, then it links commitment X to that withdrawal.We need to track something was spent without revealing which commitment.Commitment.Do you remember when we deposited, we generated a random nullifier and included it in our commitment.
When you withdraw, you have to send the hash of that nullifier nullifier hash.And our Solana program needs to store all of the use nullifier hashes.So if you try to withdraw twice, you'd submit the same hash twice and then our program can reject.Isn't possible to link a nullifier with the commitment.The commitment was just a hash of the nullifier secret and a nice Amount.The nullifier hash is a hash of the nullifier.They have completely different outputs and we can't reverse one or derive one from the other.Observers will see a deposit with some sort of commitment hash, then a withdrawal with some sort of nullifier hash, but no connection between them.This step is pretty simple.We're going to update our program to create a nullifier set account to store all the used nullifier hashes.
We'll check the nullifier hasn't been used during the withdrawal, and we're going to mark nullifiers as used after successful withdrawal.Then we will update the withdrawal event to include the nullifier hash as well.So in this next step, we're going to add nullifier tracking to prove ownership of a specific commitment and prevent the same deposit from being withdrawn twice.Nullifier is a unique value that's derived from the deposit secret.When you withdraw, you reveal the nullifier, but not the commitment that it was associated with.The program records it.So if you try to withdraw it again, again with the same nullifier, it's going to get rejected.Each deposit produces exactly one nullifier and the nullifier can't be linked back to a commitment.So we know this deposit was spent without knowing which deposit was spent.So let's go to our terminal here and we're going to do git checkout.
Step 3 Prevent double spend and we can see that our program has changed a bit.So if we scroll the whole way to the bottom, we're going to see that we now have a nullifier set account here.We're storing the pool the nullifier set is associated with, and we're setting our nullifiers in here.We're just gonna store a vec of our little Poseidon guy and we're gonna have our vec as a max length of 256.You can store it however you want.
In quite a lot of production systems, you would actually store your nullifiers in a Merkle tree as well.But we don't need to do that for this one.So you might wonder, why are we creating a new account just to hold all these nullifiers?When we get could just put this part into our pool account that already exists.There's a few different reasons for that.The first being that Solana accounts can only go to a certain size and you pay rent depending on the size of your account.So we kind of want to have as many small accounts as possible rather than one huge account.And the second one is that we get a separation of concerns here.So say you would want to Store your nullifiers as a Merkle tree.You don't want to then store all of the, like, you know, next leaf index, pull, nextleaf index nullifiers.
It would get quite complicated to use and you'd be doing different things and, you know, these are different information.They don't need to live in the same place.It also allows us to upgrade.So, for example, if you wanted your nullifier set to be a Merkle tree, like if you set it up this way and then you realize, oh, I want to make it a Merkle tree so I can store more nullifiers, you can just do that and you won't have to to touch the pool account at all.So you can keep all that.That's not going to change, that's not going to break accidentally and you can just update your nullifier set.So it's a pretty cool upgrade flexibility thing with Solana.
And anyway, if we look in here, we can see that we also have a few functions for nullifier set.If you're unfamiliar with Rust, this is just how we define that.We've got our struct we set up here and then we do our implementations here.So we've got is nullifier used.All we're doing is taking our nullifier set, we're taking the hash, and we're seeing if the hash exists in our nullifier set.
And then we also want to mark our nullifier as used at some point.So again, we take in our nullifier set, we take in our hash, and then we push the nullifiers into the nullifier hash.We also make sure here that we're not going to go over our nullifier set by ensuring that the length of our nullifier set is less than the length that we have set.It's going to be as 250, 56.We're going to be using our nullifier set in our initialize function so that we associate a nullifier set with a with a pool whenever we initialize a new pool.
So if we head up here, we'll be able to see that our initialize accounts will include our nullifier set here.And then let's go up to our initialize function and see what we're doing with it.So our initialize function now is getting our nullifier set from the constant context and it's setting the nullifiersets pool as the pool also from our context.So now we have a nullifierset struct to store and use nullifiers when we initialize it.When the pool is created, the nullifierset is linked to the pool via a PDA program derived address.
So now we need to actually use it.The way that we're gonna do that is in our withdraw function.So withdrawflow now needs to accept the nullifier hash from the user, check that it hasn't been used before, and mark it used before it transfers funds to have a look at our withdrawal function.We're taking in our nullifier hash, then we're just setting it as a variable here, getting it from the accounts, and we are ensuring that it hasn't been used, exclamation point has not been used.And then we're going to mark it as used.Right here you can see that we mark our nullifier as used actually before we do the transfer of the withdrawal.And there's a good reason for that.It's called reentrancy attacks.It's a common thing in blockchain.Basically, it's, for example, if we had this, we had it after our transfer down here, then what could happen is somebody would call the withdraw function and they'd have a valid nullifier, so it would all go through and then that transfer would happen and the transfer starts sending funds into the attacker's accounts.
But before this step actually continues and they call withdraw again and they will still have a valid nullifier because we haven't marked the nullifier as used, and they might be able to do this all over again.So that's quite a common attack that happens in blockchain systems.So we just make sure that we put our nullifier used up before we do the transfer.And then also, just like always when we're emitting our event, we're emitting that the nullifier hash has been used so everyone can have an updated Merkle tree and nullifier hash tree on their system.So in this step, we've created a new nullifier set account to store all of the use nullifier hashes.
We've said that withdrawals must provide a nullifier hash.We've made sure that if the same nullifier hash is submitted twice, the second withdrawal is rejected and our nullifier hash cannot be linked back to the original commitment.So right now we've got a pretty good system in place.We've got something that can hide deposits, prove ownership, and prevents double spend.But we're not actually verifying that any of this is really true.So anyone right now could just submit a random nullifier hash that hasn't been used and then get a commitment, because we're not associating those things with each other.
13. Zero Knowledge Circuits with Noir
Builds the withdrawal circuit in Noir to prove knowledge of the secret, nullifier, and Merkle path without revealing them. It also compiles the circuit and generates proving and verification keys.
That's where zero knowledge comes in, which is what we're going to do in the next step.So now let's get on to the fun part.Zero knowledge proofs.We can deposit privately, prove membership and prevent double spending.But right now, if we just send a Merkle proof, we'll be revealing which commitment is hours.This is because when you're submitting a Merkle proof, you're basically saying, here's my commitment at leaf position 5.And here are the sibling hashes that prove it's in the tree.The proof contains your actual commitment, the path of sibling hashes, and the index position.Anyone watching the blockchain can see exactly which commitment you're claiming, look back at when that commitment was added during deposit, and then link your deposit wallet to your withdrawal wallet.
So we're going to prove I know a nullify the secret and the amount the commitment is in the Merkle tree.The nullifier hash is correct.The verifier in Solana is convinced that we now have a valid deposit, but they learn nothing about which deposit.We can write these circuits in a language called Noir.It allows someone to generate proofs client side or on a back end server, and then the user can send that proof to be verified on Solana.
The reason we want to use Noir is that syntax wise, it's very familiar to our favorite language, Rust, and it also allows us to use whatever proving system we want.This means we can use Graal 16.Graal 16 is especially great for us because Solana needs small proofs and fast verification, which is exactly what GRO16 is known for.These are all going to be visible on chain.The Merkle root, the nullifier hash, the recipient address and the amounts, those are going to be visible on chain and used by the verifier on Solana.
Our private ones, which we're going to be putting into the circuit.These are never revealed.Our nullifier, our secret, our Merkle proof path, which we'll get to in a second, and the path directions where it sits in the tree.In this step, we're going to look at this entire withdrawal circuit, install Nargo, the Noir compiler, compile the circuit and generate the proving and verification keys.So in this step we're going to go through all the circuits and talk about what they mean, and then run on our back end to generate a proof and then we can verify them on chain.
Let's go to our terminal again and do git checkout.Step 4 ZK and first thing we're going to want to do is Have a look at our circuits.So our circuits exist in the circuits directory.Here we go into circuits, we'll see withdrawal, we'll see Nargotoml, which basically tells our Noir compiler how to compile and what dependencies we need.And then if we go into source, we can find our main circuit here, here.
So the way that circuits kind of work when you write them in Noir is that if this computation succeeds, we generate a proof.So normally that means at the end of the circuit you're going to want to write some sort of assertion statement.Like we, for example, here we assert that the computed root is the root.And you might just assert that A equals B or A is not equal to B or something.And if that goes through, then we create a root, we create a proof.But if it doesn't go through, then we, we just submit this error.So we have a look at this.This is going to prove knowledge of our nullifier, our secret and our amount, and prove that it exists inside the tree.So these are all the parameters that we need for our circuit.The first four are public ones.
Everything is just private by default with Noir.But if you put a pub in front of it, that means it's going to be public and used by our Solana verifier as well.So these things are already a public.These things are being emitted and are on chain already ready.We have our Merkle roots, we have the nullifier hash, we have the recipient and we have the amount.
And then our private inputs, we've got our actual nullifier, we've got our little secret thing that we use to generate our commitment, we've got our Merkle proof, which is basically the path of where our commitment is up until the Merkle root and we have is even.So when you have a commitment, you have two different leaves at the bottom that are hashed together.It's saying, which of these leaves is it?Is it the one on the side or is it the one on the this side?Is it odd or is it even?
So the way that the circuit is going to work is we're going to first compute the commitment.So we're going to use our Poseidon 2 here, which is also what we're using on the Solana side.And we're going to hash together the inputs.We have here our nullifier, our secret and our amount.And we have to also send the next parameter here for Poseidon, saying that expect three different things to be hashed together here.
And then we're also going to compute our nullifier hash to make sure that the nullifier hash that we have publicly is the same one as an as the nullifier they sent in here.So what we do here is we just hash our nullifier and we say, okay, is it computed nullifier hash the same as the public one?And if not, then we error out.And then we're going to compute our root.So we have another function here that we're going to get to in a second.
What we do here is we provide our commitment, commitment that we just created here from our nullifier or secret and amount.We provide the path, which is the Merkle proof, the path all the way up from our commitment to the top of the tree.And we do iseven, which basically tells Poseidon which order we want to hash them.Do we want to hash this commitment, this commitment, or do we want to hash this commitment, this commitment?And then we just assert that the computed root is actually the Merkle root.
And if it is, then the proof is generated.Generated.And we also do this cool thing at the end where we, you know, we pass in this recipient, but we're not doing anything with it.But we do want to basically bind this to that recipient.So what this is doing here is it.It is associating this proof with a certain recipient.So if somebody changes this recipient amount, then the proof will not be valid.So it can only be proved by one specific recipient.So we have a look at this compute Merkle root here.This is a thing that happens all the time with Noir.So this is like super simple code that you can just find anywhere.And basically what this does is just computes a Merkle route from a leaf and its path all the way up to the top of the root.
So we take in the leaf, which is just the commitment.We take in the path, which is the Merkle proof that we pass in.And we take in if it's even which commitment that it is, the ones that are being hashed for its parent.
And then we just go through them until we get the depth and just keep hashing all these together until we get to the very final thing and we then give back what is the Merkle root that we've just computed here?And we can see in here, that's the thing we have here.And we just make sure that it's the same as the root.The rest of the code in here is just tests which are pretty useful.You can test directly within war.So you just write something that looks similar to rust and you give it random nullifiers and random secrets and Everything and make sure that this is actually valid.So if we go into our circuits directory, we can test it by running Nargo test.Okay, can't see that, oh, we're not in the right place.We got to go into withdrawal and we run Nargo test and we can see that all of our test pass.
You can have a look at this by looking at the code, but it's not really important right now.
We can also compile it and that's going to be important for making sure we can actually use this.So when we compile it, we can see we now have a target directory inside of our circuit and this has our withdrawal JSON.We are going to be using this in the next step to generate verification and proving keys so that we can develop a Solana program that actually verifies that this proof is legit.We are going to use this compile circuit to generate improving and verifying keys so we can actually verify this stuff on Solana.Graas 16 proofs require something called a trusted setup which is a one time process that generates cryptographic keys specific to our circuit right here.
And the reason it can do this is because it needs a really, really tiny proof size and fast verification.And it does that by baking in the circuit structure into special keys during this set setup.So this setup when we run it is going to produce our proven key which contains all the cryptographic parameters needed to generate proofs.And it's going to generate the verification key which contains just enough information to verify those proofs.That's going to be what's deployed on chain.
The trusted part of the phrase trusted setup refers to the randomness that's during the setup.If somebody knew this randomness, for example, they could just fake the proof proofs.So we're going to do this easily in development we can just run something.But if you want to do this in production, you'd want to have multiple parties doing the trusted setup all together and then discarding all the information so that one person can't just do fake proofs.So inside of our circuit we're going to use something called Sunspot.
Sunspot is a tool that you will want to install from here.You install it and just add it to your path.This will be linked in the description so you can do that to yourself itself.And then I'm just going to run sunspot help and once this succeeds for you, you'll know that you've actually installed it.So what we're going to do is we're going to do sunspot compile target withdrawal JSON, that's this.
And this is going to convert this into a CCS format, which is what Sunspot is going to use to generate the proven keys.And now we can do the setup, the trusted setup by doing Sunspots spot setup and passing in that CCS file.
And now we have our proven key and we have our verification key.So we have our setup completely done.You can look at these files, but in most code editors you're not going to be able to understand what this says unless you have something that can view CCS files.These are our proven keys and our verification keys.So this one's going to be baked into our on chain verifier, which is pretty exciting.
So in this step we have got a circuit that proves that you own a deposit without revealing which one.And we have developed our proven key so that our backend can use that to generate proofs.And then we've created our verification key which is going to be deployed on chain as a verifier.
14. Deploying the ZK Verifier Program
Generates and deploys the Solana verifier program from the verification key. The section then updates the private transfers program to call the verifier via CPI before releasing funds.
So now we have the circuit, we have the keys.Let's deploy the verifier program to Solana.
So now we've got our ZK proof, now we got to verify it on Solana.This is the final piece in the puzzle of zero knowledge proofs.We're going to deploy a program on Solana that is like, just like a verifier that's generated from Spunspot.And our private transfers program is going to call that verifier via cpi.If the proof is invalid, the whole transaction will fail atomically.
So in this next step, we're going to generate the Solana verifier program from the verifier key that we just generated here.We're going to deploy that to dev and then we're going to add that verifier program ID to our code and call it via CPI to verify our proofs.So now we're going to deploy our Sunspot verifier program and verify ZK proofs on Solana.The way we're going to do that is generate our verifier program from the verifier key that we already generated in the previous step.Then we're going to deploy that verifier to Solana Devnet.And then we're going to update the program that we have here to verify ZK proofs by calling that verifier via cpi.So the first thing we need to do is tell Sunspot where our verifier template is this is just a bit of a setup process.So you need to set an environment variable called narcverifierkey.
I've already set it.But basically whenever you install Sunspot, inside that directory, you're going to see something called verifier bin.And you just need to set that as an environment variable.So we can have a look here and we can see which one I have.Echo, Narc, verifier bin.This is where mine sits.Yours is probably gonna be somewhere similar.So just make sure you set that variable before you continue.And then we're gonna generate our Verifier program.So super, super simple, inside of our circuit where we have all of these pieces, we need the verification key.And what we're gonna do is Sunspot deploy and pass in the verification key.And this is going to generate a Solana program.
All right, that was super simple.It's not going to generate it in the nice format that we like.It's going to generate it in an already compiled format that looks like this.A little bit scary again.But we can just deploy that directly to devnet and not worry about it.
So let's deploy to devnet.Let's make sure first off that we are setting our config to DevNet.So we're going to do Solana config set and pass in URL DevNet.You can also pass in an RPC there that already goes to DevNet.Okay, we can see that we are doing it for devnet.And let's check that we have enough Solana balance.Okay, I've got eight SOL.This is probably going to cost about 1.5 SOL to deploy.It's pretty large program.It contains all of the GRO16 cryptographic math.
So it's quite large.And then what we're going to do is Solana program deploy.Deploy and target.Yes.This one right here, it's the Solana program.
And now we are deploying that to DevNet.You might notice that we're doing Solana program deploy here instead of something you might be familiar with, which is anchor program deploy.It's because whatever program was developed and generated by Sunspot is not an anchor program.It's just like a base Solana native program.So we have to use Solana program deploy.
Deploy.All right, it's deployed.We've got our program ID here.This is going to be important, so make sure you copy this.So now we've got this copied, we're going to head back into the program that we're pretty familiar with.Our Private transfers program up here.And we're going to update at the very top we've got a const here, which is our sunspot verifier id.We're going to pop this one in there.We are also going to update it in our anchor tunnel we've got our sunspot verifier and make sure it's this one.You can ignore mock verifier for now because that's something that we use for testing, but we're not using that today.
So now we've actually got to CPI into the sunspot verifier and call it with our proof and our public inputs and verify that proof.So the first thing we have to do is encode our public inputs on our proof in a way that our verifier expects.It expects a very specific binary format for its input input.So if we head down into a function called encodepublicinputs, we will see something that looks like this.And we're taking in our root, our nullifier, hasher, recipient and our amount.
So you notice that is the same order that we have in our function here, our root, nullifier, hash, recipient and amount.So we pass those in and then we are setting the public inputs as Flag 4.It's important that we know how many public inputs we have because we have to tell that to our verifier.And then we're going to allocate how much space we need for our inputs, which is going to be a vec with a capacity of 12.That is our header.This graal 16 expects, which we'll get to in a second, and our 128, which is how much space we need for our actual public inputs.So our header is something that our verifier expects and it expects the first three bytes to be the number of the public inputs.Then the bytes 4 to 7 to be any commitments that we're doing, which we're not because we're already doing that somewhere else.
And then bytes 8 to 11 to be the number of public inputs again.So strange thing that is expected from verifier programs.
But if we have a look in here, we are adding our number of public inputs that we defined here to Big Endian bytes.This word might might be familiar to you or maybe not.If you study software engineering, then you might know what this is.But basically, Big Endian and Little Endian are ways to format bytes in memory.And most systems now use Little Endian because it's just faster.
Like Solana uses Little Endian and probably your computer does as well.But a lot of crypto, especially zero knowledge crypto, uses big Endian.So we gotta convert things into big Endian bytes.So that's what to be is Big Endian bytes means.So we convert our public inputs into big Endian bytes.
Then we're converting this is just a zero into our big Endian bytes because we don't have any commitments to do here.And then we're doing our public inputs to big Endian bytes once again.So that is our header which fills up this 12.And then we can go into the next part which is actually our public inputs.And all we have to do is add these into our input bits the way that they are here, which is like our Poseidon hash.
And we do this, this needs as ref.These are already.They're Poseidon hash format, but this one isn't.So we have to do that there.
And then because our amount bytes is a little bit smaller, we just have to pad it with a bunch of zeros, which is what we're doing here.We're padding it with zeros and then we're adding, adding it and then we're just returning inputs.So this is our public inputs now all formatted ready to go for our verifier.And we just have to CPI into it with our proof and our verifier.So we have got to make sure that we add our verifier to our withdrawal accounts because we're only going to be verifying whenever we're trying to withdraw.So we need to make sure that our withdraw function is aware of our verifier program.So if we head down here, here into our withdrawal accounts, we can see that we have our verifier program here.It's an unchecked account because we don't know what that program actually looks like.
So we can't check it.But we are constraining it here by making sure the verifier program is equal to the Sunspot verifier ID that we identify at the very start of this program.
So whenever we do anything with an unchecked account in anchor, we have to put this here, here to say that like, okay, yes, unchecked, but it's safe.So we're already validating it by the constraint here.So now our program is aware of our verifire program and let's actually call it.So we go back up to our withdraw function, which is getting to be quite a long program now it's right here and we can scroll down and see that before was.Wait, no.
So we can scroll down and see that before we do our transfer, we're going to call and make sure that our proof is valid.So what we're doing here is we're getting our public inputs and encoding them and passing in the root of our hash recipient and amount that we are getting from the parameters in here.And then we're going to do this instruction data, which is the data that we're actually going to be be passing in to do the cpi.And we are doing our proof and we're doing a public inputs.So then we're going to invoke this cpi.
We're doing this in a slightly different way than what we did previously when we're calling the system program, because the program that we're trying to CPI into is not an anchor program.So we have to use this instead.So we just use something called invoke.Whoops.And it's pretty simple.And then, then we pass what the instruction is going to be.So we're calling the program ID of the verifier program from the accounts.We're passing in nothing for the accounts, it doesn't need any accounts.And we're passing in this instruction data, which is our proof and our public inputs.And that's going to verify the proof.And if that goes through and everything is verified, then we go to the next part of the code and mark our nullifier as set.Just a bit more information about these cpi.And we use invoke when we're calling another program, the caller's authority is going to be passed through into this one.
We use invoke signed to sign with PDAs for example.So we'll use this when our PDA needs to authorize something.But we're just using invoke here and we're using invoke instead of CPI context because this, the program that we are calling is not an anchor program.Okay, so now we are ready to deploy our program.We've got everything working.We've got our sunset verifier here.We are verifying your proofs, we're making sure we're updating our Merkle tree and we can actually deploy.
15. Wiring the Private Transfer Frontend
Explains how the frontend uses Kodama-generated clients, Solana Kit, and React hooks to build withdrawal transactions. It ends with a full demo of depositing and withdrawing privately through the app.
So if we head into our anchor directory, this is an anchor program, so we can just build it first to make sure everything's good.Oh yeah, we have to sync our keys first.So basically what happened there is that our anchor, the key here was different from the key at the top that's declared here.So we just run anchor Key sync and then we can sync them all up.Then let's try building it so we can see in our program that our withdrawals now require ZK proof.Our program calls a verifier before CPI releases funds.If the verification fails, the entire transaction requires reverts and no funds can actually move without a valid proof.So let's just wait for this program to be built.Okay, we got some warnings there, but we don't have any errors yet.So.
Okay, awesome.It built.That's good.So now we can deploy it to our DevNet.So we do anchor program deploy and pass in the cluster as devnet.
All right, our program's been deployed onto deadnet.I'm going to make sure that we keep this program id because we're going to be using that in the next step, which is where we wire everything together in our front end.So now we've built all of the individual pieces, we're going to see the complete flow and have a look at how we're calling some of these things and wiring all of them together.In this step, we're going to understand everything about the front end, how it builds and sets sends withdrawal transactions using Solanakit.So we're going to be generating our TypeScript client with Kodama.We're going to walk through with the withdraw transaction code and then we're going to run and test the complete flow with everything all together.So first off, we're going to be generating our Kodama client with our idl.An IDL stands for interface definition language.
Think of it kind of like a JSON file that describes your Solana programs API, what instructions it has, what a kind of they expect, what data types they use.Whenever we run Anchor build just now, Anchor automatically generates an IDL file by reading our Rust code.
And we can have a look at that actually in our target idl Private transfers JSON.This can be used by any front end or script that wants to call.Your program can read this IDL to see exactly how to format their requests.It makes it just a lot easier to be doing stuff with Solana.So for example, our IDL will describe our withdraw instruction and it says that it needs a proof in type bytes, it needs a nullifier hash and all the different hypes and everything in there.
Without this, you'd have to like manually figure out how to serialize data correctly and what byte order they use and everything.And that's just a bit tedious and error prone to do.So we're gonna be using this IDL with something called Kudama, which is gonna generate typescripts definitions for us and make it super, super easy for us to use this within our front.So if we go into our front end here and we can find scripts generate client, you can find this really easily online on the Kodama docs.I'm just copying and pasting from there.
Basically what we're doing here is we are getting our IDL path that we've specified in here.Then we're creating from root with our IDL path and we're just saying where we want to put it.So we want to put it in source generation generated.So let's go ahead and run this script by CDing in a front end again using Bun bun run scripts generate client.And if we have a look at our source directory, we've got all the generated stuff in here.So if we look at it, we can just see it makes it super, super easy to use all this stuff within typescript we have a bunch of functions we can use, a bunch of types that we can look at and it's gonna be much easier for us to do a our front end.So before we dive super into the front end code, we're going to need to set some cons here.
So let's again get our sunspot verifier ID from up here.This is not the program that we just deployed, but it's our sunspot program.Okay.I think it should be.Okay, I don't know where it is, but I do know that it is in my anchor TOML here.So I'm going to paste that.Copy that and paste it into our.
If we go to front end source consonants, consonants, constants.Front end source constant constants.Sorry, wait, so if we go to our front end source and constants, we've got our sunspot verifier ID there.We're just going to change it to this one.And then we are also going to need our program ID to put onto our backend.
So we get our program ID from our private transfers, we head into backend.This is going to be generating all of our proofs for for us source and server.And then we'll see something at the top which is our program ID here.We don't need to do this for our front end because Kodama automatically gets our program ID for us, so we don't have to set it anywhere.All right, let's go back to our front end and we'll head inside our components and have a look at our withdraw section so we can see how we are formatting all of our requests.
So we are using something called Solana React hooks.This is going to make it super easy for us to use react.We're using Use Wallet connection and use send transaction.This is a REACT hook that gives us the connected wallet.And this is a react hook that helps us signing and sending transactions.
Then we've also got Solanakit, which is what React Hooked use.Under the hood.We've got an address which is going to convert a base 58 string into an address address.We've got get program derived address or get PDA which drives a PDA from the seeds and the program ID.We get GetBytesEncoder, which is gonna help us create an encoder that converts our strings into bytes which we're gonna use for our withdraw.And then we've got address encoder which converts an address to 32 bytes which we're also gonna use.And then we're getting some of these things from Kodama as well.Well, so we can actually get an encoder to make sure that we get all of the data correct in our withdrawal function.And we're getting our program address for our private transfers.So let's head down here and find our withdrawal proof.
So we're getting our withdrawal proof from our backend and then we're just converting this into the types that we need in here before we call our withdraw function.Then we're going to be deriving some PDAs here.This is super simple to do.We can just get our pull PDA from getpda.We pass in our program address that Kodama gives us.We pass in the seeds that we have set in our consts.We also need our nullifier set pass in the same seeds and we need our pool vault, which we do the same thing for.And then we need to encode our instruction data into the way that our program expects.This is super simple with what Kodama has given us with get withdrawal instruction encoding.So we just pass in our proof our nullifier hash, our root recipient amount in the same order that's expected from our program and we just encode it and then we're all good to go.
So now we're actually ready to build the instruction.So what we have to do here is we pass in the program address and then we give it all the accounts that it needs.So we've already derived these accounts before.We got our pool pda, our nullifier set pda, our pool vault PDA our recipient address.And then we also need our Sunspot verifier ID and our system program id.
These roles specify what is actually going to be done with the account.So if it's 1, that means that it is writable and if it's 0, that means it's read only.We can also set 2 as read only and assigner and we can do 3 as being writable and assigner as well.But we just need these ones for now.And also something that we specifically should do for verifying ZK proofs is request more more budget for compute.Because sometimes we need a little bit more to verify proofs.So what we can do here is just create a new instruction.I'm going to call it compute budget instruction.We'll pass the compute budget program ID that we've set in a const.It just looks like this.
We'll pass the accounts, which is nothing, and then we pass the data which we have set up here.Basically the only important thing that we need for here is how much compute units we're actually wanting.And we're going to request this, but many.That should be enough.
And then we can send the instruction.First thing we want to do is just make sure we get that compute and then we'll send the withdrawal instruction.
I think we are ready to run the front end.Let's head back to our terminal.We're going to first run into our backend because our backend is actually going to be the one that verifies our proofs.We'll do bunrun dev.Oh, we need to install first.
Whoops.Bun run dev and then we will open our front end and bun run dev into here as well.I think we've already installed.All right, so we head back to our browser.This is it.Open here.I've already connected with my wallet previously and we can deposit.Deposit.Let's zoom in a bit.
Let's deposit 0.2 sol.So we're generating the note here.I have to sign this in my wallet.This isn't a real wallet, so it's fine.It's going to say that it won't succeed.That's because we don't have any SOL on Mainnet, but we do on Testnet, so it will succeed.Succeed.
Okay, so now we've got our deposit note and this is what we need in order to withdraw.So if we paste this deposit note into withdraw, we can see that this is the amount that we want to deposit and we can hit withdraw.This is generating our proof right now.It doesn't take much time to do it with grass 16 it's pretty fast.And then we've got our 0.2 SOL.
So we have just deposited into a pro private and then withdrawn also privately.And there you have it.You've built a privacy preserving transfer system on Solana.Using zero knowledge proofs, we built a private transfer pool where deposits are hidden behind cryptographic commitments, withdrawals reveal nothing about which deposit is being spent, and double spending is prevented without revealing identity.We practiced anchor macros, CPIs, PDAs and we learned a little bit more about Kit.
We also generated a ZK verifier, learned about ZK verifying and a bit more about Solana compute budgets and transaction sizes and we learned best practices when it comes to handling ZK and privacy on Solana.All about Merkle trees, commitments and nullifiers, Poseidon and how to keep things private on a public ledger.
16. Stablecoin Mechanics and Token 2022
Introduces a fiat-backed stablecoin and explains minting, burning, and compliance requirements. It covers Token 2022 extensions, minter allowances, pause controls, and the program's test suite.
Congratulations and I hope you enjoy the rest of the bootcamp.
In this project we're going to build something that powers billions of dollars in transaction volume on Solana.A fiat backed stablecoin.If you've ever used USDC or usdt, you've interacted with this exact type of system.So what exactly is a fiat backed stablecoin?It's a token where every single coin in circulation is backed one to one by real dollars sitting in a bank account or treasury bills.
When someone deposits $100, we mint 100 tokens.When they want their dollars back, we burn the tokens and release the funds.Simple concept, but building it correctly requires understanding some critical Solana concepts.Here's what you're going to learn.First, we'll dive into token 2022.
It's Solana's modern token standard, designed to build tokens with real on chain rules, advanced control and and features like freezing, burning and role based access.Token 2022 has several different extensions that you can add onto a token for various use cases.Specifically for this project, we're using token 2022 to be able to meet regulatory compliance needed to issue a stablecoin.For example, if a sanctioned wallet is holding your stablecoin, regulators expect you to be able to freeze or seize those assets, and token 2022 enables this feature.Second, we'll learn about program derived addresses or PDAs.
Our program uses PDAs as the mint authority, which means no private key controls the token supply.The program itself is the only thing that can mint, and it only does so when the rules are followed.Third, we're implementing a minter Allowance system.Not everyone can mint tokens, only authorized minters.And each one has a cap on how much they can create.
This teaches you how to build role based access control on chain.Fourth, you'll build emergency controls, a pause mechanism that stops all mincing if something goes wrong.Real stablecoins need circuit breakers and you'll understand exactly how to implement one.The patterns you learn here are similar to those used by actual stablecoin issuers.So let's get to building.
Before we build our stable stablecoin, let's make sure we understand how tokens work on Solana.So there's two account types that you need to know.First, there's the mint account.This defines the token itself.Think of it like the definition of a currency.
It stores the total supply, the number of decimals, and who has the authority over it.There's only one mint account per token.Now.Second, there's the token accounts.This is where tokens are actually held.
It's like the wallet for that specific currency.It tracks a balance and points back to its mint, but also its owner.There can be millions of token accounts for a single mint one for every user holding that token.The simple way to remember this, the mint is like the currency definition and the token account is a wallet that's holding that currency.Solana has a built in program for handling tokens called SBL tokens.
If you're familiar with Ethereum, this is Solana's equivalent to ercp.It handles the basics like creating mints, minting tokens, burning tokens and transferring between accounts.It's been around since Solana's early days and works great for simple tokens.But this is where token extensions comes in.It's also called token 2022 and it's a superset of the token standard that's backwards compatible with the SPL token.
But it adds powerful new features called exclusive extensions.There's over 15 different extensions available today and a token can mix and match with different extensions as you see fit.For example, if I'm creating a subscription service and I can have a website that is token gated, but you must own the subscription token to be able to access it.So for this to work I would create this token with an extension like non transferable and permanent delegate enable enabled.So when someone signs up for the subscription, I'll mint a token to their wallet and they won't be able to transfer it and this validates that the holder has actually successfully signed up for the subscription.
Then if the user stops paying for the subscription the permanent delegate can then go and burn their token, revoking their access to the subscription service.Now, for this project, it's a stablecoin.So a stablecoin would use token extensions to ensure regulatory compliance.So here's some extensions that we could use for our stablecoin.There is permanent delegate.
This lets an admin transfer burn tokens to any accounts.And why would you want that?Well, for compliance, sanctioned enforcements, legal recovery, real regulated stablecoins, they'll need something like this.There's also default account state.New token accounts can start frozen or unfrozen by default.This gives you control over who can actually hold your token tokens.Then there's metadata.You can store the token's name, symbol and image URI directly on chain, so there's no external programs that are needed.And then another one would be transfer hooks.So transfer hooks allows you to run custom logic on every single transfer.
This enables things like KYC checks, blacklists, or transfer fees.So when we initialize a mint account for a stablecoin, we'll initialize all the extensions that we want to use as at that time, and we'll see exactly what that looks like when we clone our code right now.
So here is our stablecoin code.And as always, we're going to start with the program state.So we're going to scroll down.Everything here is in one librs file.So we're going to be bouncing back and forth around the file a bit, but we're going to scroll down to where all of our account structures are and the way that this stablecoin is created.There are three core accounts.So the first one is our config accounts.And the config account is essentially like the control center of the stablecoin.And how that works is there's only one config per program.And the reason behind that is because when you're defining the config account when it's initialized, so we're going to go back to initialize and going to the initialized struct where all of the accounts are passed through.
When you're defining the config account, you can see here for the seeds field where the PDA is defined.The PDA is just config.So because it's only one word that is a constant, there can only be 1 config PDA.If there were other variables added, like the payer's public key, then there could be multiple config PDAs.However, here there's only one constant for the PDA.So one config account per program.And this config account is what's going to be, like I said, the control center of the stablecoin.So going back to our account structs, we have our config and the reason that there's only one is because it's essentially the mint authority and the permanent delegate of the coin and it signs all tokens operations via CPIs.So when our CPIs are called within the program, it's going to be with signer seeds and those signer seeds are going to be the config accounts.
So this has the main control of the stablecoin.
However, we're going to want to have people being able to have access to mint new tokens and burn those tokens.And we're going to do that by having a minter config and being able to able to add and remove minter authorities to the program.So that is what this other account is, this minter config accounts.And what that is is just storing all of the information for a mentor that is going to have access to mint tokens.So you can see here, it's storing the Minter's public key as well as how many tokens they're allowed to mint and then keeping track of how many they've minted so far.So if you're allowed to mint Mint 1000 tokens, each token that you mint is going to add as a counter here in this allowed mint field, I mean amount minted field and then just saving if it's initialized and then the bump which we've already went over in the escrow program, why you want to save the bump field in your program state.
Okay, so these are the two accounts that actually have a custom data structure that's defined here that are holding data on trend chain.The other account, which is the last core account for this program is going to be the mint account of the stablecoin itself.And that's not going to have a struct here because it's not going to be holding actual data besides what is in the mint account for a token.And that is created by using the mint instruction with the token program or token extensions program which we'll be using specifically for this program project.
So that does not have to have a data structure defined here.So just to recap, you only define this account structure when you are creating a new account on chain that has custom data that needs to be met and you're going to specify every field for that data For Mint accounts, all of the data that's stored is already predefined for token mints.Okay.So before jumping into the actual instructions, one more thing to just recap with our config accounts is we have the Minter config to enable multiple minters with different issuance constraints and specified amounts over time.So this helps meet a lot of regulatory compliance that's needed for issuing the stablecoin.
Now we can dive into the actual instructions in the themselves.So if we go back up to the top where we have this program macro from anchor and then we have our stablecoin that has every function defined here, we're just going to go over high level what each function is and then we'll dig in a bit deeper where we need to review new concepts that are being learned from this boot camp.A lot of the concepts are repetitive from previous projects in the bootcamp, so we're just going to not dive too deep into that, but give a high level overview.So the first instruction here is initialize.And what this does is it basically bootstraps the whole system.
So I'm initializing what the config account is and storing all of the data needed for that config account.I'm initializing what the mint is and what extensions that I want added to that Mint account.And I'm also calculating how much space is needed for the Mint account to be minted and held on chain and setting up the instruction to initialize a mint using the token 2022 program.Now the next instruction is configuring a minter.So what this instruction does is it configures a new mint authority on the stablecoin.
So if you're adding a new person into the stablecoin that has the authority to mint x amount of tokens tokens, you're going to execute the configure mentor instruction and specify the public key, the amount allowance that they have to be able to mint.And then that way the user will be able to execute the mint instruction that we'll show in just a second.And then we have the remove minter instruction which basically just does the opposite, removing that public keys access to be able to mint tokens.Now, now you have this mint token instruction and typically you can just call mint from spl token and anyone that has the correct authority when you mint it, you are going to be able to just call it.However, the reason we have a custom program here is we want to have specific mentors with a specific amount of allowance over a certain amount of time.
So to be able to customize that, it's going to be in its own program.So here you can see that there's this require we're requiring to make sure the stablecoin is active, but there's also a require to make sure that it's the correct Minter config.So the correct person with the correct allowance can call this mint instruction.And if you don't have that correct allowance, then this instruction is going to fail.So what sets this apart from the typical mint instruction for SPL tokens is that your Minter authority is able to be controlled with an allowance and a time that it's able to mint because of this configure Minter account that we have and these instructions that we have as well.
So again, this is just to map back to making stablecoins meet specific regulatory compliance that's needed from specific companies.Now the next few instructions that are here are just additional sanity checks and security checks like being able to pause the token or unpause it, being able to force burn or force transfer tokens.And these are just again to meet regulatory compliance.And one instruction that I did skip over was the burn tokens instruction.And the burn tokens instruction is just burning tokens once someone is ready to then receive fiat back for their stablecoin.
So just to go back over how this entire life cycle of a fiat backed stablecoin works is you have a user deposit fiat into a bank account and then they're able to mint the equivalent one to one of what that asset is on chain.So if I deposit a hundred US dollars into a bank account then I'm able to mint 100 USDC on chain and then when I'm ready to withdraw that from the bank accounts, I am able to burn those 100 tokens on chain and then withdraw from the accounts.Now very simple mint and burn mechanism that happens here.However, to be able to have this meet compliance and be able to make sure that the backing stays one to one and everything is correctly working, we have these additional authorities and configs set for having mentors that have specific access to be able to validate to make sure these transactions are accurate.
So now that we've gone over all of the functionality behind the stablecoin and mainly the importance of SPL tokens versus token extensions and where that use case comes in for using token extensions, we can now test the project.
So we're just going to run cargo test and let all of the tests run.And this has a pretty developed Test suite, just looking at different edge cases, security restraints for the stablecoin.So you can see here there's 23 tests, they all passed.And you can see we have testing, initialize, testing, if it fails, testing the Mint authority and testing all of the instructions.So the best way to properly understand this program is just going through all of the tests, seeing how they work and checking out all of the ways to build out a stablecoin.
So that's everything for the stablecoin project.
17. Stable Swap AMM Math and Safety
Breaks down the stable swap AMM, including the invariant math, Newton's method, and oracle-based depeg protection. It also explains dynamic fees, signer PDAs, and remaining accounts for flexible swaps.
If you're interested more in depth of how each one of these functions work and how the derive accounts worked for Anchor.When you're passing through all of the accounts that are associated with an instruction, go back to that escrow project because that goes in depth on that section of Anchor and how to write out your instructions and the accounts associated with it.So that's everything for the sample game.
In this project, we're building a stable swap AMM.Now, you might be familiar with constant product AMMs like Uniswap, where swapping follows the classic X times Y equals K formula that works great for volatile token pairs.But when you're swapping stablecoins like USDC for usdt, that curve creates unnecessary slippage.You're trading assets that should be worth the same.So why lose value?
That's where a Stable Swap comes in.Originally it was created by Curve Finance.This design uses a hybrid invariant that flattens the curve near the one to one ratio.And the result, it's a near perfect swap between between stable assets with minimal slippage.In this project, you'll build a stable swap from scratch.
We'll implement the core math, including Newton's method for computing the invariant, and you'll understand what that amplification parameter actually does.But we're not stopping at the basics.We'll also implement dynamic fees that protect liquidity providers from arbitrage, a pith oracle for real time price feeds and depeg detection that automatically pauses the pool when a stablecoin loses its peg.By the end of this project, you'll have hands on experience with advanced defi mechanics, complex on chain math and security patterns that separate basic projects from protocols that can handle real value.So let's dive in.
So we're going to open up our code for the stableswap amm and first we're just going to go through all of the code that we have because it is quite a lot.So this project is a bit more in depth.So first we're going to open everything in our source file under the Program, you can see there's an instruction folder, and inside this folder is all of the public instructions that exist for the amm.Now, there's several other files.So we have this constants file that's all of the constants throughout the project.
Specifically for math or different tokens, we have this dynamic fees section, which is how we're calculating the dynamic fees, all of the errors for the program, the librs, which is where all of your instructions live, a math file, oracle file, and then state, which is where all of your custom state for the accounts that we're creating for this project exist.So before we go through the actual instructions for the project, there's two main concepts that I want to go over a bit in depth first.So first we're going to click on this math file and it's quite long, a lot of different calculations that happen.So we're just going to understand what it is at a high level.And then if you want to dive in deeper, all of this is documented with some pretty in depth notes explaining where the math came from.
But from a high level we have two main functions.So one is this compute D function.So imagine the pool has a health score called D that represents the total value.When you add or remove liquidity, D changes.But when you swap, D must stay exactly the same.
And that's the rule that makes the math work.The problem is that the equation for D is too complicated to solve with simple algebra.So we use this technique called the Newton's method.Essentially, it's making an educated guess and refining them until we get close enough.And it's like playing hot and cold, where each guess gets you a little bit closer to the answer.
And all of that is explained in the calculations for compute D. The next one is compute Y.And that is is when someone wants to swap a token A for token B.We know exactly how much token A is that they're trading to the pool.The question is, how much token B should they get back?Since swaps must keep D constant, we work backwards.
If D stays the same and token A increases by this amount, then what must token B be?The difference between old and new token B be is what the user receives.Again, we use Newton's method to find the answer because the math is too complex for DirectFormula.So all of that is explained within the documentation for the math RS file.Now, the next concept we're going to introduce in this project is oracles.
So oracles are very common in defi projects, and I believe this is the first project within the bootcamp that an Oracle is being introduced.So we're just going to go over high level what an Oracle is and why we're going to need it here.So a smart contract on Solana can only see what's happening inside Solana, like account balances, other programs, transaction data.It has no way to be able to look at the outside world.It can't just Google what's the price of USDC right now or check a news website.
So sometimes contracts do need real world information.In our case we need to know is USDC actually worth $1 dollar like on exchanges of Coinbase or Binance?So the solution here is integrating Oracles into the smart contract.So an Oracle, it's a trusted service that brings external data onto the blockchain.Think of it like a courier who goes outside, checks the real price of USDC across major exchanges and writes that price into a special Solana account that our program can then read.
So for this project we're using the PITH network.There's multiple other Oracles on Solana, but here we're just using pyth.So PYTH is a decentralized Oracle that aggregates prices from many professional trading firms.Every few hundred milliseconds, PITH updates price accounts on Solana with the latest market prices.So this project, it needs Oracle pools because we need to understand when a stablecoin is at the correct price or if it depict.
So our pool only knows its internal price.So how much USDC versus USDT is in their vaults.But what if USDC crashes to 90 cents in the real market while our pool still thinks it's worth $1?That's going to cause arbitrage opportunity.This is creating an unrealistic market for USDC and giving people USDC at the incorrect price that it's not actually valued at.
So by checking pith Oracle prices we can detect when a stablecoin has depegged from $1.When that happens, we'll pause the pool or charge emergency fees to protect the liquidity from being exploited.So we have all of those checks in place within this project.But the key takeaway here is that Oracles are a bridge between blockchain isolation and real world data.So without them, DEFI protocols would basically be flying blind.
So that's why we'll need Oracle's integrated into this project.Now let's move on to going through all of the functions that are created in our smart contract.So looking into the instructions we have initialize pool, which creates a new two token pool in the amm, we have add liquidity or modify liquidity.And what modify liquidity does is both add liquidity and remove liquidity.So adding liquidity is just depositing tokens to receive LP tokens in return, and then removing liquidity is just burning LP tokens to then receive tokens back.
Then we have swap, and that is trading token A for token B, or vice versa.And then we have check depeg, and that is just a permissionless Oracle check.And it pauses the pool if it is depegged.And when I say depegged, I mean it's no longer at the stable value that it's assuming to be.Those are all of the public instructions.
So now we're going to start with the state and once we review all of the state stored in the program, we're then going to go back and do a deep dive into the instructions.So going back to the state file, I always like to start with the state so we understand what data that we're working with and how to take that data to then use in our instructions.Logic.So here you can see we have two accounts.Now let's talk about them.
So the first one is our Oracle account.And what this account holds is all of the configuration needed to be able to query a specific Oracle from the PITH network.And here you can see it has Oracle A and Oracle B and their corresponding public public keys.And those public keys are how you're able to identify the specific price feed from PIF for a specific token.So for example, let's say I have a USDC USD pair in my amm.
I'm going to have the public key for the USDC Oracle as Oracle A, and I'll have the public key for the USD Oracle as Oracle B, and that's then going to be able to query the real time price feed for both of those assets.So those public keys need to be defined in the config because it's going to then be held in our Oracle config account to then query throughout all of the instructions to be able to pull the exact public key that you're going to use from pif.Now the next field here is a max DPEG BPS and what that is is basically just a maximum allowed deviation from $1.So this is how far we'll be able to allow our stablecoin to vary.And if it varies past this allowance, then it's going to pause the pool and then the next field here is your emergency fee BPS and what that is is a fee to charge when the pool is in a decent.
So both of these two fields are really just to take precautions for any security concerns in case an asset does depeg from its stable asset.And then the last field enabled is just a boolean, true or false, and it's just a detection if the pool needs to be depegged or not.So if the Oracle data comes back and it surpasses the depeg allowance, it's going to update the boolean to then pause the pool.So that's everything for the Oracle account.And you see this function here that is really just calculating the length of the Oracle.And what that's doing is understanding how much space that's going to take up on chain.Now the next section is pull.So what is the pool account?The pool account is just storing all the configuration for a stable swap pool.And it's a PDA seeded by the LP mint address.
So the pool is a PDA that owns the token vaults.And because it is a pda, the program can then sign transfers from those vaults.Now let's go through all of the fields.We have the admin, and the admin is just the public key of the person that is able to modify the parameters for that pool.Typically in production this would be like a multisig or a governance program.
And then we have this LP mint and what that is is the public key for the LP token.So if I'm providing liquidity to a pool, I'll receive an LP token in return.So later, when I want to withdraw the liquidity that I I'm providing to the pool, I will show the liquidity token to understand how much I provided, that token will be burned.And then I'll be able to receive the tokens that I originally provided to the pool.So this is really just a way to track how many shares I own of the pool and how I can get them back in the future when I'm ready to withdraw my liquidity.
Now the next one is this amplification and what that is is the amplification coefficient that we talked about in the math section.So A is just controls how the swap curve behaves.So if you have a higher A, it's going to have a flatter curve so the swaps are closer to one to one.When the pool is balanced, if you have a lower A, then it's more curved so it behaves more like how the Uniswap protocol works.So typical values you would see here, like if you're using USDC or USDT, it'd be about 100 to 2,000.
If you're using like staked ETH and ETH as a pool, it would be about 50 to 100.So it's a little less correlated than a 1 to 1 USD stablecoin.So, so the next section is your fee bps.So what that is is your swap fee in basis points.So anytime you make a swap on an amm, there's a fee that is accrued and we're just going to have what that is.
So all of this really is just the config that you're setting for how you want your pool to be built.The next one is the token mints.So this is an array of public key keys and it's going to have the token mints that you want for this pool that you're creating.And again, the mint is the mint address to the Mint account, which is the global information for that token.The next one is the bump, which we know you'll be saved the PDA bump seed to your account state.
Then we're also saving the Oracle config.So once the Oracle config is initialized, we're going to take that and save it into the pool.Because when the pool is running, it's going to need to be able to query the Oracle.So we're going to need to get the public key of each token's Oracle to get the real time price feed.And then the last one is just a boolean if you're pausing the pool or not.
So if the asset does depeg, the pool is going to be paused.And this is just a boolean to mark if it's paused or not.And then the next is just adding additional functions on that account, which is calculating the space, the mints, and how to find the Mint index.So that's everything for the state.Now that we understand the state, we're going to just deep dive into a few key points in the code.
So you have all the code, it's all documented.And a lot of this has built off of all of the previous projects in the bootcamp.However, there are a few specifications that I do want to talk about.So the first one is just key validations that happen within this project.So when we initialize a pool, there's a few validations that take place.
So I'll open up the initialize pool instruction and we're just going to search for it.So we have this required for amplification.So this is just making sure that your Amplification, your A value is going to be a reasonable value.So it's making sure that it's positive.You need your A to not be too large and also not be a negative value.
So this is just validating that, making sure all of our math checks out.The next one is our feed bp.So this is just a sanity check to make sure that a fee doesn't exceed 100%.So maybe someone accidentally put the wrong decimal value.This is just making sure that that doesn't happen and returns an invalid fee if the fee is above 100%.
And then one other validation check to go over is validating if the Volts are correct.So let's find that one.
So here we have the vault validation.So the vault must be the correct associated token account for the owner of the pool and the mint of the token.And this is just critical for security.So we want to make sure that if we accept tokens as the vault, then it is the correct owner.So it has to be an associated token as address.
And the reason is because ATAs are deterministic, meaning it has a specific owner and a specific mint.And there's only one valid address for an ata.So you can see here there's even a note in the code specifying that if we do accept any token account as the vaults, the attacker could pass their own token accounts as the vaults.And when users swap tokens, they could go to the attacker instead of the pool.So very important that we're using an ATA here because it is a deterministic address.
So the next thing we're going to go to is the swap section.So this swap instruction, this is the core flow of your amm.So here we have to get the current reserves of the vaults and then you're going to calculate the dynamic fee, which is the fee that you want to charge when you're making a swap from the Volts.And then it's going to do all of the swap math that we talked about at a high level that comes from this math RS file.And then lastly it's going to do a slippage check.
And that slippage check is just making sure that the amount out is larger or equal to the minimum amount out.So that is the core logic for how our AMM is going to to work for a stable swap.Now, the next section that I want to talk about is that we have PDAs signing for the vault transfers.So I want to find that function to show you.If we go here and we do token transfer, I believe It'll come up.
Here we go.So here you can see when we're transferring out of the vault.So there's two transfers that happen in a swap.You have transfer in and this is just a regular CPI because anyone is able to transfer tokens into the vaults.So I could connect my wallet and deposit.
Now when I want to get tokens out of the vaults, it's going to have an additional security check.So this may sound familiar to you because this is exactly how we designed our escrow program earlier.But if I go here to transfer out when we're calling the CPI instead of just new, it's a new assigner.And it's new assigner because the vault is who has authority to be able to transfer tokens out of the pool.And the vault is a pda.
So it has to be signed by the PDA to be able to transfer tokens out of the vaults to the user that's requesting them.Now this goes back to the same logic we talked about in the escrow program where the vault has a PDA signer and this way we don't have an attack vulnerability for having a private key being able to sign this transaction.It is only the code itself can sign with the signer seeds of the pda.So here you can see we're making a CPI call with a signer.We specify the signer seeds and those signer seeds are exactly what the PDA is is for this vault.
So the pool owns the vault, but it can't hold a private key.So we use PDA seeds to let the program sign on behalf of the pool.And if we go back to the state file, sorry, not the state initialize pool.
And here we go.So we're in the initialized pool file.If I go to the derive accounts mockup grow, this is where all of the accounts that are passed through for the pool are specified.And you can see here for the pool it is defined with these seeds and those are the seeds for the PDA of the pool.And if you go back to our swap, so we'll go back to the token transfer and the transfer was signed.
So you can see here the signer seeds, you have an input parameter for the transfer out function of your signer seeds.And those are going to have to match up with the PDA signer seeds of the pool that we just defined.Okay, now one more feature to go over here is when we remove liquidity from the pool.So I'll go back to modify liquidity and I'm just going to search for this calculate withdrawal amounts function and we'll talk about what it does.So this is a function for when you're withdrawing from the pool.
And what this is calculating is exactly what will be withdrawn.And this function is specifying that you always get back proportional amounts of both tokens.So there's no single sided withdrawals allowed in this implementation of the amm.So if I am depositing USDC and usdt, when I go to withdraw and burn my LP tokens, I am going to receive proportional amounts of both USDC and USDT back in my wallet.Now we're going to talk about just three advanced features that have been implemented into this AMM and then we'll be able to wrap up our whole overview of this project.
So the three features we want to talk about is first the dynamic fees.So you can see there's an entire file talking about dynamic fees.So why do we implement dynamic fees?This function was implemented to be able to deter arbitrage on the project.And that's because an arbitrager knows the true price and they'll extract value from LPs.
And this happens when an asset depegs a pool is imbalanced or a large swap occurs.And by having dynamic fees on the project, this makes toxic arbitrage very expensive and then it compensates LPs for their informed traits.So this just helps make a more healthy amm.Now the next that we're going to talk about is depeg protection.So there's a function when you're checking oracles and it's going to be called checkdpeg.
So let's find that and here's the function and what this does is it is, is checking to see if the asset has depegged from the stable price that it should be.And if it does, then it's going to pause the pool to stop trading, which again is going to deter arbitrage that is toxic in the amm.Now anyone can call this so if USDC goes to 90 cents, the pool will pause and it will start charging 5% in fees.And this is just going to protect LP piece from getting the pool drained.The next topic we're going to talk about is the remaining accounts pattern.
So if I open up the swap RS file you can see I'll go to the derive accounts and this is where you need to specify all of the accounts that are passed through for an instruction that the instruction is going to interact with.And anchor requires fixed account counts at compile time.But we do want to have flexibility for variable token accounts.So here you can see we only have for swap the pool, the user and the token program we're missing vaults and user atas.So that is going to be as a remaining account.
So when you are writing out in your context you can call remaining accounts and that is going to be able to able to pull in vaults and user ATAs.
So you can see here, when you are calling the swap function, we have this remaining variable and that is pulling the remaining accounts from your context.So here the remaining accounts for a two token pool is going to be your token A vault, your token B vault, your user input token accounts and the user's output token account.So all of that is going to be under anchor's remaining accounts function.So this pattern, it lets us validate accounts at runtime while keeping the instruction flexible for multi token pools.And that is all of the features that I wanted to go over for the project.
You can see here, throughout the projects everything is documented with with an in depth explanation.And a lot of this does refer back to previous projects that we've worked on throughout the bootcamp.So we've just talked about all of the new logic that was introduced specifically for the stableswap AMM project.And that is everything here.And check out the project, build it and run your tests.
18. Building an X402 Paid App
Demonstrates how to build a paid app using the Solana X402 template and a simple payment-gated flow. The app combines Solana payments with an OpenAI-powered fortune-telling feature.
GM this is Mike, the APC dyo at Solana Foundation.I'm gonna show you how to build a paid app from scratch using Solana S402 template.In this repo, we will focus on client side development.If you are new to the S402 protocol, I highly recommend you checking out Jonas Han's video.The new app called S402.
Okay, just kidding.But seriously, it's a great intro to Asphodel 2 and AI agent pay payment.So why do we even need an AI agent payment standard?Imagine this.You see a really interesting article on your feed, you click it and boom.
It asks you to pay a 12 month subscription just to read one article.Annoying, right?Now think about bots and AI agents.They hit the same paywalls when they try to access data or service on the web.That's where the S4.2 comes in.S4.2 is an open payment process protocol developed by Coinbase.It lets website and AI agents charge per request using stablecoins.Here's how it works in simple terms.Your AI agent asks a server for something.The server replies with a price.
You pay and you get what you ask for.No signups, no subscriptions, no long forms even better, s4.2 is built directly into the normal web traffic HTTP.So it works with the Internet the way we already use it today.In theory this means you could pay just a fraction of a cent to read one article instead of buying yet another for a subscription.Now that you have a basic idea of what Asphalt 2 is, if you are interested in what SFO2 ecosystem on sonar looks like, jump in here.Asaana.com S4.2 so how does it work?402 is actually part of the part of HTTP protocol and ask for a tool.Mini payment is just plain HTTP request.First client hits your URL and then you reply for two payment requirements with a JSON payment requirement object.The client pays and retries with an X payment header.
Your verify and title respond 200 and OK no accounts, no oauth.Also take a look of Solana Developer guide intro to S402.You can find a lot more details about the S4.2 ecosystem, less SDKs.The explorer are some real examples in this guide.But today I just want to show you one thing how insanely easy it is to build an app with native S4.2 support on Solana.
So here's we are going to build users will pay $0.01 in USDC and after they pay they can access an AI Fortnite tailing service powered by ChatGPT.They just enter their birth information and the app gives them an AI generated fortune prediction.That's it.Super simple.To build this we are gonna use Solana developer templates.
You can use all of them directly through Qua solanadap.If you are new to Solana developer templates, I really recommend you playing with them first.With these templates AI you can save a ton of time and try out ideas really fast.From this example we are gonna use the Solana S4.2 next JS template because we got a demo app that already has S4.2 payment support built in.Alright.In this project we will use Solana S402 template.It is a simple next JS starter template with X402 payment protocol integration for Solana.It demonstrates a Streamlined integration of X4.2 payment protocol using a library called X4.2Next making it very easy to add cryptocurrency payment gates to Nest JS apps.All right, let's first take a look at the templates.This is the templates we're gonna use for this project.
You can see it's very clear we got what is Nest Flow 2, its features, get started, how it works and product structure.We can just install it because I already installed all the prerequisites.We can just simply copy it.
Here.We're gonna jump into this.Okay, You see there's an arrow but no need to worry about about it.We can just see if it's already created.Okay, it's good.
Let's come into it.We can just manually install.
Great.Right now we got everything installed.Let's take a look of the code.
We can first take a look of the template structure here.We got app and this homepage Also we got a layout here and styles also let's go to the content.
Also let's say the most important part here is actually the middleware.You can see we installed the the payment middleware function from the NAS x4.2 NAS library.Okay.As you can see, we got the route for each payment service here.We will configure the payment routes using this payment middleware feature from the Nuxt Fold 2 library.
And please note the original matcher here is actually match all the patterns path which means that this middleware, which is a payment middleware will be valid to each path except for the static files.This is where we need to set up the route for the path covered by the middleware.So we already take a look of the overall structure.We also need to set up the environment here.So we can just set up another one but call it.
ENV local.You can set up the Solana receiver drives and also the network you want to use.So here we're going to use Solana Devnet for our testing purpose.And you can also set up your facilitator URL.Right now we are using the official S4.2 facilitator and also you need to set up your CDP API key because this is from.
You can find it from the portal of Coinbase developer platform here.I'm going to use mine but you know we can take one minute to set it up.Just give me a minute.
Great.It's already set up.Let's take a try.We can run so make NPM run directly after we set up the environment.
Great.We can just take a look how it work.
It needs a bit time to get it loaded.We can access.
No, we are not going to use metamask but feel free to use it.My used wallet is phantom.You can choose your favorite wallet to use it, but default one is actually metamask.As you can see the demo work well.Just as expected.
You can unlock your exclusive content or uncontent here.Here so you just paid by your wallet.Right now we use divide.So it's Div Knight Token.
So this is what the template look like.Now let's add our own feature and further make this template into a certain tailing app.I already finished the code actually.So you can just check the code link in our description.But also you can try yourself to wrapcode and it it's actually very easy to achieve the same features.Let's go through the finished code.Let's take a look of what we create here.Let's go through the app API and the route Here we create a route which is a post method to receive the birth information we need to get from the user.And also in this post request we call OpenAI ChatGPT for all mini model to act as the fortune tailor and return the photon results as a JSON fail.So this is basically the API here.
And also we create two page here.One is a filter, another one is a pay for turn here.So under the pay for turn here we actually it will trigger a payment when your services pay 13 payments page and it will show this page after a successful payment from our paywall of XFO2 and then provide a button for user to open the feature page and after user managed to pay it and open the feature page 13 page.We can see the 13 page here.
So here we set up like Swiss state.Here one is birthing which will accept the user's input.And also we got a button to return the information got from the ChatGPT.And also we need to fix a little bit.You can see it's very easy front end.
And also we need to fix a little bit about the page.We need to add another button which is called Fortune teller.
All right, so this is what the finished code look like.We can just start to explore it.
We still just use NP Rundao.
Okay, we can just go back to.Net.
As you can see here we got a new fortniteller button.We can just click it and pin out.
Okay, it's payment successfully.And let's open the front teller here you can input your like birth date.I just use a template one.
Ah, as you can see you got the 13 result as a JSON file from the ChatGPT model.
It shows as a gates into the comastic energy surrounding your birthday.So it will show you 13.It's actually a tradition in ancient China, but it's a fun.Lots of people actually believe in it.All right, this is the app we built today.
So let's go through the architecture together here yeah we change the button in the main page page and then it actually goes to the pay fortune page.But when I need to go to this pay for them page I also need to set up here in the middleware so that I need to set when I go through this path I need to set the S4.2 payment is actually routing to this path pass and make it solid.So after it gets accepted into payment middleware and it will trigger payment and further show the content from this pay fortune page and then when user opens the frontailer it will generate the fortune page and in this page we will going to call the post request to OpenAI and then get response months from the chatgpt to get the 13 result.All right today we'll learn how to use Solana S402 to build a payment app on Solana with its native X4.2 support and also we use a library called X4.2 NASJS here in our project and also we learned how to integrate third party APIs here we used OpenAI ChatGPT model.
If you want to learn more about S4.2 how it works, what it is in Solana, feel free to check out the Solana.com NextJS page.Looking forward to see you cook with Asphalt 2 on Solana.Cheers.
19. RWA Mystery Box and Kodama
Shows a real-world asset minting app where users mint Labubu NFTs from a mystery box. It covers Anchor build, IDL generation, Kodama client generation, frontend integration, and metadata setup.
Ciam everyone this is Mike the APCW at Solana Foundation.The most common topics we heard these days is Real world outside.Today we are gonna explore what is real world outside, why it matters and we will also build a RWA app on Solana together.Real World Outside tokenization is a process where an outside is converted into a programmable token.Starting from the stablecoin more and more outsides are gradually adopted to get tokenized on chain.
Now Solana has over $1 billion daily volume of RWA trading.Solana aims to be the Internet capital market where a distributed Internet ledger hosts all finance within a unified liquidity layer.On the sell side, issuers can get the most efficient price discovery with deepest liquidity.On the buy side, asset ownership can be more distributed.Investors open access to our assets class anytime anywhere enjoy the most capital efficiency.
Okay, let's get started to the building part we will build a daemon app to let users mint real world outside.The app will be Labubu Mystery Box onchain.It allows users to mint Labubu as non fungible token on Solana.Please note that Labubu we mentioned here is just used as a demo example for educational purpose.My favorite Labubu collections Series is Lazy Yuga.In Lazy Yuga there are 11 types including 10 common one and one rare hidden one.In our example we assume to have 120 Labu boos for each common type and 6 Labu boos for the rare type.So in total we've got 1206 Labubus in our inventory.We also need to create a token program for each labuuubu type.We need to use token2022 as a token standard and using Kodama CLI to generate client code for building the app.
I already finished the starter code with the mock frontend and finish program.You can find the repo in the linked description.The starter code is based on Create Solana app templates with nice JS as the front end and anchor and kit are the tech stack of Solana.You can find the original templates in Solana developer templates.Let's take a look of the starter code.
Alright.First let's take a look of the overall code structure.It's actually a very basic anchor code.Here you can see we got program with the SRC and IRB for the Rust program.And also we got a test here and also we got a Nest JS app.You can see the front end with our components and a basic page.So let's go to the IRB dls which is our program fill.First you can see the overall structure.Here we got the necessary imports and also we got three core instructions which is Initialize Collection, Create Labu Mint and the third one is Mint random.
Also we got the data structure here if you see the line a little bit below.
Also we got a solid account validation logic here.An error definition.We customize here.First let's take a look of the three core instructions.The first one is Initialize collection.In this instruction we initialize an inventory array called collection for the Labubu series and collection will be used as a PDA account later.
And you can see the second instruction is create Labubu mint.Here we create a token 2022 mint for each Labubu type.First we validate the ID is valid and also we calculate the rent.And then we create Mint account using system program where it's actually trigger a CPI which is cross program invocation.
And finally we initialize Mint using the token program which is initialize mint 2.
Here we define it is an NFT because it's indivisible.
Third let's take a look of the Mint random instruction where we will mint a labopo NFT to a user.
First, if we also validate, the ID is valid and the inventory is also valid, we need to decrement the inventory by one and then we have PDA to sign.We use collection PDA as a mint authority here.And finally we update the state.
If you say overall.Actually this is a program where we use CPI is that first we have our vault program to cross program invocation call our system program to create account and further we use CPI call to the token program which you can see we use initialize min2 and also mean to user.
All right, after we have a basic understanding of the program, let's go through the co generation part.
Right now we already have our program ready.We need to use anchor build to build the program.
Let's go to anchor file first and then anchor build.
In this process, it may need a bit time to rebuild the program.We need to wait for a while.
All right, now it's finished.Let's take a look here.We're just using anchor build.So what happens during this anchor build is that it will compile the Rust code which we just see into the binary program.In this process it will also generate IDL file into a target which is under target idl you can see there is a JSON file here.
This one is very important because you can basically interact with any problem on chain with this ideal.And also you can find its IDL through the Solana explorer.All right, let's take a look of IDL fail structure.Here you can see the address, metadata, instructions and data structure and lots of stuff.And in our next step we'll show you how to interact with this on chain program by generating a client using its IDL JSON field.
So in this case we will use codama to generate TypeScript client.Kodama is a tool which support Python, JavaScript, TypeScript and Rust to generate the client code interacting with the on chain program.In our case, we will generate the JavaScript code.
All right, we will use Kodama CLI tool which we need to set up the configuration under our root directories.Kodama JSON let's take a look here.You can see we got ideal paths and also it is a Scripts and JS which define which is a JavaScript code generator.You can see we use JSRENDER here and also we define the output directory.All right, let's run the generation comment.
It's actually very clear.Let's go to the root and then NPM run Codama VS Bingo is generated.You can see under the app it's Generated a directory called generated.
You can see the overall structure here.It has programs, also instructions and accounts and also the RSV customize and some shared types here.
So what we need to use is actually we need to interact by using these instructions.
Alright, so our next step is to integrate our frontend.In our case we can take a look of our what our fondant is like.
There are two page important here.The first one is actually just the same with our Solana template.If you already tried it, it's basically the same.Another one is where we need to customize the front end logic here to interact with the client code we just generated which is the Bubu card here here.So in our frontend which is client we use next JS and react as a front end stack and we use Sona Kit as client interacting with wana blockchain.
All right, let's take a look of the core part of our mystery box component.
As you can see the current starter code use a mock daemon for the blend box.
We can first run the NPM run dev to take a look.
It's already got started.
You can see this is the front end code which already generated.You can easily generate it like in less than five minutes with AI tooling here when you click the open mystery box button, it will generate the labopu randomly.So this is the basic feature of our frontend.
All right, so let's move back to add the real token Mint logic to really allow users to mint laboboo on chain by interacting with the Kodama client we just generated.
So here we need to import the function from the generated code code.
Here.I used Copilot to help me generate code, but sometimes it's not that accurate.
So this function is where we just generated it.
Let's continue.And also in part two we also need to have a list of mint drives of Labovo series.
After we initialize the document we can define it as Minter Dries.
So with this address user will mint labubu directly from this Minter drives.
Alright, let's finish the logic here.We need to define send ascending in our frontend.
No, we need to use the kit which is use send transaction here.And also we can define a signature because we can show the in our fondant and then add our logic here.
You can take a look of what basic logic here.It's actually just use our total weight in our inventory which is in total we got 1206 labo boost including like 1200 common ones and 6 rare ones.And also we pick one of them randomly.Here we can just continue the logic here.
Here we need to get the corresponding mint address first.
And then we need to create an object which is used as User Center.
And also under this user signer we have three parameters.We got user, wallet, address and sign Transaction, which is a transaction signing function and also the message signing function.
And then we need to create the instruction.
And in this step we need to really use the generated code.
Here we need to have User center, which is object we just defined, also Mint Mint PDA drives.
And also Labubu id, which is a selected labubu id.
And finally we need to send a transaction to Solana Blockchain.And also we can just directly get the signature because we can show it in our front end signature.
Here we use send with our instructions.
Also we can set the signature after we finished and also potentially we can set up an alert to say if we are successfully.All right, now we have already finished the front end part.But remember, we also need a script to guide the mint address, right?Because this time it's marked or our collection account doesn't exist and our 11 mint address doesn't exist too.
So it's a bit tricky here because we also need a test here.Here.
The task fail here.Although it's called a test, the file is actually a deployment initialization script.
The process is like after we anchor build, we also need to deploy and then we can just run the initialize here, which is a test.The test field has two goals.The first is to initialize the collection and the second is to create 11 mints for each labubu.
We can take a look of the test field Here it got two test.The one is initialized collection.The second one is query 11 minutes.
So in the first task it calls initialize collection instruction and create collection PDA account and then read and verify the account data.And in the second instruction it will loop 11 times to create a Mint for each Labovo type and print each mint address which is needed in our frontend.Let's take a try.
First we need to deploy it.
We can just deploy it to our devnet.
All right, as you can see here, we got a program ID and signature is deployed successfully.
Here it's a bit tricky because usually we need to run anchor test, but here I defined initialize in our anchor toml.Here you can see the initialize actually to run.You can see the initialize actually run the test.Here.
All right, I just chose another way to initialize it because something went wrong with my yarn here.Right now you can see it's actually already like initialized.And you can see there are 1211 address here.So remember we just like marked the mint address here.We need to copy each address.
All right, so already we got the collection initialized and also we got the front end finished.We can finally have a try of what the current front end is like.Like we need to run.
When I click here, you need to ask me to enter.
Bingo.So you can see here it successfully minted labubu with a transaction here.And this is a mystery box I just minted.So in our case we just use Labubu here for educational purpose.It's not for commercially used.
All right.You can also check the wallet to see there is actually a Labubu here.
So why is data here here?Actually you can set up the metadata in the metadata file.Let's take a look of what the current file is like.So here in the metadata, I set up the labopu with its ID name and description and attributes.Actually you can also set up this ESPL token with ITS metadata or token 22 metadata feature.
All right, let's try to summarize.So in our project, actually we go through the whole process of the program and anchor build into the IDL and using this IDEO to generate a client code by Kodama.And with this generated typescript code, we further import it into the front end to get integrated and add our core logic to allow users to mint Labu branching.All right, that's all for the demo app we built for you users to mint atlabubu.RWA will meet RWA as tokens on Solana SU token 2022 and make a mystery box to allow users to mint RWA tokens themselves.
Solana token 2022 is far more than this.You can explore more about the token extensions on Solana to achieve more features for your rwa, including KYC freezing, Async, confidential transfer and much more.Looking for forward to seeing your from AWA on Solana.Cheers.
22. Prediction Markets on Solana
Explains why prediction markets fit Solana well and outlines the full-stack architecture for a simple yes-or-no market. The section introduces the four core instructions and the generated client workflow.
Since late 2024, prediction markets have entered a mainstream discussion.The most famous ones being Kalshi and polymarket Prediction market platforms play uniquely to the strengths of the Solana Network, as they benefit immensely from low fees and fast transaction speeds.If you're placing a $5 position, you really cannot afford $1 in transaction fee.Solana makes small stakes viable, allowing everybody to participate.They especially grew in popularity when they correctly predicted the outcome of the 2024American presidential elections.
This election was famously misjudged by professional pollsters, while prediction markets correctly predicted the outcome.Why did the markets get it right?It comes down to skin in the game.Unlike polls, where opinions are free, prediction markets require you to back your belief with money.If you have bad information, you lose money.
If you have good information, you profit.Over time, the price moves toward the truth.People sometimes call this the wisdom of the crowds.With accountability at their core, these products offer a really simple premise.You are presented with questions about optimization outcomes.
Most of these questions are in the form of a binary outcome, a question for which the answer is the inverse of the other.Let's review a few examples.The price of Bitcoin will close in 2026 above 150,000.Yes or no.You pick a side, you stake your money, and you wait.
We could create a more interesting contract by proposing different brackets.Let's see an example.The price of Bitcoin will go above 150, but will stay below 200,000.The second question would be the price of Bitcoin will go above 200,000.By composing these binary true or false questions, we are able to find the closest outcome and refine our predictions.
Do note that some prediction markets have the capacity to use questions with more than two outcomes.But today we will focus on designing with these binary question limitations.This keeps the Solana program logic simple, simple, and they still provide a great user experience.So how do these markets actually work?The mechanics are straightforward.
When you take a position, your funds go into a pool.There's a yes pool and there is a no pool.When the outcome is decided, the winners split the loser's pool proportionally to their stake.Let's say that there's a market with 100 sol in a yes pool and 50 sol in a no pool.You put 10 sol on on yes.
That means you own 10% of the yes pool.If yes wins, you get to keep your 10 SOL back plus 10% of the no pool.That's 5 SOL profit.If no wins, you lose your 10 SOL and it gets distributed to the no side.This creates a natural price discovery mechanism.
If most people think yes will win, more money flows into the yes pool, but that also means that the payout for yes gets smaller at some point.Contrarians see value in taking the other side.The ratio between the pools reflects the crowd's collective probability estimates.This is what we're going to build today.By the end of this lecture, you'll understand how to create a prediction market on Solana from scratch.
We'll cover the full stack from the Rust programming running on chain to the React frontend that users interact with.On the Solana side, we'll write four instructions, create a market, take a position, resolve the market and claim winnings.We'll go over account design and constraints.We'll look at the validation logic that prevents people from taking position after the deadline or claiming twice their winnings.On the front end side, we'll see how to fetch all markets from the chain, decode the account data and display it in React components.
We'll use a code generation tool called Codama that reads our Rust program and generates a TypeScript client code automatically for us.This means we get type safe instruction builders without writing serialization logic by hand.The architecture might seem like a lot at first, but it breaks down into clean layers.Let's start by looking at the big picture.Let's get an overview of the architecture.
This is a full stack lecture, so while we'll go over the program, we will focus more on the integration between applications, signers and transactions.The front end of our application will use Next.We could use totally another React framework or even another JavaScript framework, but next JS offers an easy way to get started with a good production standard.We'll have stuff like server side rendering capacities, many optimizations for which we don't want to spend our time setting up manually.The front end will be split between two pages.
The first page will show all of the currently active markets.That would be where Our Bitcoin above $150,000 example would be, with components allowing users to take position on the yes or no.The second page will be a profit and loss page with statistics on past markets.We'll use this page as an example of how you can display data and present it nicely for users.These components will talk to the blockchain using two easy to use libraries that are part of the Solana SDK suite.This will be Solana Client and Solana React hooks.These will handle the wallet connection and the communication to the RPC layer for us.So when it comes time to connect to our program and submit instructions or read from the accounts, we'll be using a TypeScript client that will be generated from the Codama IDL library.Transactions will be sent to an RPC on Devnet and our anchor program will take care of acting on the different accounts in the PDAs.
One caveat that I would like to discuss is that this is not a production ready market.
The use of an Oracle would typically be recommended to resolve the markets as it would build trust in the process.But in our implementation, the market creator is the person responsible to resolve the outcome.As you can see, we've kept our account design pretty simple.In this implementation, each market will be a PDA so that we can easily reconstruct them on chain and each position holding a user's stake will also be a pda.Our whole design will contain a simple count of four instructions.
Create market.Market initializes a new market with a question that is yes or no.Place bet takes a position on either yes or no and then resolve.Market is for the creator who declared the market to resolve the outcome and claim winnings will allow people who've won money to claim their payout.The beauty of Solana programs is that these four simple instructions can be composed into a fully functional prediction market.
At this point you should have a clear mental model.A market is two pools, a market account and a handful of instructions that move lamports around with strict rules.Nothing magical, just constrained state transitions.That's why Solana is a great fit.It makes these tiny frequent stakes cheap and fast while keeping everything transparent.
In the next part, we'll get hands on with the onchain program.We'll define the account data, derive PDF and write the instruction handlers with the guardrails that prevent late stakes or double claims.We'll also talk about the trade offs we're making like create a resolve outcomes and how we might upgrade this in a future production system.Once the program is solid, we'll back up the stack.We'll wire the UI with a generated client.
23. Prediction Market Program Logic
Defines the market and user position accounts and walks through the create, bet, resolve, and claim instructions. It also explains the program's constraints, safety checks, and test coverage.
Alright, before we write any code, we need to design what lives on chain on.Solana programs are basically pure functions over accounts.Each instruction takes accounts in, reads them and writes them back out.So the account layout is the real API.
Get that right and everything else becomes straightforward state transitions.For this build, we're keeping it minimal.Two accounts market for global state and user position for a single user's exposure.No order book, no price history, no off chain reference, just the data that we need to validate date positions and pay people out.That's the whole idea.
All right, so we defined these In State rs we have the account macro and we have the derive init space macro.Just a quick note, the init space matters because Solana really allocates spaces upfront.So if you go too small, your transaction would fail.But if you go too big, you're going to burn fees forever.So with init space, essentially what happens is that anchor will do the sizing for us and we don't have to care about about it.
Alright, so here we're going to go a bit over how we did the actual protocol in the backend.So we're going to be going over the State RS where we define the different accounts that we'll be using in our prediction market.The first one I want you to take a look at is this market account.We have a little bit of information on it.A few things that I think we should look at is that we have a creator.
This is the person that's actually created the market.So the person who received creates a question, sets the yes or no every detail about the prediction market.The specific question.It is this person who took this action.So we're storing the creator's public key here.
We also have a market id.This is interesting because we will be having multiple markets.So we want to store all of these as a single ID so that we can differentiate them.Another important one I am highlighting here, the question part.We have to put a maximum length because we need all this data to be known ahead of time.
If not, we'll be paying too many fees.So I put a length of just 200 characters.This will keep the question pretty interesting.It will also ensure that we have a maximum.So this way you cannot have a full novel when you.For example, Some people might want to do really complicated prediction markets, but for a good UX you should keep things light.So maximum of 200 characters should be pretty good.So resolution time is the next thing we're going to have a look at.It is an i64 to store the actual time when the question will end.As an example, you could not really keep that open forever.People will want to see if they won, if they were right on yes or no.So the resolution time is the moment where the market stops, where we're going to release.Say this is finalized after tomorrow.
You cannot take any more position on this.And then we are storing the yes pool and the no pool.
So this will be where we store the full amounts.For example, more and more people are going to take participation on yes and on no and we actually need those later.On to do the calculations.For example, when yes has won and we need to know who took a position and who can can get the distribution of the pool.So we're keeping the total amount that people are putting on these yes or no options.
And now going over at the end result result is pretty simple.It really says has this been closed?So we have the.I think one specification is that the resolution time is the moment where you cannot take a position after this.But it could be that the question needs so you know, somebody to verify if the price really was over 100,000 as an example.
So the result is when the person closing the market has resolved it.We're going to flip this to true and the outcome will simply be if yes or no as one.And we are keeping the bump simply because this will make it easier for us to get back our accounts.Let's go now, scrolling down to the user position.What makes this a really simple design is that each user will have its own position, so its own account.
And in there we'll have to link it to the actual market.So if I'm a user and I'm taking a position on the yes pool, I need to know exactly of which market on which question I'm answering.I really want my stake to be put on the right.So I'm going to put this here on my user position so that I can link both a bit later.We're going to keep the user here just for simplification so that this user position, we're easily able to match it to the actual user who took it and we'll have a yes amount or a no amount.
This will have to work a bit in the constraints because we will want a user to really put a position only on yes or only on no.So we'll work on this a bit later.And lastly, we're keeping also the claimed status as an example.If I'm picking my money a first time, I should not be able to take it twice.So I'm going to store this information on the user position account so that we're preventing double claiming.And this is the tour of the accounts we had in state rs.Let's move on to the next parts.Alright, so in this next part part, even though we're not going to be building the prediction market line by line, the whole goal of this section is really to show you the interactions between the program, the code generation and the front end.Even if we're still going a bit high level, we should be trying to understand the business logic of our program.
Because anytime you're building a full stack application, you really need to understand these interactions between both.
It is really what makes the difference between just a quick screen that you put up and an actual thoughtful application that you've built.So we're going to go into Lib RS here, where most of our business logic is living.We saw in the step just before in the video, we saw that we had four instructions.We have create market, we have place bet, resolve market and claim winnings.So obviously creating a market will mean that we need all of the information that we had in state rs.
All of this information, we will need to have it in this function and we will want to set it onto the account.So going back to Lib RS here, if I expand this section, you will see that the user has to provide a market id, a question and a resolution time.All the rest were able to actually initialize it ourselves.So a few things that we need to take into account.If I'm setting this resolution time here, I obviously want it to be in the future.
For example, creating a market about an event in the past makes no sense.You could be taking position against who won the 2024 election.We all know what happened, so there's no point in actually creating it.So this is why we will use our require here to ensure that the resolution time time is actually above the current timestamp.We could go a bit into deeper territory.For example, I could say give it at least a day or at least an hour, but we're keeping things light and we're just making sure that it is over the current time.And after we are getting the market account and we are setting on this market account all the details that we want, such as the the question that we received, the resolution time, and the other parameters we're able to infer naturally.So, for example, at the beginning of creating this market, we know that nobody has taken a position on it yet.
So we can initialize the yes pool and the no pool, where we're tracking the actual amount of people that have invested lamports in this, we can initialize both of them at zero.Obviously, if we're just creating this market, it has not been resolved.So we can set that to false.And in terms of the outcome, we are not ready yet to set the outcome because this will have to happen in the later instructions when we're resolving the market.So I'll go ahead and I will close the create market instruction here and let's have a look at placing a bet.This is when the user actually takes into their own hands to say, yes, this is true.No, this is true.
True.So we'll have two information into our parameters for these instructions.Obviously, the user will want to put money where their mouth is.So they were going to say, going to put 10 SOL on this, or I'm going to put one SOL on this.And we will have to see if they decided yes or true in their stake that they're taking.
So let's expand a bit the instructions here.We need a few basic checks and balances.So we need to ensure that, you know, they're placing an actual bet, so an actual stake.So we need to check that the amount is bigger than zero.And if it is not, we will be returning an invalid amount error simply, we're also doing the checks to ensure that the user are still allowed to participate in that market.
So we will be checking that the current time is below the resolution time, because if it is over, it means that the market is closed.People cannot take a position anymore.Bitcoin has already crossed, you know, the threshold.We cannot really take.We cannot really place at this moment a stake.
And lastly, what we're doing here is a CPI to get the user's balance from the user and to get it into the account of the market.So this way we're actually able to confirm that the user is sending their soul onto the position that they're taking.And these last few steps.And again, I'm going a bit faster on this.I'm not trying to get you to write line by line this program.
Let's really focus here on the architecture.So we're trying to get the business logic so you could read all of these lines.But what I really want you to focus on is how they will serve into the future application that we're building on the front end and how the little different parts, you have to keep those into account.For example, I showed you here that we're returning errors.When you're building your front end, you will want to know those errors.You will want to understand what to display on the screen.If a user is doing something which the program is not set up to do, this is when you should understand what these errors are doing.So all this business logic that we have here is simply just taking care of setting the user stake when they're deciding if they're placing their stake on yes or on no.
And if it's not happening, we're simply returning errors and we're finalizing the user stake.
Let's close place bet and let's explore the last two instructions that we have quickly.What I think is the two most important ones which are resolving the market and claiming the winnings.Resolving the market.Here we're doing something actually pretty simple where we have the actual answer.Has Bitcoin passed 150?
Yes or no?Who won the election, Candidate A or candidate B?So all that we're doing at this point is that we are setting the actual answer, which means who has won, who has lost.So this is as simple as it gets.And the last instruction that we have is claiming the winnings.
This one is pretty simple.We have again time logic.So we're checking, okay, is the market resolved?Because if you claim winning on a market which is still open, obviously you should not be getting that money.And we're also doing a required check here because we want to make sure that you've not already taken your money, taken your winnings out of the pool.
This is important because we're trying to prevent double spending here.We're trying to make sure that everybody has access to their winnings.
And lastly what we're doing is that we're actually taking out the money of the users and we are also ensuring that the totals that are left in the pool are properly done.So These are our 4 instructions.As you can see, they're not super complicated.You'll have access to the code.If you really want to understand this line by line line.
What I really want to understand here is that these four instructions, we will need to interact with them in the next JS application.You could wire those manually if you wanted.However, serializing the answers from the front end to the program and using the RPCS to broadcast your transaction.It is actually a lot of manual work.So this is one of the reasons why in the next section we're going to have a look at a tool called Kotak, which will allow us to create our own TypeScript client automatically from this anchor program that we've created.
I'm just.As a final overview before we move on, I'm just going to go and show you that we also have a few tests here.So these tests essentially ensure the safety of our program.They do not replace a full audit.But you always want.When you're building a full stack application, you want to test every bit.You want to start from the program and you want also unit tests on your JavaScript application.This is important because creating a full on program means there's a lot of moving parts.So you need to pay special attention to these little Details.All right, so we're going to jump straight up into creating our Codama IDL client and I'll see you in the next step.
24. Generating the Prediction Market Client
Shows how the IDL is generated from Anchor and converted into a TypeScript client with Kodama. The section emphasizes why code generation reduces serialization bugs and keeps the frontend in sync.
Now we actually have running our full application here.I want to understand a little bit how we actually communicate between the program and the actual front end that we have on screen right here.So I have this next JS application.We are fetching markets, we're displaying statistics for our users.The key to wiring this up is creating what we call an idl.
So I will jump back into my coding editor right now.We sign our anchor folder right here, here, that we had this program with a bunch of instructions, with a bunch of errors.This is the way that we actually do them on chain consuming this program, you could decide to do it in a Java application, in a JavaScript application, or even in Python.The way that you're achieving this layer of communication is actually pretty important because you need to pass from the different discriminators, the different instruction data with the different bytes and the perfect order.So how do you actually achieve that communication?
You could wire it up manually, but just understand that this is error prone.There is a lot of little bytes, a lot of little details, a lot of things to tweak.So understand that doing this manually could lead you to have a bunch of errors in your program and they are just not fun to debug.They're a great learning experience.But instead we should be using what we call an idl, an interface definition language.
I will go back to my terminal here and I'm going to run npm.Run, run Anchor build.You've probably seen that command directly with the anchor cli.I wired up all of this in a full stack application.So I'm using NPM to run commands.
But what NPM Anchor Build is doing right now is that it is building our program and it's compiling.But if we go back to our editor here, there is a specific file that I want to look at.If I go under Anchor program, Prediction, Market and Target get, there's a folder called Ideal.Let's open it up and open the big JSON file in it.At first glance, this file is nothing special.
We can see different names like the market discriminators.But if we look a bit more closely, there's something interesting.Everything that we've built into our anchor program, such as our instructions, the error, everything maps perfectly into this IDL file.For example, under the instructions section.If I open that up, you see that I have my claim winnings.
Instructions here.This is Essentially the translation of the logic of your program for other clients to consume.The IDL is essentially a contract layer between the program and the different consumption and you could have a lot of different consumers.So if you want to go ahead and use this data, use this ideal in something of a more useful format.What you would do would be to wire up a tool like Kodama.
With Codama, you're able to generate a client from that IDL into the target language of choice.In our case this will be typescript, but you could fully decide to go with go with Python.There are a lot of different options for your your target language.So we've wired this up with this Codama JSON file and the way that we wire it up is that we give it the information of where to look for the IDL file.So if you see this property here, we're pointing to the relative path of predictionmarket JSON in the IDL folder.
And we're also pointing exactly to where we want this to be generated.More special, what I want to happen is that I want to generate a client inside my next JS application so that I will be able to consume it later on.Let's see it in action.So if I go back to my editor here, our last command was NPM anchor build.Now we will do NPM run Codama js.
What this does is that it reads the IDL and it generates a typescript client for us.Let's go and open that client itself.We've put it into application generated and then index TS once I am inside my generated client file here we can actually see that the accounts, the errors, the instructions and the programs are matching one to one the different sections of the idl.This is important because this is a communication layer between our frontend and our backend.As an example here, here if I click to definition on my instructions, you can see that the exact same four instructions claim winnings, create market, place a bet and resolve their market are matched one to one as we wrote them in our program.
I'm going to click again to claim winnings to explore a little bit how these files look like.Do you understand that you don't have to understand these files completely?There is a good reason that we generate them automatically instead of manually because this is really error prone.If you did this yourself, it is a great learning experience.But you might not want to directly yourself consume this low level code simply because if you're making small errors, it is quite common that you'll see these errors reflected in your Front end quite quickly.
One important thing to understand about these generated IDLs and degenerated clients is that this is a workflow that you have to work in between your program and your front if you have a bug, if something is not working when you're decoding accounts, there's a high chance that you've probably forgot to redo your program and redo your generated client.The workflow is generally you do a change in anchor, you run NPM anchor build and you do again decode.On my client, you could put those in a loop if you wanted.You could do hot reloading as an example, where each time there's a change in your anchor program, you rebuild your client.However, sometimes building a program can take a few seconds to up to a few minutes.
So I would say do it manually at first.And once you find your groove, if you feel that you need to automate this, this is a good call.In the next section, we're going to dive directly into the front end architecture.
25. Prediction Market Frontend Integration
Walks through the Next.js frontend, React context providers, wallet hooks, and market creation UI. It shows how the generated client powers fetching, submitting, and refreshing on-chain data.
So I'll meet you on the next steps.In this next section, we're going to see the front end architecture.In the previous section, we've talked about the program making the IDL in a client frame, but now we got to put it all together and see how it all works and connects to make a full Stack application.So in my editor here, in my terminal here, I will do NPM run dev.I will switch over here to my code editor.So this application is an XJS application.You have a few options when it comes time to make your front end or for a web application.
The most popular ones on Solana are generally React applications.And the most popular React framework for production, I would say, is Next js.Even though you could be using Svelte, you could be using Vue, but Next JS is where you will probably find the most support in libraries and in content like tutorials on the web.So if we explore here the structure here I have my anchor folder, which I'm going to skip because we've seen that a bit before.And I also have my app folder.
Inside my app folder we have our generated client, which we'll be consuming in our pages directly.And if I open the index page here, page tsx, we're going to see a few things where we have our homepage.Let's scroll down just a little bit.Our homepage, if we see what it looks like in the ui, our homepage has a header where we have the wallet connection at the top.We also have a button to create new Markets and we have this table here where we're showing active markets.We're going to create one in a little bit and we have these past markets.So this page, as you can see, has a lot of Solana information.We needed to actually wire this to go ahead and start the fetching so that each component can properly display its own data in Next js.When you want to provide information to multiple components, you will want to use what we called a react context.
A react context is simply simply a component which is able to pass down its information to all of the child components under them.
And if you want to put a context for the whole application into Next js, you will put it in your layout file at the root of the application.So let's open out Layout TSX here where we wired all the magic of this context provider.It's pretty light.As you can see there are the regularnext JS information here.We're not going to look too deeply on those.What I really care about is this provider tag in JSX here.This provider is essentially wiring all of the different communication layers that our components will need in order to communicate with the Solana RPCs.So let's go over and open up that component.So inside Providers tsx there are two things I really want to look at here.The solanaclient NPM packet and the Solana React Hooks NPM package.
A few things to note.These libraries are part of the JavaScript SDK family that are built on top of Solana Kit.But you do not have to write a lot of solanakit code manually by hand because these libraries are fully wired so that you can really go and focus straight on your business logic.I'm gonna go over pretty quickly what we're doing here.We have this devnet RPC UI which you will want to have a proper RPC provider if you're going to production with your application.
We'll talk about this later.In the boot camp you will want to create a client where you pass the wallets that you want to support and you will pass your commitment level.The client by the way supports a number of options.By default you do not have to set those.However, you have full control if you decide to to do.
And lastly, this one is a bit interesting.I'm going to pass in my query configuration.This allows me to set the behaviors of how fast I want my application to refresh and revalidate content on chain.This is pretty fun because some things, like a simple CRUD application, for example, would not need to be refreshed.Pretty often you would simply create an update data and you'd refresh it when your chain.
But if you're doing something like a prediction market, which have to have a really real time feeling, you might want to bump those numbers to be refreshing even faster.In our example, we're refreshing every 3,000 milliseconds here, which translates to three seconds.You can even get that down if you wanted to 500 centiseconds.We're passing in between units here, but essentially this wouldn't be near real time.But understand again that there are different options between polling and doing websockets.And we're currently doing a polling mechanism here.So once this Solana provider is wired, it will be available to all the children components such as fetching our markets or fetching the past data for the users.We're going to get back to the UI to understand a bit the components that we're playing with with here.So in our first stop we're going to look at how we can create a market.
So in the UI we simply click new market.
We have to ask a question.For example, will Sol hit $200 this month?And then we decide when it ends.This was the resolution time in our accounts.I will set it for one hour or I'll go ahead and I'll create it here.This will submit a transaction into the wallet provider that I'm using in this case Fantom.But every wallet would work and once we confirm, we should see this market appear pretty soon.So as you saw, we have around a 2 second to 3 second refresh delay.We could still wire it so that it's manual like I just did here.But essentially all of this flow and communication is consuming the IDL that we created.
Let's go over and explore a bit the components and see what they're doing.I'll go over to my code editor back here.Let's go to the Create Market form, which was the first form that we actually created that we just used to make a market.Let's focus a bit on the different sections here.So I have a few constants here, here just to make a good user experience.For example, you want to provide information for your users to get started so that they're not lost.It provides them with a good series of information to get started in userhap and then we're consuming a few react hooks such as User Wallet connection.This is simply because we need to ensure that a user is properly connected before using that component.And we are using Use Send transaction.This will be needed when the user is actually submitting the creation of the market.
And here, one part that I want to get your attention on is that we have this endle create function here, which is wired up to the submission of the form.There is one special place that I want you to have a look at.You see this get create market instruction async.That was a mouthful to 2 say this is coming from my Codama IDL.So if I click to definition here, you can see that this is all the code that was generated for me that I did not have to write by hand.
This really makes everything much easier to do simply because by passing my arguments, such as here, I'm passing the creator a tiny typescript bug that we're going to have to fix later on, but the call still compiles.As you can see, these instructions, these arguments to the function are really way simple, simpler than wiring all of this manually yourself.There's a lot less problems that you're going to deal with.You simply just have to pass your proper arguments and all of this will be handled for you.The rest after we are ready to send the transaction with the send function, which came from the use send transaction hook.
The rest is really just wiring up the UI to give a proper user experience.Experience.So as you can see, we're doing a bit of UX cleaning.As an example.We're resetting the question to be empty.
We're sending the status to say that the market was created.This is all the behavior that we're not going to go over.We're going to have deeper videos on the Solana YouTube channels about doing a proper UX.We're simply trying to understand the flow here between submitting data on channel and actually having reflect in the ui.So all of these little details, they are mostly to give your users a better experience, such as loading times, such as really reflecting the data and refreshing the data that you're seeing.
So this was for creating the actual market.Once you've created it, what will be really important will be to fetch it and display to the user.So I'll go back to my code here and I will go to this markets list.TSX file here.All right, so inside our marketslist, TSX file here, what is really important to understand is that we are fetching the markets.
We're doing a little bit of filtering by active and by past markets.For example, if a market is still open and allows a user to take a position, we're good on it.We're going to display it as the active one.And for past markets we'd have resolved which are in the past, for example the 2024 presidential election.We're going to put them in this pass section.
What I want to have a look at here is this Use program hook from the Solana React hooks library.So a program has different accounts.The use program accounts will give you directly access to fetching all of the on chain accounts directly by passing just a little bit of information such as the program address and how you want to encode and decode these.We still openly have work to do on making this easier.This will probably improve a lot by the time that this boot camp is released, so please go ahead and look in the code.
You should probably see something much easier than this on screen.But for now just understand that the best way of doing this is by consuming these used program hooks and that things will be much simpler pretty soon.And then everything that we're doing if I scroll down is logic around changing the tabs between the active and the pass markets.But I want to bring your attention over the rest of the ui.So I'm going to get back to my application here.
Something that is always nice to give to your users is data around all of your interactions with your program.So this but this activity tab here that I've just clicked on, as you can see is pretty data heavy and we are still doing the same accounts reading to provide all of this data.All we are doing is simply doing more advanced react filtering and animations to give users a bit more data.And all of this is really possible because I didn't have to write by hand all of the IDL and the generated cloud client.This would be way more difficult if we were writing and wiring all of this manually.
So as when it comes to really doing this full stack communication between your program and your front end, what you're looking for is a smooth experience for your users.You should try and build applications which make it feel like the blockchain is just erasing itself.You're trying to achieve a point where where yes, users are connecting their wallet or maybe they're even connecting with social login.But you want to get to a point where this is easy and pretty seamless for your users.And the current libraries are getting much better at doing this.
For example, this refetching in providers really allow you to create these kind of applications where everything is live and refreshes for your users.In the next part we're going to go over the different problems that we had and how you could expand upon this project.
26. Prediction Market Tradeoffs and Upgrades
Reviews the tutorial's deliberate simplifications, including creator-based resolution, polling, and no fees. It suggests production upgrades like oracles, fees, partial exits, and stronger governance.
All right, quick but important section.This is a tutorial build, so we made a bunch of choices that keep the code simple.Some of these choices trade off decentralization or product features.That's fine for learning, but you should know exactly where the shortcuts are.We'll go through the main design decision, the protections we did add and the obvious upgrades that you would have to make if this was a real product.The biggest one is resolution.In our build the creator decides the outcome.That keeps the program tiny and it avoids integrating an oracle which is a way of getting data from outside to on the chain.
But it creates a trust issue.In production you would want to add an oracle or even multi signature so that no single party can manipulate the results.We also had to make a trade off with polling.It's easy to implement and understand, but it adds a bit of latency and it's a bit heavier on the RPC load.So if you want a real time user interface, you would use websockets or an indexer.
Also, all or nothing bets keeps the mat clean, but you cannot do partial exits, cashouts or liquidity.That makes the program easier to reason about, but it limits really what traders can do.And most on chain activity is usually people trading.And finally we skip fees.This keeps the math pure and the example clear.
But it also means that there's no revenue or sustainability for the business.In production, you will want to add a small fee on each person taking a position or on the winnings simply for production.Let's also have a basic look at the security that we built in.It won't be perfect, it will not replace an audit.But here are the minimum protections that you want to add into any prediction market.
The arithmetic checks are there to really prevent an overflow attack.In a peri mutual system, overflowing a pool can change implied prices or even allow free bets.Using check math is boring, but it's critical.We also gate claims with position claim that makes claims indempotent and prevents double spending.The resolution time check prevents late position and the creator check prevents random signers from resolving the market.
Notice what's missing.We do not prevent a creator from resolving early with the wrong outcome.And we don't prevent griefing with spam markets.Those are product and governance problems that more mature code bases would actually have to handle.We also don't do anything about front running or transaction ordering on Solana.That usually shows up as users racing at the end of a deadline.But For a tutorial, it's really fine.If this was production, we'd have to do a bit more work to cut everything and put everything together properly.But you know, this is a tutorial and it's completely fine.So what could we be adding to make this a bit better?
First we could add decentralized Oracles.Switchboard or pith really come to mind.We could also add a fee so that the protocol is more mature and sustainable.Another one for people actually interacting with these markets would be partial position exits or better liquidity.And obviously like we discussed in the beginning, multi outcome markets that go beyond the simple yes or no question.
If you really wanted to keep going, I would say work first on Oracles.They're a bigger upgrade, but they're the most logical one to do.You could also look right next to this after fees to make a better protocol really work for you.So the takeaway is simple.The core is solid, but this is a learning focused version.
If you treat it like production, you will want to harden it.That's normal.Engineering and creating projects is all about a balance between solid features and security.Security.Keep your users in mind.
Take the decision, but continue learning.
28. Production Readiness for Solana Apps
Covers the operational realities of launching a real Solana application, including RPC reliability, priority fees, retries, confirmations, and error handling. It closes with guidance on key management, monitoring, load testing, and production security.
Now we're going to talk about something that does not get enough attention in the solar panel production readiness for application.You know there's a massive gap between building a proof of concept that works on devnet to really launching something that can handle money users.Because there are real consequences if there's bugs.Honestly, this gap has burned a lot of dreams.So let me start with a story that might sound familiar.
You're building this amazing payments application.Maybe it's a checkout system, a marketplace or Something with some Fordham subscription.You've tested it locally, locally, you've deployed to Devnet and everything works beautifully.You're ready to flip the switch and you start processing real transactions.And then it hits you like a truck.
So suddenly you're dealing with questions that you never thought about.Let's go through them.What happens when the RPC endpoint goes down and you're in the middle of making a payment?Or the transaction is stuck in the mempool and it expires?How do you handle priority fees when a network gets congested?
Or what happens if somebody tries to pay you but the token is actually fake but looks exactly like usdc?So how do you keep also your sending keys secure when you need to have them accessible by your backend service?This is not theoretical.These things will absolutely wreck your launch if you don't handle them properly.And the tricky part is that most of these issues don't show up in testing.
They only emerge when your life there's real money on the line and people are expecting things to just work.So let's break the down what production readiness actually means for Solana applications.Because it's not just one thing, it's a whole system of considerations that work together to create a reliable, secure and maintainable infrastructure.So first of all, let's talk about sol.This seems basic, but it's critical.
Your mainnet wallet needs actual SOL to pay for transaction fees and rent.Not a lot necessarily, but you need to think about this operationally.How much SOL do you need?Well, that depends on on your transaction volume, but you should be thinking in terms of coverage fees and thousands of transactions plus rent for any accounts you're creating.And here's the thing that nobody tells you.
You need a system to monitor this balance and alert you before you run out.Running out of SOL mid operation is embarrassing and completely avoidable.Now, RPC configuration.This trips up a lot of teams during development.You're probably used to using the Solenoid RPC endpoint.They're great for testing, testing, but they're not suitable for production.They're rate limited, they can be slow, and they don't give you any guarantee of uptime or performance for production.You need to configure your own RPC endpoint.This normally means running your own validator, or more commonly, getting a dedicated provider like Helios, Triton, Quicknode and way many more.There are great services out there, so use them.
But here's the crucial part.Whatever RPC setup you choose, it shouldn't be a public endpoint point that anyone on the Internet can hit your production RPC should be private, authenticated and locked down to only your services.Why?Because there's rate limits, security, and you want to control exactly how your application interacts on the network.And speaking of RPC endpoints, you need a fallback.
Your primary RPC is going to go down at some point.It will.There are great resources.But if there's a maintenance window that you forgot about, or there's a DDoS attack, maybe even the submarine cuts, an underground cable, that can happen and it is a real problem.Whatever the reason is, you need a backup RPC for your application that you can fail over to.This means your codes needs to be designed with multiple RPC endpoints in mind from day one.Let's talk about priority fees.It seems optional, but when it becomes actually needed, it is critical.Solana's transaction process processing is a first come, first serve by default.So when a network is busy, priority fees lets you jump the queue right up.
For applications that are critical, like payments, this can be the difference between a transaction going up right away or sitting in the mempool and waiting for seconds, not minutes.But it could delay your user experience.The smart approach here is dynamic pricing.Don't just hard code a priority feature fee value.Implement logic that looks at the current network prediction and adjust the priority fee accordingly.
When a network is quiet, pay the minimum.You're good when things are congested.Prefer what you need so that your transaction goes in.Your users want your payments to work.They cannot wait for seconds at the coffee shop.
So spending an extra fraction of a cent on priority is worth making the experience better.Now we go into the retry logic and it gets interesting.Interesting.Solana transactions can fail for all sorts of reasons.Maybe the block hash expired before the transaction was processed.
Or maybe there was a temporary network hiccup.Maybe the RPC endpoint had a momentary issue.Whatever the cause, your application needs to handle these failures gracefully.But here's the tricky part.You can just blindly retry after every failed transaction.
Some are permanent.Maybe there was a real error in the code.Or maybe somebody tries to pay you with an account that they don't control.Retrying won't help.If there are insufficient funds, retrying won't help.Again, your retry logic needs to be smart.You need to understand what is a temporary failure and what is actually a bug that is preventing your user from doing the real transaction.And there's another layer to this block hash expiration Every Solana transaction includes a recent block hash and it's Only valid for 150 slots in today's conditions, 60 to 90 seconds.If your transaction doesn't land within that window, it expires.Your retry logic needs to detect this and create a fresh transaction with a new block hash and retrying instead of using the old one.
Confirmation levels this is really a concept that trips people up because it's different than other blockchains.On Solana, a transaction can be confirmed at different level processed, confirmed and finalized.Processed means it's been included in a block coming Confirmed means that the block has been voted on by a super majority, and finalized means that the block is rooted and cannot be rolled back.For most applications, confirmed is the right level to wait for.It's fast, takes a second or two and the probability of rolling back is really low.
Finalize is more secure, but it's longer.There's 32 slots around 13 seconds around the current conditions, so you need to choose based on your use case.High Value Payments Payments might be justifying waiting for finalize, but regular payments don't.Error handling really deserves its own deep dive.Also because Solana's error messages can be cryptic at times and your users don't want to decipher them themselves when something goes wrong, you need to catch the error, understand what it means, and present it as something useful to the user.
Transaction simulation failed is not helpful Insufficient funds to complete this payment is actually working well.Think about all the common error cases like insufficient SOL for fees, insufficient token balance, invalid recipient address slippage tolerance exceeded if you're doing swaps account not rent exempt Every one of these should be handled specifically with a clear message if you're dealing with gasless transactions.When you're covering fees for your user, there's another level of complexity added.You need a robust system to sign and submit transactions on behalf of their users while keeping security and preventing abuse.This typically involves some kinds of transaction relay service with rate limiting and fraud detection.
Now token verification.This is critical and often overlooked.If your application accepts payments, for example usdc, you must verify that the USDC payment that is being sent is not a scam token created to look just like it.Check the mint address against the authentication official list and don't trust what the token calls itself.Verify the actual on chain address of the mint token.
Security is paramount.Your private keys are the keys to your treasury.Literally.They should never ever be stored in your front end code anywhere a user can access them.Backend only encrypted address with proper key management practices.
Consider using a hardware security module HMS or a key management service and implement the principle of least privilege.Keys should only be able to do exactly what they need and nothing more.Actually, let's expand on that.You should have separate keys for different operations.Don't use the same key for signing transactions or for controlling your treasury.
Set up a proper key hierarchy with hot wallets for day to day operations and cold storage for the bulk of your funds.And please, please backup your keys secure, securely Multiple backups in different locations Proper encryption now Transaction Monitoring and alerting.You need to know what's happening in your application in real time.Set up monitoring for transaction success rates, average confirmation times, failed transaction patterns.If something is going wrong, you want to know immediately, not when.Users start complaining.Build dashboards, put up alerts and integrate with incident management systems.And finally load testing.You cannot skip this.You need to test your system under the load that you expect to handle and then test it under way more load than that.
What happens when you go to processing ten thousands an hour?One million?What's the bottleneck?Is it your RPC connection?Your database signing service?
You need to find the limits before your users find them themselves.Production readiness is not about checking boxes on link list.It's about making a system that is reliable, secure and maintainable under real world conditions.It's about thinking through the failure modes, the edge cases, and respecting the fact that you're handling money and users expectations.The good news is that this is doable.
Teams successfully launch applications on Solana all the time, but they do it by taking these considerations seriously from the start, not adding them on as afterthoughts.So as you're building your next application, keep these principles in mind.Your future self, your users and everybody will thank you.