In this tutorial, we will learn how to create a simple blog post dapp on Solana blockchain. while building this dapp we will learn how to write the Solana rust program, Test the program and finally integrate the program with React frontend.
This tutorial assumes that you have,
- Basic understanding of React.js
- Basic understanding of Rust
- completed Solana 101 Pathway
This tutorial covers how to build a dapp on the Solana, but does not go through the installation of individual dependencies(as it assumes you already completed the Solana 101 Pathway on Figment learn).
Anchor Framework - Anchor is a framework used for Solana dapp development, It provides DSL to interact with the Solana program. If you are familiar with developing in Solidity, Truffle or Hardhat then consider the DSL is equivalent to ABI. Follow the guide to install Anchor along with Rust and Solana cli.
React.js - To interact with our Solana program we will create client-side app with React.js.
Phantom Wallet - Phantom is a digital wallet that lets you connect your crypto account to any dapp that build on the Solana blockchain. We will use Phantom wallet to connect to our Blog Dapp.
Vs code - I will recommend using vscode with rust analyzer extension as it has a great support for Rust language.
Program - Solana is a fast and low-cost blockchain, to achieve speed and low-cost Solana has slight different programming model. Solana uses Rust programing language to create programs as, you notice we keep saying Solana program instead of Solana smart contract from choosing programing language to naming concepts Solana is different, in Solana world smart contracts are known as Solana Programs.
Account - Solana programs are stateless so, if you want to store state, you need to use an account for it and accounts are fixed in size. Once the account is initialized with the size, you cannot change the size later So we have to design our application by keeping this in mind.
Rent - on Solana, you need to pay rent regularly to store data on the blockchain according to the space the data requires, The account can be made rent exempt (means you won't have to pay rent) if its balance is higher than some threshold that depends on the space it is consuming.
As we have learned, we need an account to create our blog dapp that has a fixed size so, if we create a single account with X size and start pushing posts inside that account, eventually the account will exceeds its size limit and we won't be able to create new posts. If you know Solidity, in Solidity we create a dynamic array and push as many items to it as we want, but in Solana our accounts will be fixed in size so we have to find a solution to this problem.
Solution one - What if we create an extremely large size account like in gigabytes? on Solana we need to pay rent of an account according to its size so, if our account grows in size the account rent will grow along with it.
Solution two - What if we create multiple accounts and connect them somehow? Yes, Thats the plan. we will create a new account for every single post and create a chain of posts linked one after another.
Linked 🤔 yeah, you guessed it right. We will use LinkedList to connect all the posts.
Before we start with actual development. We learn some Solana CLI commands docs:
To see your current Solana configuration use (I assume you have followed Solana 101 Pathway and you have all the CLI installation done):
Your output might have different file paths. You can check the current wallet address by:
You can check the balance of your wallet:
Or, you can airdrop tokens to your account:
Check balance again. Now you should have a balance of 1 SOL in your wallet.
Now its time to scaffold our blog app with the help of Anchor CLI:
The anchor init command creates the following directories:
Before writing program code update Anchor.toml
Now we are ready to start with the Solana rust program. Open up the
lib.rsfile located inside
This is the basic example of an anchor Solana program. There is only one function
initialize, which will be invoked by the client. The initialize function has one argument of type context of
Another noticeable thing is
declare_id!, which is a macro that defines the program address and is used in internal validation. We don't have to think about it too much. This will be handled by the Anchor CLI.
Now its time to start declaring states of our blog app.
As you have seen in the first basic example, we need to create functions that will define tasks that we want to perform on a program like, init_blog, signup_user, create_post, etc.
we will start with creating our very first function
As you know, every function needs a typed context as the first argument. Here we have defined
InitBlogas a type of our
init_blogctx. In the ctx type we have to define the account and the account will be provided by the client(caller of the function).
InitBlogthere are 4 accounts:
- init attribute to create/initialize a new account
- space = 8 + 32 + 32. Here, We are creating a new account, that's why we have to specify account size. We will see later how to calculate the account size.
- payer = authority. authority is one of the accounts provided by the client. Authority is rent payer of blog_account.
- We are also creating this account, that's why the init, payer, and space attributes are there
- To create LinkedList we initialize the blog account with the very first post so, we can link it to the next post.
- program signer is a creator of the blog.
- required by the runtime for creating the account.
init_blogour plan is to initialize the blog account with current_post_key and authority as blog state so lets write code for that,
This how easy to create an account that holds some state data with the Anchor framework.
Now, We will move to the next function, what we can do next?? user, user signup. Let's define signup function with which users can create his or her profile by providing name and avatar as inputs.
That's the basic skeleton to create a new function but here is how we get the name and avatar from user?? Let's see.
We can accept any number of arguments after ctx like here name and avatar as String (Rust is a statically typed language, we have to define type while defining variables). Next is
SignupUserctx type and
Here, We need three accounts and you already understand in the previous function all the attributes(like init, payer, space) so, I won't re-explain that here. But I will explain to you how to calculate the account space this time. To measure account space we need to take a look at what state the account is holding. In user_account case UserState has 3 values to store name, avatar and authority.
|State Values||Data Types||Size (in bytes)|
Pubkey: Pubkey is always 32 bytes and String is variable in size so it depends on your use case.
String: String is an array of chars and each char takes 4 bytes in rust.
Account Discriminator: All accounts created with Anchor needs 8 bytes
Moving forward, Let's complete the remaining signup function
Until now, We have created 2 function
signup_userwith name and avatar. Specifically,
signup_usertakes two arguments. What if a user mistakenly sent the wrong name and the user wants to update it?? You guessed it right. We will create a function that allows the user to update name and avatar of their account.
- mut: if we want to change/update account state/data we must specify the mut attribute
- has_one: has_one checks user_account.authority is equal to authority accounts key ie. owner of user_account is signer(caller) of update_user function
Our blog is initialized, a user is created, now what's remaining?? CRUD of the post. In the next section, We will look into the CRUD of the post entity. If you feel overwhelmed, take a break or go through what we have learned so far.
Now, Let's move to the CRUD of the post!! CRUD stands for Create Read Update Delete.
What do you think? Why do we need blog_account as mut here? Do you remember current_post_key field in BlogState. Let's look at the function body.
The post is created, Now we can let the client know that the post is created. The client can fetch the post and render it into the UI. Anchor provides a handy feature of emitting an event, Event?? Yup, you read it right. We can emit an event like post-created. Before emitting an event, We need to define it.
Let's emit a post created event from
Next, Update Post.
Updating post is really simple, take title and content from user and update the mut post_account
Delete post is little challenging. To store posts, We have used LinkedList. If you know LinkedList, After deleting a node from LinkedList, we need to link the adjacent node of deleting a node. Let's understand this through a diagram.
If we want to delete 2nd post, we have to link 1 -> 3.
Let's jump to the code, I know you will understand it easily.
constraint attribute performs simple if check.
So to delete a post, user needs to send post_account and next_post_account. But what if there is no next_post?? What if the user wants to delete the latest post that has no next post?? To handle this case, we need to create another function delete_latest_post
That was the last function of our Rust Program.
Next is the Testing program. Don't worry, we'll fast forward to the next section.
Before we dive into writing test cases, every test needs an initialized blog, a brand new user, and a post. To avoid repetition, we will create 3 simple reusable utility functions.
createBlog- Initialize new Blog account
createUser- Create a new User
createPost- Create new Post
Now we are ready to write our first test case. Open up the test file located in
/test/blog.jsdirectory. We will write all the test cases inside the
Next, run the test:
anchor testyou will see the 1/1 test passing.
Now we complete the remaining tests, we will write all the test cases below the previous test case inside
Now, We're ready to build out the front end. We will create a new react app inside the existing app directory.
The directory structure of a basic React app made with
Before we start writing the frontend part of the tutorial we will create a simple script that will copy the program idl file to the React app. Whenever we deploy our rust program with anchor deploy the Anchor CLI generates the idl file that has all the metadata related to our rust program (this metadata helps to build the client-side interface with the Rust program).
copy_idl.jsfile in the root of your project and copy the code given below. The code is just copying the idl file from
Next, install dependencies.
npm i @solana/wallet-adapter-react @solana/wallet-adapter-wallets @solana/web3.js
app/src/App.jsand update it with the following:
In the further tutorial, I'm just gonna explain the logical part of our dapp and I'll leave the styling part up to you.
Now, Let's start with the login functionality of our dapp. We will need a button that handles the user login with Phantom browser wallet.
create button inside Home.js component with
onConnectonClick handler like this,
onConnectfunction that handles the click event of connect button.
Let's deploy our Rust program first then copy the idl file with the help of the
copy_idl.jsscript that we wrote before,
Before deploy make sure you have
localnetcluster set in Anchor.toml file. Now open up a new Terminal session and run the
solana-test-validatorcommand. This will start a local network of the Solana blockchain.
then deploy the program with,
If you run into an error,
- Make sure
- Make sure your Solana config is in a valid state (I mean the RPC url, KeyPair path etc.)
Once you successfully deploy the Rust program with the anchor CLI then run the
It will copy the idl file to
/app/srcdirectory, now you will see the
Now we will initialize the blog. Initializing blog is one-time process. Let's create
init_blog.jsfile inside the
/app/srcfolder and copy the code given below.
initBlogfunction we have imported the program idl then we have generated two Keypair for blog account and initial dummy post account and after that we just call the initBlog function of the program with all the necessary accounts. we are creating two new accounts here
genesisPostAccountthat's why we have to pass it as signers.
Now the UI part of this, we will create a temporary button to call the
initBlogfunction. Once the blog is initialized we will remove the button, as it won't be needed anymore.
initBlogfunction will create a brand new Blog and console log its publicKey. Make sure you still have
solana-test-validatorrunning in another terminal and your Phantom wallet is connected to the local network (
If you don't know how to connect the Phantom wallet to Localnet here are the steps,
- Go to settings tab in Phantom wallet
- Scroll down and select change network
- Finally choose Localhost
Next, you need balance in your account on Localnet to get balance you need to run the following command.
Then check your account balance with the command:
You will see
1 SOLprinted on your terminal.
Once you are connected to the Localhost you are ready to initialize the blog account. Run the app with
npm run startit will open up your Dapp in your browser there you will see two buttons one is
connectand another one is
init blog. The first step is to connect to the Phantom wallet by clicking
connectbutton. Once you are connected then click on the
init blogbutton. it will trigger the Phantom wallet confirmation popup click
Approve. After 1 to 2 sec the
initBlogfunction will console log the publicKey of your blog account, just copy the blog publicKey and store it in
BLOG_KEYvariable and that's it. We have successfully initialized our blog account.
Your blog is initialized now you can comment out the
init blogbutton as well the
Now we will move to the part where the user enters his/her name and avatar URL and we will initialize his/her account.
Let's create two input fields one for user name and another for user avatar.
Next attach React state to the input fields and create
Now we will write the functionality of the
_signupfunction, If you remember the
initBlogfunction, there we have randomly generated Keypair but in this case, we won't generate the Keypair randomly as we need to identify the user for all the subsequent logins.
fromSeedfunction that takes a
seedargument and generates a unique Keypair from the given seed.
So we will create a seed by combining
wallet_keyso it will create a unique Keypair.
Let's complete the
Now we have created user account next we will fetch the user account to see whether user has already signed up.
First we will create a form to take post title and post content from user input.
Now we can complete the
here we have passed the
BLOG_KEYthe same key that we have obtained by initializing the blog.
Now we will fetch all the posts created on our Blog. If you remember the Rust program, we have attached the previous post id to the current post, here we will use the previous post id to iterate over the list of posts. To do so, we will create a function that finds the post of a given post id.
To loop over all the posts we need latest post id and we can find the latest post id from the blog state. create
Now trigger the fetchAllPosts function when the user login with the Phantom wallet.
Show posts in the UI
Till now, we have integrated initialization of blog, user signup, fetch user, create post, fetch all the posts there still some part is remaining like,
- update user
- update post
- delete post
If you follow the same steps as we have learned, you can be able to complete the remaining to-do tasks.
Deploying to a live network is straightforward:
- Set Solana config to devnet
- Open Anchor.toml and Update the cluster to devnet
- Build program
- Deploy program
Congratulations on finishing the tutorial. In this tutorial, We have successfully created a Rust program for Blog post Dapp on Solana Blockchain. We have also integrated the Blog post Rust program with React.js on the client side. Here, we followed a little different approach to storing and retrieving posts. The Solana blockchain is still in its beta phase so, don't bother to try experimenting. Who knows, you might find some cool patterns out of it.
There is still room for improvements in the Blog Dapp like,
- UI improvements
- Creating frontend app with Typescript
- Adding pagination while fetching all the posts
- Creating user timeline(posts created by the user)
- Adding Rxjs to fetch posts(streaming posts)
This tutorial was created by Kiran Bhalerao. For any suggestions/questions, you can connect with author on Figment Forum.