[Substrate Kitties Course 😸] Finalize the updated version of Substrate Kitties Course #12
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Substrate Kitties by TheLowLevelers
The original version from Substrate Developer Hub is here, please give it a few credits to the team behinds it: https://www.shawntabrizi.com/substrate-collectables-workshop/#/
🖐️ What is this version made by TheLowLevelers?
The original version is outdated.
substrate-node-template
no longer has a concept of runtime modules butpallet
. Hence, it is not likable to us the outdated material to learn about Substrate.What is this?
This is an interactive hands-on self-paced workshop. You will learn how to build your first blockchain using Substrate, the OpenSource Rust Blockchain Development Kit by Parity. Through the lessons of the workshop, you will build a collectables blockchain -- a chain that creates assets, and allows you to interact with and managing ownership of them.
As such, this material will focus on building the logic of this chain. It won't cover the networking, consensus or economic incentive aspects of blockchains. Fortunately, Substrate comes with decent networking and consensus engines built in, so we can just focus on the chain logic.
Substrate is built using Rust, a modern statically typed systems programming language. We won't go into the details of the language within this workshop. The language is quite easy to read and follow and if you have programmed before, you shouldn't have too much trouble following what is going on and finishing the exercises even if Rust is new to you.
Tutorial Steps
Business logic
We are going to build a simple NFT marketplace for
Substrate Kitties
(Please take a look at CryptoZombies or CryptoKitties on Ethereum blockchain to get an idea of what is Substrate Kitties) that allows users to:mint
: Mint a new NFT item (we call it a Kitty)transfer
: Transfer a new NFT item from the sender to a destination account.list_nft
: List the NFT on the marketplace so other users can buybuy_nft
: User can buy NFT on the marketplace from other users if the NFT is listedPrerequisites
This requires you to finish a first few tutorials of Substrate development from the official documentation. If you have not walked through those first. Please take a look at these first before diving deeper into this interactive tutorial:
Step 0: Setup your local environment
If your hardware is a modern M1 Apple sillicon chip, working with Substrate can be very painful because there is many unstable compilation issue happens during your development. To avoid this, please install Rust toolchain following these versions below.
Step 1: Clone repository + Setup code template on your local
There are multiple version for this awesome Substrate Kitties tutorial. However, based on my experience, those are outdated and it takes you a lot of time to set up a right dependecy version to work on.
So please checkout
1-setup
to get well-tested code template for this tutorial.After checking the branch, please run below command to test if you can run a node from your local environment.
Let's break down the given template code and what you need to work on:
The full flow for Substrate development will be
Pallet > Runtime > Frontend
substrate-kitties
and checkout branch1-setup
to setup the template code on the localStep 2: Learn about Pallet storage and write basic data structures
Reading Materials
I would recommend you to read these materials below first before looking at the code implmentation of the data structures. These materials below cover very well the concepts of FRAME storage in Substrate development.
Data structures to work with Storage API
The FRAME Storage module simplifies access to these layered storage abstractions. You can use the FRAME storage data structures to read or write any value that can be encoded by the SCALE codec. The storage module provides the following types of storage structures:
Struct data for Kitty
The blow type alias
BalanceOf
llows easy access our Pallet'sBalance
type. Comes fromCurrency
interface.Struct for holding kitty information. You may notice a few macros used for the below struct like
Encode
,Decode
,TypeInfo
,MaxEncodedLen
. Let's break down the use of these macros.Encode
,Decode
: Macros inparity-scale-codec
which allows the struct to be serialized to and deserialized from binary format with SCALE.MaxEncodedLen
: By default the macro will try to bound the types needed to implementMaxEncodedLen
, but the bounds can be specified manually with the top level attribute.TypeInfo
: Basically, Rust macros are not that intelligent. In the case of the TypeInfo derive macro, we parse the underlying object, and try to turn it into some JSON expressed type which can be put in the metadata and used by front-ends. (Read more Substrate Stack Exchange -What is the role of
#[scale_info(skip_type_params(T))]
?)The Rust macros for automatically deriving MaxEncodedLen naively thinks that T must also be bounded by MaxEncodedLen, even though T itself is not being used in the actual types. (Read more)
Another way to do this without macros like
TypeInfo
and#[scale_info(skip_type_params(T))]
is to pass in the generic type forT::AccountId
andT::Hash
directly instead of pointing them from the genenricT
type (which does not implementMaxEncodedLen
).In 4-onchain-randomness we will cover the Onchain Randomness topic which is used to generate the DNA for the Kitty. Then this DNA is used to generate the gender for the Kitty as well.
One last thing about type object, you may notice there is
MaxKittiesOwned
type declared in theConfig
trait of thePallet
. The purpose of this type is to tell the Runtime which bounded value that can be passed in from the Runtime (Learn more from Substrate Docs - Configure Runtime Constants).Let's implement the storage variables for Substrate Kitties
In the context of Substrate Kitties, we will need data structures that can provide:
Collection of Kitties
: We want a data structure that can helps to get the Kitty complete data by DNA whenever we need. Hence,StorageMap
is a suitable data structure for this. We don't want to useStorageValue
withVec
because this is expensive when we want to access the Kitty.Relationship between Kitty and its Owner
: This is aone-to-one
relationship that helps to identify who owns that Kitty. In this case, we needO(1)
data structure that can help to traverse the relationship betweenOwner
andKitty
quickly. Hence, we can useStorageMap
.Relationship between Owner and their Kitties
: This is aone-to-many
relationship that helps to identify Kitties owned by an Owner. In this case, we needO(1)
data structure that can help to traverse the relationship betweenOwner
and a list ofKitty
quickly. Hence, we can useStorageMap
withBoundedVec
to store list of Kitty DNAs. Remember that any computation and memory space costs money, so we should useBounded
storage structure for memory efficiency.Total number of Kitties minted
: We want to track the number of Kitties minted through our blockchain.Step 3: Learn about dispatchable functions, event and write a method to mint a new kitty
Dispatchable functions
When users interact with a blockchain they call dispatchable functions to do something. Because those functions are called from the outside of the blockchain interface, in Polkadot's terms any action that involves a dispatchable function is an Extrinsic.
A function signature of a dispatchable function declared in the Pallet code must return a
DispatchResult
and accept a first parameter is an origin typedOriginFor<T>
.Events & Errors
Events and errors are used to notify about specific activity. Please use this for debugging purpose only. Events and Errors should not be used as a communication method between functionalities.
In our codebase, we will declare these errors and events. The syntax is basically Rust code but with macro
#[pallet::error]
And comment out the
Created
event so that we can deposit an event on new kitty minted.To dispatch an event, we do
Write a method to mint a new kitty
Step 4: Learn about onchain randomness and how to generate a random DNA for the Kitty
Onchain randomness is quite important for a Turing complete applications. There are many use cases involved the randomness like gambling, probabilistic computation or random factors in gaming. But in blockchain, state in the state machine must be deterministic so we don't have a real randomness but
pseudo randomness
.To add randomness feature to our Kitty DNA generation logic, we will use
pallet_insecure_randomness_collective_flip
pallet. This Pallet is not supposed to be used onproduction
so please be aware of it.Logic behinds
pallet_insecure_randomnes_collective_flip
The logic behinds this Pallet crate is simple. It has an offchain worker running to store a block hash to the onchain storage when block is initialized.
After 81 block hashes, it takes all thoses and generate a random value based on it. In the Pallet source code, you can see it defines this constant value for the number of random materials.
Take a look at the seed generation code
As you see, it is quite straightforward and simple to understand. This randomness can be predicted in advanced but still random enough to not be predicted.
Generate random DNA for the Kitty
Ok so now how can we implement this randomness feature for our Kitty DNA generation code?
We will add the below
KittyRandomness
type metadata into theConfig
trait of the pallet. Hence, on theRuntime
side, we can add a Pallet that matches the type.On
Runtime
side, we add thepallet_insecure_randomness_collective_flip
crate to the dependency list.With this, we can define the Pallet on the
Runtime
side. Insideconstruct_runtime!
macro, we addRandomnessCollectiveFlip
.We need to make changes to the Pallet Kitties config code. It simply plugs and play.
To check if Runtime code is functional, please run
cargo check
.Now we are ready to generate DNA for our Kitty
Step 5: Interact with the Substrate Node from the frontend.
That's enough for the Substrate part, now we have an API for
create_kitty
ready, let's implement a frontend to interact with the logic defined in our Pallet code. Before starting the frontend, please follow these steps:There are many abstractions on the frontend side as it uses multiple open-source libraries like
@polkadot/api
. I will keep it in a decent level of abstraction so you can still understand how does it work.Let's break down API call to the Substrate Node. On the frontend code, you can log out the methods by doing
Simply speaking, this API call abstract a way RPC call to the storage getter.
How about dispatching an extrinsic call to the Subtrate node backend? Please a full view of the
TxButton.js
file to get the overall idea.This is what you actually see in that code file, in
Kitties.js
What it actually looks like is
Reflect this with what we did on the Substrate backend
So it would be
Kitties.create_kitty
which is converted to the frontend code after deserializing isapi.tx.kitties.createKitty
.That's it, it is quite simple about how to interact with your Pallet code right? Polkadot team definitely has many good engineers. There are a few other things like "How to get account in the network and its balance?"
It simply get the data from the
System
pallet about the accounts on the existing blockchain and their balances.I guess that's enough for the overall idea of the frontend code. After retrieving the data from the Substrate node, the part of visualizing it is your thing.
Also, I might miss this. If you are interested into how the unique Kitty image is generated, please take a look at
KittyAvatar.js
.Step 6: Full code implementation
Now follow what you learnt, implement the logic for these methods below. Play with it both on Runtime and Frontend so you can understand the flow of Pallet development fully.
How to contribute
Before committing to the tasks in the community, please skim through the guidelines below to grasp the overall idea of how the community works first. It does not take long but I believe it will give you a big picture of the vision and culture of TheLowLevelers.
Acknowledgements
Open source projects like Substrate and this workshop could not be successful without the collective minds and collaborative effort of the development community.
The Substratekitties workshop stands on the backs of giants like Cryptokitties, Cryptozombies, Docsify, Monaco Editor, David Revoy's Cat Avatar Generator, and numerous volunteers to report errors and bugs along the way.
We hope this educational material teaches you something new, and in turn, you teach others too.