Add the Nicks Pallet to your Runtime
As you saw in the Create your first Substrate blockchain tutorial, the Substrate node template provides a working runtime that you can use to start building your own blockchain. The node template includes a runtime that is constructed using FRAME development framework and a few modules—called pallets—to get you started. In this tutorial, you'll learn how to add the Nicks pallet to the node template. You can follow similar patterns to add other FRAME pallets to your runtime. However, you need to configure each pallet with the specific settings it requires to use it. This tutorial illustrates how to configure the settings each pallet requires but not the specific settings that apply to each pallet you decide to add to your runtime.
Before you begin
Before we even get started, let's layout our objectives:
Add the Nicks pallet to your node template
Implement the Nicks pallet configuration trait.
Use the front-end template to interact with the Nicks pallet.
Learning outcomes
- Learn how to integrate a FRAME pallet to your node and runtime
- Familiarize yourself with customizing a pallet's configuration trait
- Learn how to publish a custom pallet to crates.io
Add the Nicks pallet to the node template
The Nicks pallet allows blockchain users to pay a deposit to reserve a nickname and associate it with an account they control.
To add the Nicks pallet to the Substrate node template:
Open a terminal shell and change to the root directory for the node template.
Open the
runtime/Cargo.toml
configuration file in a text editor.Import the
pallet-nicks
crate to make it available to the node template runtime by adding it to the list of dependencies.pallet-nicks = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.17" }
In this dependencies
section, thedefault-features = false
setting specifies that the pallet features are not used by default when compiling the runtime.
Instead, the features that are compiled for the runtime are controlled by specifying which features to include when compiling the native Rust binary listed in a separate features
section.
Because the Substrate runtime compiles to both a native Rust binary with standard library functions std
and a WebAssembly (Wasm) binary that does not include the standard library, the pallet features that get compiled depend on whether you are compiling the Rust binary (std
features) or the WebAssembly binary (no-std
). For more information about compiling std
and no_std
features, see the Rust Embedded Book.
The remaining lines specify that Cargo should use the pallet-nicks
crate from the paritytech/substrate
repository with the correct matching version of substrate all other crates are using.
For an introduction to editing Cargo.toml
files, see the Cargo reference documentation.
Add the
pallet-nicks
crate to the list offeatures
to be included in thestd
feature set by adding the following linefeatures
section.[features] default = ['std'] std = [ ... 'pallet-aura/std', 'pallet-balances/std', 'pallet-nicks/std', ... ]
If you forget to update the
features
section, you might see errors similar to the following when you build the native binaries:error[E0425]: cannot find function `memory_teardown` in module `sandbox` --> ~/.cargo/git/checkouts/substrate-7e08433d4c370a21/83a6f1a/primitives/sandbox/src/../without_std.rs:53:12 | 53 | sandbox::memory_teardown(self.memory_idx); | ^^^^^^^^^^^^^^^ not found in `sandbox` error[E0425]: cannot find function `memory_new` in module `sandbox` --> ~/.cargo/git/checkouts/substrate-7e08433d4c370a21/83a6f1a/primitives/sandbox/src/../without_std.rs:72:18 | 72 | match sandbox::memory_new(initial, maximum) { | ...
Check that the new dependencies resolve correctly by running the following command:
cargo check -p node-template-runtime
Review the configuration trait for the Nicks pallet
Every pallet has a Rust trait called Config
that is used to configure the parameters and types that the pallet needs from the runtime.
If you review the comments for the runtime in the node template, you will see examples of using the Config
configuration trait to define and tune parameters for each pallet.
Most of the pallet-specific code required to add a pallet is implemented using the Config
trait.
To see what you need to implement for the Nicks pallet specifically, see the pallet_nicks::Config
documentation or the definition of the trait itself in the source code of the Nicks pallet.
In the source code for the nicks
pallet, the Config
trait declares the following types:
pub trait Config: frame_system::Config {
/// The overarching event type.
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
/// The currency trait.
type Currency: ReservableCurrency<Self::AccountId>;
/// Reservation fee.
#[pallet::constant]
type ReservationFee: Get<BalanceOf<Self>>;
/// What to do with slashed funds.
type Slashed: OnUnbalanced<NegativeImbalanceOf<Self>>;
/// The origin which may forcibly set or remove a name. Root can always do this.
type ForceOrigin: EnsureOrigin<Self::Origin>;
/// The minimum length a name may be.
#[pallet::constant]
type MinLength: Get<u32>;
/// The maximum length a name may be.
#[pallet::constant]
type MaxLength: Get<u32>;
}
Review the implementation of the Balances pallet
Just like you used the Balances pallet as a template for importing the Nicks pallet, you can use the Balances pallet as an example to see how to implement the Config
interface for the Nicks pallet.
Open the
runtime/src/lib.rs
file in a text editor.Locate the Balances pallet section and note that the implementation consists of two parts:
The
parameter_types!
block where constant values are defined.parameter_types! { // The u128 constant value 500 is aliased to a type named ExistentialDeposit. pub const ExistentialDeposit: u128 = 500; // A heuristic that is used for weight estimation. pub const MaxLocks: u32 = 50; }
The
impl
block where the types and values defined by theConfig
interface are configured.impl pallet_balances::Config for Runtime { // The previously defined parameter_type is used as a configuration parameter. type MaxLocks = MaxLocks; // The "Balance" that appears after the equal sign is an alias for the u128 type. type Balance = Balance; // The empty value, (), is used to specify a no-op callback function. type DustRemoval = (); // The previously defined parameter_type is used as a configuration parameter. type ExistentialDeposit = ExistentialDeposit; // The FRAME runtime system is used to track the accounts that hold balances. type AccountStore = System; // Weight information is supplied to the Balances pallet by the node template runtime. // type WeightInfo = (); // old way type WeightInfo = pallet_balances::weights::SubstrateWeight<Runtime>; // The ubiquitous event type. type Event = Event; }
The impl pallet_balances::Config
block allows you to configure the types and parameters that are specified by the Balances pallet Config
trait.
For example, this impl
block configures the Balances pallet to use the u128
type to track balances.
If you were developing a chain where it was important to optimize storage, you could use any unsigned integer type that was at least 32-bits in size because the Balance
type for the Balances pallet Config
trait is bound by the AtLeast32BitUnsigned
trait.
Implement the Nicks pallet Config
trait
Now that you have seen an example of how the Config
trait is implemented for the Balances pallet, you're ready to implement the Config
trait for the Nicks pallet.
To implement the nicks pallet in your runtime:
Open the
runtime/src/lib.rs
file in a text editor.After the last line of the Balances code block, add the following code block for the Nicks pallet:
/// Add this code block to your template for Nicks: parameter_types! { // Choose a fee that incentivizes desireable behavior. pub const NickReservationFee: u128 = 100; pub const MinNickLength: u32 = 8; // Maximum bounds on storage are important to secure your chain. pub const MaxNickLength: u32 = 32; } impl pallet_nicks::Config for Runtime { // The Balances pallet implements the ReservableCurrency trait. // `Balances` is defined in `construct_runtime!` macro. See below. // https://docs.substrate.io/rustdocs/latest/pallet_balances/index.html#implementations-2 type Currency = Balances; // Use the NickReservationFee from the parameter_types block. type ReservationFee = NickReservationFee; // No action is taken when deposits are forfeited. type Slashed = (); // Configure the FRAME System Root origin as the Nick pallet admin. // https://docs.substrate.io/rustdocs/latest/frame_system/enum.RawOrigin.html#variant.Root type ForceOrigin = frame_system::EnsureRoot<AccountId>; // Use the MinNickLength from the parameter_types block. type MinLength = MinNickLength; // Use the MaxNickLength from the parameter_types block. type MaxLength = MaxNickLength; // The ubiquitous event type. type Event = Event; }
Identify the types that the Nicks pallet exposes.
You can find a complete list of types in the
construct_runtime!
macro documentation.The Nicks pallet uses the following types:
- Pallet Storage: Because it uses the
#[pallet::storage]
macro. - Pallet Events: Because it uses the
#[pallet::events]
macro. You will notice that in the case of the Nicks pallet, theEvent
keyword is parameterized with respect to a type,T
; this is because at least one of the events defined by the Nicks pallet depends on a type that is configured with theConfig
configuration trait. - Callable Functions: Because it has dispatchable functions in the
#[pallet::call]
macro. - The Pallet type from the
#[pallet::pallet]
macro.
- Pallet Storage: Because it uses the
Add Nicks to the
construct_runtime!
macro.For example:
construct_runtime!( pub enum Runtime where Block = Block, NodeBlock = opaque::Block, UncheckedExtrinsic = UncheckedExtrinsic { /* --snip-- */ Balances: pallet_balances, /*** Add This Line ***/ Nicks: pallet_nicks, } );
Check that the new dependencies resolve correctly by running the following command:
cargo check -p node-template-runtime
If there are no errors, you are ready to compile.
Compile the node in release mode by running the following command:
cargo build --release
Start the blockchain node and use the Nicks pallet
After your node compiles, you are ready to start the node that has been enhanced with nickname capabilities from the Nicks pallet and interact with it using the front-end template.
Start the local Substrate node
To start the local Substrate node:
Open a terminal shell, if necessary.
Change to the root directory of the Substrate node template.
Start the node in development mode by running the following command:
./target/release/node-template --dev
The node-template command-line options specify how you want the running node to operate. In this case, the
--dev
option specifies that the node runs in developer mode using the predefineddevelopment
chain specification. By default, this option also deletes all active data—such as keys, the blockchain database, and networking information when you stop the node by pressing Control-c. Using the--dev
option ensures that you have a clean working state any time you stop and restart the node.Verify your node is up and running successfully by reviewing the output displayed in the terminal.
If the number after
finalized
is increasing in the console output, your blockchain is producing new blocks and reaching consensus about the state they describe.Keep the terminal that displays the node output open to continue.
Start the front-end template
Now that you have added a new pallet to your runtime, you can use the Substrate front-end template to interact with the node template and access the Nicks pallet.
To start the front-end template:
Open a new terminal shell on your computer.
In the new terminal, change to the root directory where you installed the front-end template.
Start the web server for the front-end template by running the following command:
yarn start
Open http://localhost:8000/ in a browser to view the front-end template.
Set a nickname using the Nicks pallet
After you start the front-end template, you can use it to interact with the Nicks pallet you just added to the runtime.
To set a nickname for an account:
Check the account selection list to verify that the Alice account is currently selected.
In the Pallet Interactor component, verify that Extrinsic is selected.
Select
nicks
from the list of pallets available to call.Select the
setName
dispatchable as the function to call from thenicks
pallet.Type a name that is longer than the
MinNickLength
(8 characters)and no longer than theMaxNickLength
(32 characters).Click
Signed
to execute the function.Observe the status of the call and the events emitted by the Nicks pallet.
Query information for an account using the Nicks pallet
Next, you can use Query capability to read the value of Alice's nickname from the runtime storage for the Nicks pallet.
To return the information stored for Alice:
In the Pallet Interactor component, select Query.
Select
nicks
from the list of pallets available to query.Select the
nameOf
.Copy and paste the address for the alice account in the Account field, then click Query.
The return type is a tuple that contains two values:
The hex-encoded nickname for the Alice account.
The amount that was reserved from Alice's account to secure the nickname.
If you were to query the Nicks pallet for the nameOf for Bob's account, you would see the
None
value returned because Bob has not invoked thesetName
dispatchable and deposited the funds to reserve a nickname.
Explore more advanced topics
This tutorial uses the Signed
button to invoke the killName
dispatchable function and uses Bob's account ID as the function's argument.
The killName
function must be called by the ForceOrigin
that was configured with the Nicks pallet's Config
interface in the previous section.
You may recall that we configured this to be the FRAME system's Root
origin.
The node template's chain specification file is used to configure the Sudo pallet to give Alice access to this origin.
The front-end template makes it easy to use the Sudo pallet to dispatch a call from the Root
origin - just use the SUDO
button to invoke the dispatchable.
Since we just used the Signed
button as opposed to the SUDO
button, the function was dispatched by the Signed
origin associated with Alice's account as opposed to the Root
origin.
You will notice that even though the function call was successfully dispatched, a BadOrigin
error was emitted and is visible in the Events pane.
This means that Alice's account was still charged fees for the dispatch, but there weren't any state changes executed because the Nicks pallet follows the important verify-first-write-last pattern.
Now use the SUDO
button to dispatch the same call with the same parameter.
The Sudo pallet emits a Sudid
event to inform network participants that the Root
origin dispatched a call, however, you will notice that the inner dispatch failed with a DispatchError
(the Sudo pallet's sudo
function is the "outer" dispatch).
In particular, this was an instance of the DispatchError::Module
variant, which reports two pieces of metadata: an index
number and an error
number.
The index
number relates to the pallet from which the error originated; it corresponds with the index (position) of the pallet within the construct_runtime!
macro.
The error
number corresponds with the index of the relevant variant from that pallet's Error
enum.
When using these numbers to find pallet errors, remember that the first position corresponds with index zero.
In the screenshot above, the index
is 9
(the tenth pallet) and the error
is 2
(the third error).
Depending on the position of the Nicks pallet in your construct_runtime!
macro, you may see a different number for index
.
Regardless of the value of index
, you should see that the error
value is 2
, which corresponds to the third variant of the Nick's pallet's Error
enum, the Unnamed
variant.
This shouldn't be a surprise since Bob has not yet reserved a nickname, thus it cannot be cleared!
You should confirm that Alice can use the SUDO
button to invoke the killName
dispatchable and forcibly clear the nickname associated with any account (including her own) that actually has a nickname associated with it.
Here are some other things you may want to try:
- Add a nickname that is shorter than the
MinNickLength
or longer than theMaxNickLength
that you configured with the Nick's pallet'sConfig
configuration trait. - Add a nickname for Bob then use Alice's account and the
SUDO
button to forcibly kill Bob's nickname. Switch back to Bob's account and dispatch theclearName
function.
Adding other FRAME pallets
This tutorial illustrated how to add a pallet to the runtime using the Nicks pallet as an example. However, it is important to note that each pallet is a little different. If you aren't sure how to add a specific pallet, the best place to look is the runtime from the main Substrate repository. Unlike the node template used in tutorials, the runtime in the main Substrate repository includes most of the pallets available in the library of core FRAME pallets.
Refer to the bin/node/runtime/Cargo.toml
file for the main Substrate node runtime to see examples of how to import different pallet dependencies.
Refer to the bin/node/runtime/src/lib.rs
file for the main Substrate node runtime to see examples of how to configure the runtime to include each pallet.
Generally, you can copy the code from the main Substrate node runtime as a starting point to include a pallet in your own runtime.
Learn how to add a more complex pallet to the Node Template by going through the Add the Contracts Pallet guide or complete the Forkless Upgrade a Chain tutorial to learn how Substrate enables forkless runtime upgrades and follow steps to perform two distinct types of upgrades.
Publish your own pallet
By now you should have successfully imported the Nicks pallet. In future, you will write your own pallets to execute your application-specific logic. In those cases, you may want to share these pallets with others.
In this section, we will cover how to publish your own pallet for open source usage.
Option 1: Publishing on GitHub
To publish your pallet on GitHub, you need to create a GitHub repository and push your pallet's code to it.
Once published, other developers could refer to your pallet in their Cargo.toml
using the following snippet:
runtime/Cargo.toml
your-pallet-name = { default_features = false, git = "https://github.com/your-username/your-pallet", version = "1.0.0" }
# You may choose to refer to a specific commit, tag, or branch instead of (or in addition to, over-specifying) a version
# rev = "<git-commit>""
# tag = "<some tag>"
# branch = "<some branch>"
Option 2: Publishing on crates.io
crates.io allows permissionless publishing of any Rust module. You could learn the procedure by following their guide on how to publish on crates.io.
Once published, other developers could refer to your pallet in their Cargo.toml
using the following snippet:
runtime/Cargo.toml
[dependencies]
your-pallet-name = { default_features = false, version = "some-compatible-version"}
We do not specify any target destination on the above, and by default it will search for the package in crates.io repository.
Next steps
- Follow the tutorials that demonstrate Substrate development concepts and techniques.
- Browse How-to Guides for examples of common runtime development patterns.
- Explore the FRAME examples to learn what's available in the FRAME library.