Primitive Token Mint
This is a beginner guide intended for new Substrate developers looking to explore ways to create tokens in Substrate. This approach is not a recommended best practice. Use this guide to learn how to improve upon your runtime logic capabilities and code quality. See the Examples section for practical implementations of this guide.'
Goal
Create a simple token mint pallet.
Use Cases
Give any account the ability to create a token supply in exchange for native token fee.
Overview
This guide will step you through an effective way to mint a token by leveraging the primitive capabilities that StorageMap gives us.
To achieve this, this "primitive" approach uses the blake2_128_concat hasher
to map balances to account IDs, similar to how the Balances pallet makes use of it to store and keep track of account balances.
Steps
1. Setup your pallet's Config
trait.
Using the node template as a starting point, specify the types your pallet depends on and the Events
it emits:
// The configuration trait
pub trait Config: system::Config {
type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;
type Balance: Member + Parameter + AtLeast32BitUnsigned + Default + Copy;
}
/* --snip-- */
pub enum Event<T: Config> {
MintedNewSupply(T::AccountId),
Transferred(T::AccountId, T::AccountId, T::Balance),
}
2. Declare your storage item StorageMap
.
This pallet only keeps track of the balance to account ID mapping. Call it BalanceToAccount
:
/* --snip-- */
#[pallet::storage]
#[pallet::getter(fn get_balance)]
pub(super) type BalanceToAccount<T: Config> = StorageMap<
_,
Blake2_128Concat,
T::AccountId,
T::Balance,
ValueQuery
>;
/* --snip-- */
3. Create your pallet’s functions.
We can now bring our attention to creating the intended capabilities of our pallet with the following functions:
mint()
: to issue a token supply from any origin./* --snip-- */ #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub(super) fn mint( origin: OriginFor<T>, #[pallet::compact] amount: T::Balance ) -> DispatchResultWithPostInfo { let sender = ensure_signed(origin)?; // Check if the kitty does not already exist in our storage map ensure!(Self::kitties(&kitty_id) == None, <Error<T>>::KittyExists); // Update storage. <BalanceToAccount<T>>::insert(&sender, amount); // Emit an event. Self::deposit_event(Event::MintedNewSupply(sender)); // Return a successful DispatchResultWithPostInfo. Ok(().into()) } /* --snip-- */
transfer()
: to allow the minting account to transfer a given balance to another account.
Define transfer variables
Start with writing out the variables, using get_balance
to reference to StorageMap
of balances previously
declared in storage:
pub(super) fn transfer(
origin: OriginFor<T>,
to: T::AccountId,
#[pallet::compact] amount: T::Balance,
) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
let sender_balance = Self::get_balance(&sender);
let receiver_balance = Self::get_balance(&to);
/* --snip-- */
Verify and add error handling.
When performing balance updates, use checked_sub
and checked_add
to handle potential errors with overflow:
/* --snip-- */
// Calculate new balances.
let updated_from_balance = sender_balance.checked_sub(value).ok_or(<Error<T>>::InsufficientFunds)?;
let updated_to_balance = receiver_balance.checked_add(value).expect("Entire supply fits in u64, qed");
/* --snip-- */
Write to storage
Once the new balances are calculated, write their values to storage and deposit the event to the current block:
// Write new balances to storage.
<Balances<T>>::insert(&sender, updated_from_balance);
<Balances<T>>::insert(&to, updated_to_balance);
Self::deposit_event(RawEvent::Transfer(sender, to, value));
Ok(())
}
/* --snip-- */
If checked_sub()
returns None
, the operation caused an overflow and throws an error.
4. Include your pallet in your runtime
Refer to this guide if you’re not yet familiar with this procedure.
- Safety. The mint function takes in an amount to 'mint' which is not good practice because it implies that users have unlimited access to writing to storage. Safer approaches include: using configuring 'GenesisConfig' or fixing a predetermined maximum value in runtime.
- Weights. All the weights were set to 10_000 in the above code snippets. Learn more about weight configuration in this basic guide on weights.
- Origins. This guide assumes that the origin will always be the sudo user. Origins are a powerful capability in Substrate. Learn more about how they work here.
Examples
- mint-token example pallet
- reward-coin example pallet