Skip to main content
Version: 3.2.0

PSP22 Capped

This example shows how you can reuse the implementation of PSP22 token with the PSP22Capped extension.

First, you should implement basic version of PSP22.

Step 1: Add imports and enable unstable feature​

  • Use openbrush::contract macro instead of ink::contract.
  • Use openbrush::implementation macro to inherit implementations of PSP22 and PSP22Capped traits.

Step 2: Define storage​

Declare storage struct and declare the field related to the capped module data structure. Then you need to derive the Storage trait and mark the corresponding field with the #[storage_field] attribute. Deriving this trait allows you to reuse the PSP22Capped extension in your PSP22 implementation.

#[ink(storage)]
#[derive(Default, Storage)]
pub struct Contract {
...
#[storage_field]
cap: capped::Data,
}

Step 3: Inherit logic​

You can customize (override) methods using #[overrider] attribute.

Override psp22::Transfer to check is the cap exceeded before minting.

#[overrider(psp22::Internal)]
fn _before_token_transfer(
&mut self,
from: Option<&AccountId>,
_: Option<&AccountId>,
amount: &Balance,
) -> Result<(), PSP22Error> {
// `is_none` means that it is minting
if from.is_none() && capped::Internal::_is_cap_exceeded(self, amount) {
return Err(PSP22Error::Custom(String::from("Cap exceeded")))
}
Ok(())
}

Step 4: Define constructor​

Define constructor. Your PSP22Capped contract is ready!

impl Contract {
/// Constructor which mints `initial_supply` of the token to sender
/// Will set the token's cap to `cap`
#[ink(constructor)]
pub fn new(inital_supply: Balance, cap: Balance) -> Self {
let mut instance = Self::default();

assert!(capped::Internal::_init_cap(&mut instance, cap).is_ok());
assert!(PSP22Mintable::mint(&mut instance, Self::env().caller(), inital_supply).is_ok());

instance
}
}

Final code​

#![cfg_attr(not(feature = "std"), no_std, no_main)]

#[openbrush::implementation(PSP22, PSP22Capped, PSP22Mintable)]
#[openbrush::contract]
pub mod my_psp22_capped {
use openbrush::traits::{
Storage,
String,
};

#[ink(storage)]
#[derive(Default, Storage)]
pub struct Contract {
#[storage_field]
psp22: psp22::Data,
#[storage_field]
cap: capped::Data,
}

#[overrider(psp22::Internal)]
fn _before_token_transfer(
&mut self,
from: Option<&AccountId>,
_: Option<&AccountId>,
amount: &Balance,
) -> Result<(), PSP22Error> {
// `is_none` means that it is minting
if from.is_none() && capped::Internal::_is_cap_exceeded(self, amount) {
return Err(PSP22Error::Custom(String::from("Cap exceeded")))
}
Ok(())
}

impl Contract {
/// Constructor which mints `initial_supply` of the token to sender
/// Will set the token's cap to `cap`
#[ink(constructor)]
pub fn new(inital_supply: Balance, cap: Balance) -> Self {
let mut instance = Self::default();

assert!(capped::Internal::_init_cap(&mut instance, cap).is_ok());
assert!(PSP22Mintable::mint(&mut instance, Self::env().caller(), inital_supply).is_ok());

instance
}
}
}

You can check an implementation example of PSP22 Capped.