Mint basic tokens

This guide demonstrates how you can mint a token by leveraging the primitive capabilities of a StorageMap. In this guide, the StorageMap primitive uses the blake2128concat hasher to map balances to account IDs. This approach is similar to how the Balances pallet makes use of it to store to keep track of account balances.

You should note that this guide is only intended to illustrate a simple approach to creating tokens in Substrate. This approach is not a recommended best practice. You should keep in mind the following limitations and assumptions used in this guide:

  • 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 a GenesisConfig or fixing a predetermined maximum value in runtime.
  • Weights. This guide uses an arbitrary weight of 10_000 in the code snippets. Learn more about weight configuration in Transactions, weights, and fees.
  • 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 in Privileged calls and origins.

See the Examples section for practical implementations of this guide.

Use cases

Give any account the ability to create a token supply in exchange for native token fee.

Steps preview

  1. Set up the Config trait.
  2. Declare your StorageMap storage item.
  3. Create the mint function.
  4. Create the transfer function.
  5. Add checks and error handling.
  6. Write to storage.
  7. Add your new pallet to the runtime.

Set up the Config trait

Using the node template as a starting point, specify the types your pallet depends on and the Events it emits:

// TODO - this block was malformed

/* --snip-- */
pub enum Event<T: Config> {
	MintedNewSupply(T::AccountId),
	Transferred(T::AccountId, T::AccountId, T::Balance),
}

Declare your storage item

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-- */

Create the mint function

We can now bring our attention to creating the intended capabilities of our pallet. Create the mint() function 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-- */

Create the transfer function

Create the transfer() function to allow the minting account to transfer a given balance to another account.

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-- */

Add checks and 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.

Add your pallet to the runtime

See Import a pallet if you’re not yet familiar with this procedure.

Examples