Note - this is part of a wider micro-course that I released. However, I have condensed it as much as I can for the purposes of this special Hackernoon release. I cut out some detail - if you wish to read it, be sure to follow my Twitter and dm me!
Today, I’ll show you exactly how to create an "About Me“ blockchain module based on Substrate. It will allow any user to assign information about themselves on-chain.
That’s right, we’re no longer transacting money - but creating value in an entirely different way.
Essentially, you will be setting up and developing your very own blockchain - all tools and instructions provided below by yours truly.
Without further ado, let's get into it!
Intro to Substrate
To start off — keep in mind you should have some experience with development and/or software development. By no means do you need to be an expert, but you should have the following before starting:
- An interactive shell of some kind - Linux, macOS, or Windows.
- Basic familiarity with using IDE and CLI tools.
- Experience with software development or coding.
- Willingness to learn and mess up - even when it goes terribly wrong.
What is blockchain?
To quote the Substrate Documentation,
A blockchain is a decentralized ledger that records information in a sequence of blocks. The information contained in a block is an ordered set of instructions that might result in a change in state.
In other words, a blockchain creates a linked collection of records. Normally, we’re used to seeing this implemented to keep track of monetary transactions.
However, this is no longer the case. Funnily enough, just as we can verify monetary data/balances, we can apply those same concepts to almost any type of data we want.
Blockchains are essentially state management machines. In other words, they keep track of who changed the state of the chain, when, and whether it was valid or not. When someone wishes to change the state, they submit a request, often referred to as a transaction, that changes the state of the chain — only if it's approved!
What is Substrate? Why use it?
Developing on the blockchain is difficult. Blockchain development either requires you to dedicate time to learning a specific protocol to build on, or build an entirely new chain from scratch.
Substrate alleviates this by providing a framework that makes it way easier to build your own blockchain from scratch.
They take care of the hard parts so you can focus on the fun part — the applications.
From an architectural standpoint, Substrate chains are also interoperable. It’s possible to perform cross-chain operations quite easily (see how Polkadot works for more).
The way it’s built also allows for it to be very future-proof —which in this ever-evolving industry, is extremely important.
For example, if you need to communicate with Ethereum, but also need to leverage your own custom on-chain functionality, you can build that on Substrate.
🦀 Installing Rust
Since we’re going to be using Rust to develop our custom pallet, make sure you have the Rust toolchain installed. If you need a guide for installing Rust, visit here.
🐥 Installing the Frontend Template
The front-end template is a valuable source that will allow us to quickly verify and test our Substrate chain with minimal interference.
# Clone the repository
git clone https://github.com/substrate-developer-hub/substrate-front-end-template.git
cd substrate-front-end-template
yarn install
# To start it
yarn start
More info can be found at the repo link.
💥 The Polkadot.js Explorer
The explorer is also a great way to get more insight into your node’s operations. Go to:
Polkadot/Substrate Portal
Once there, you can click the upper left, and make sure you have Local Node selected.
🌀 Cloning & Building The Substrate Node Template
You should already have the Rust toolset installed from the previous step. If not, go ahead and follow those then come back here. Keep in mind this may take a bit to build and run, so feel free to get a cup of coffee or read an awesome Medium article while you wait.
Keep in mind, this repo is a clone of the original substrate-node template, but with a few tweaks to make it easier to work with when we start our pallet development.
git clone https://github.com/CrackTheCode016/substrate-node-template-course.git
cd substrate-node-template-course/
# this will build and launch the node
# if you wish to just build it, then run cargo build --release
cargo run --release -- --dev
Once it’s built, we have multiple ways of interacting and running our node.
🎑 Running & Viewing our Development Chain
For immediate results to ensure our chain is up, go ahead and visit the Polkadot.js Explorer:
Polkadot/Substrate Portal.
This link will automatically connect to your localhost
node, where you can view all sorts of chain stats. For example, like accounts and their balances! You can view blocks and events on-chain too, which will come in handy later. Have a good look around!
Another cool thing we can do is actively observe state changes live via examining the node’s storage instance under Developer > Chain State
. Here, you can get the state of various storage mappings or values that were previously defined by the pallets in the runtime. These are called State Queries.
For example, you can select the timestamp state query and click the plus button on the far right to get the time for the node:
You can also simply search storage by raw hexadecimal key, however, most of the time it’s easier to perform state queries via the respective pallet.
⛄ Using the substrate-frontend-template
While the explorer is a good place for general functions, let's see what we can do with the substrate-node-template
that we installed earlier.
Navigate to where you installed it, and run yarn start
. Once it’s launched my should see something like this:
Voila! You now have most functionality and access to your chain through a GUI. You can use the transfer
pallet to transfer currency between accounts, upgrade your runtime via a forkless upgrade, and interact with pallets to modify the state directly.
Feel free to play around here, and experiment as much as possible with this interface. You’ll learn a lot just by doing that. If you notice in the dropdown — one of the pallets is called templateModule
.
In the next section, we’ll be modifying and going through this pallet to make it our very own.
If you thought we got technical in the last sections, here’s where it’s REALLY about to go down.
Creating our first pallet — “About Me”
This pallet will be pretty simple. It will allow for any user to assign information about themselves, or “About Me” publicly on the blockchain, similar to something like Discord.
Without further ado, let’s speed-run creating the best blockchain module of all time.
1. Open up substrate-node-template
Lucky for us, the substrate-node-template
gives us a template pallet to work with directly.
If you haven’t already, make sure the node template is cloned and built just as we discussed in:
Installing & Using The Substrate Node Template
Navigate into your node’s included pallet template, and open the directory in your favorite code editor:
# Open this in your coding editor.
cd substrate-node-template/pallets/template
Once you’re in, you’ll notice it looks like your standard Rust Cargo crate — and that’s because it is. Pallets operate as Rust crates and can be shared and distributed as such.
Once you’re in, you should see a structure just like this:
.
├── Cargo.toml
├── README.md
└── src
├── benchmarking.rs
├── lib.rs
├── mock.rs
└── tests.rs
1 directory, 6 files
By default, you’ll notice we already have example code and tests. benchmarking.rs
deals with measuring the performance of your functions in the context of running on-chain. This is to help ensure you’re not going to stop the chain with functions that use too many resources.
2. Add our custom structs
For our pallet, we’ll be essentially associating some user information with a particular user. Users here are usually defined as and are crypto addresses. It’s pretty hard for our human brains to remember a 64-character string of characters, so let's fix that by giving it a human-readable struct!
Go to lib.rs
, and there you’ll see the heart of your pallet. This is where we’re going to put our business logic, errors, and events. You’ll notice the Config
trait —this is how we’ll implement our pallet in the runtime later on.
/// Configure the pallet by specifying the parameters and types on which it depends.
#[pallet::config]
pub trait Config: frame_system::Config {
/// Because this pallet emits events, it depends on the runtime's definition of an event.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
}
Under this trait, let's define a struct for how a user should look (UserInfo
):
#[derive(Encode, Decode, Clone, PartialEq)]
pub struct UserInfo {
/// Username, stored as an array of bytes.
pub username: Vec<u8>,
/// Number id of the user.
pub id: i64,
/// The "About Me" section of the user.
pub about_me: Vec<u8>,
}
Something important to note is the use of Vec<u8>
— this is essentially how we tell the Substrate runtime that it’s a string of characters that we’re expecting. Normally, we’d make sure this bounded has a limit, which we’ll do later.
We also implement the struct with several trait macros (Encode, Decode, Clone, PartialEq, Default, TypeInfo
) to make it easy to deal with in our dispatch function later on.
3. Creating our StorageMap
With our custom data structure defined, we can now start adding the storage implementation for how we tell Substrate to store our custom struct in association with users.
The substrate has a few methods for doing this. For this course, however, we’ll be using a StorageMap
. It fits our use case perfectly and is simple to use.
Right under our struct, go ahead and define our StorageMap
:
/// Mapping of account ids to UserInfo.
#[pallet::storage]
#[pallet::getter(fn info)]
pub type AccountIdToUserInfo<T: Config> =
StorageMap<_, Blake2_128Concat, T::AccountId, UserInfo, ValueQuery>;
Just like that, we defined a storage mapping for our users. When new user registers their info, they will be added directly to the blockchain’s storage via this mapping.
The [pallet::getter(fn info)]
macro ensures a getter method, meaning we can fetch the stored later info simply by knowing the address of the user (AccountId
).
4. Defining Events & Errors
Errors are pretty self-explanatory — when something goes catastrophically wrong, we can return an informative error to let the developer know how they messed up when using our pallet.
Events in Substrate are pretty similar to events in Solidity.
They essentially are notifications that indicate when a specific action has been completed. This is super useful for frontend apps — for example, you can show an in-app notification once a transaction has been confirmed, for example.
Let’s go ahead and add an event under the included enum Event
for when a user registers:
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// Indicates a user has been registered.
UserCreated { user: T::AccountId },
}
We’ll revisit errors after the next section, as we’ll be modifying our pallet config directly. Go
ahead and add this under your enum Error
:
// Errors inform users that something went wrong.
#[pallet::error]
pub enum Error<T> {
AboutMeTooLong,
}
4. Creating our dispatch function
Finally, the last step. Now that we have all of our data / storage structures, events, and errors initialized, we can combine them into one big, happy dispatch function!
As the original substrate-node-template
states, dispatchable functions are:
Dispatchable functions allows users to interact with the pallet and invoke state changes. These functions materialize as "extrinsics", which are often compared to transactions. Dispatchable functions must be annotated with a weight and must return a DispatchResult.
Something important to notice is how we have a new word — extrinsics. Remember when we said the blockchain was a state management machine, and we sent transactions to change the state?
Extrinsics are just that — changes to the blockchain’s state. They are the same concept as transactions, but we can customize them to include anything we like.
For our usecase, we want to update the state of the chain to change the state of a particular user’s info.
#[pallet::call]
impl<T: Config> Pallet<T> {
// Dispatchable calls go here!
// Register a new user and change the state of the chain.
#[pallet::weight(10_000 + T::DbWeight::get().writes(1).ref_time())]
pub fn register_user(
origin: OriginFor<T>,
username: Vec<u8>,
id: i64,
about_me: Vec<u8>,
) -> DispatchResult {
// Gets the caller or signer of the function.
let sender = ensure_signed(origin)?;
// Define a new user in accordance to UserInfo.
let new_user = UserInfo { username, id, about_me };
// Change the state of our storage mapping by adding user info to our sender AccountId.
<AccountIdToUserInfo<T>>::insert(&sender, new_user);
// Emit an event indicating the user is now created and registered.
Self::deposit_event(Event::<T>::UserCreated { user: sender });
Ok(())
}
}
And just like that, we completed most of the work needed to get our pallet functioning. Next up, let’s take a quick peek into the runtime to see how our pallet is configured and actually added to the blockchain instance.
5. A peek into the runtime — adding & modifying the config
We’ve pretty much completed all we need to for our custom pallet. However, it’s important to realize just how it gets used in the runtime.
Go ahead and navigate to your runtime/src/lib.rs
within the node template and open it in your code editor. If you scroll, you’re going to see it imported as a normal Rust crate the same as any other:
/// Import the template pallet.
pub use pallet_template;
If you remember the Config trait from the pallet — this is where you implement it. Any custom variables or configuration options are initialized in the runtime:
/// Configure the pallet-template in pallets/template.
impl pallet_template::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
}
And finally, if you have a peek into the construct_runtime!
macro, you’ll see a number of pallets included our custom pallet:
// Create the runtime by composing the FRAME pallets that were previously configured.
construct_runtime!(
pub struct Runtime
where
Block = Block,
NodeBlock = opaque::Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{
...
// Include the custom logic from the pallet-template in the runtime.
TemplateModule: pallet_template,
}
);
aaaanddd that’s wraps! Now, lets build and test out the thing!
🚚 Build & Run Locally
Now, it’s finally time to build and run our Substrate node with our custom module configured.
Navigate to substrate-node-template-course
and run the following to build & run the node:
cd substrate-node-template-course/
cargo run --release -- --dev
This might take a while, so sit back, relax, and watch your node build itself up.
🚢 Testing in the frontend
Once your node is built and running, you should see output similar to this:
If you see that, congrats, you have your very own custom blockchain working!
Next up, let’s start up the substrate-front-end
template from earlier:
cd substrate-frontend-template/
yarn start
Fire it up in your web browser, and scroll down to the Pallet Interactor. Click the dropdown menu and you should see templateModule with our register_user
dispatchable function that we defined earlier.
Bytes
is essentially just a string, so we can fill it out accordingly. Keep in mind the three options:
There are three different types of transactions, or extrinsics, we can do. Unsigned don’t require a signature, and thus are more dangerous for a network. The one in our dispatchable is signed, though.
Fill it out, hit signed, and you should be presented with the following. Notice our event on the right hand side, UserCreated
, with the address of the user that we just assigned an identity to!
Copy that address — because now we’re going to query it to really make sure it went through. Click query in Interaction Type, then select templateModule
again, and accountIdToUserInfo
. Paste the address in the field, and hit Query:
Although the values are in hexadecimal, we can clearly see that we indeed have a user profile on chain!
Congratulations! You just made history by creating a really cool, super simple user profile system completely using web3 tech.
#blockchain #bitcoin #cryptocurrency #crypto #ethereum #btc #forex #bitcoinmining #trading #money #cryptocurrencies #eth #bitcoinnews #bitcoins #investment #business #cryptonews #cryptotrading #coinbase #invest #investing #blockchaintechnology #entrepreneur #binance #nft #litecoin #forextrader #trader #bitcointrading #bitcoincash