Developers Home»how to guides»Primitive Token Mint

Primitive Token Mint

Information

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:

(i) 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)?;

    // Update storage.
    <BalanceToAccount<T>>::insert(&sender, amount);

    // Emit an event.
    Self::deposit_event(Event::MintedNewSupply(sender));

    // Return a successful DispatchResultWithPostInfo.
    Ok(().into())
}
/* --snip-- */

(ii) 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.

Further Learning
  • 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

How-to guides

Rust docs

Last edit: on

Was This Guide Helpful?
Help us improve