Building A Blockchain Social Media Module Using Substrate - WEB3 101

Written by anormaljourney | Published Invalid Date
Tech Story Tags: blockchain | web3 | substrate | blockchain-development | smart-contracts | programming | rust | defi

TLDRToday, I’ll show you exactly how to create an "About Me“ blockchain module based off of Substrate. It will allow for any user to assign information about themselves on-chain.via the TL;DR App

Note - this is part of a wider microcourse 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 off of Substrate. It will allow for 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, lets 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 an 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 being implemented for the purposes of keeping 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 to.

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 its approved!

What is Substrate? Why use it?


Developing on 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 architectural standpoint, Substrate chains are also interoperable. It’s possible to perform crosschain 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 from the original substrate-node-template, but with a few tweaks to make it easier to work with for 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, lets see what we can do with the substrate-node-templatethat 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 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-templategives us a template pallet within 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 to a particular user. Users here are usually defined as AccountId, and are crypto addresses. It’s pretty hard for our human brains to remember a 64 character string of characters, so lets 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, lets 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 to have 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.

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 a 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.


Written by anormaljourney | full stack #web3, software, dlt dev chronic cat and coffee enjoyer i make content and videos on web3
Published by HackerNoon on Invalid Date