PSP22
This example shows how you can reuse the implementation of PSP22 token. Also, this example shows how you can customize the logic, for example, to reject transferring tokens to hated_account
.
Step 1: Include dependencies
Include openbrush
as dependency in the cargo file or you can use default Cargo.toml
template.
After you need to enable default implementation of PSP22 via openbrush
features.
openbrush = { version = "~2.1.0", default-features = false, features = ["psp22"] }
Step 2: Add imports and enable unstable feature
Use openbrush::contract
macro instead of ink::contract
. Import everything from openbrush::contracts::psp22
.
#![cfg_attr(not(feature = "std"), no_std)]
#![feature(min_specialization)]
#[openbrush::contract]
pub mod my_psp22 {
use openbrush::contracts::psp22::*;
use ink_prelude::string::String;
use ink_storage::traits::SpreadAllocate;
...
Step 3: Define storage
Declare the storage struct and declare the field related to the PSP22Storage
trait. Then you need to derive the PSP22Storage
trait and mark the corresponding field with the #[PSP22StorageField]
attribute. Deriving this trait allows you to reuse the default implementation of PSP22
.
#[ink(storage)]
#[derive(Default, SpreadAllocate, PSP22Storage)]
pub struct MyPSP22 {
#[PSP22StorageField]
psp22: PSP22Data,
}
Step 4: Inherit logic
Inherit the implementation of PSP22
trait. You can customize (override) methods in the impl
block.
impl PSP22 for MyPSP22 {}
Step 5: Define constructor
Define constructor. Your basic version of PSP22
contract is ready!
impl MyPSP22 {
#[ink(constructor)]
pub fn new(total_supply: Balance) -> Self {
ink_lang::codegen::initialize_contract(|instance: &mut MyPSP22| {
instance
._mint(instance.env().caller(), total_supply)
.expect("Should mint");
})
}
}
Step 6: Customize your contract
Customize it by adding hated account logic. It will contain two public methods set_hated_account
and get_hated_account
. Also we will
override _before_token_transfer
method in the PSP22
implementation(that methods defined in PSP22Transfer
trait), and we will add the hated_account: AccountId
field to the structure.
#[ink(storage)]
#[derive(Default, SpreadAllocate, PSP22Storage)]
pub struct MyPSP22 {
#[PSP22StorageField]
psp22: PSP22Data,
// fields for hater logic
hated_account: AccountId,
}
impl PSP22Transfer for MyPSP22 {
// Let's override method to reject transactions to bad account
fn _before_token_transfer(
&mut self,
_from: Option<&AccountId>,
to: Option<&AccountId>,
_amount: &Balance,
) -> Result<(), PSP22Error> {
if to == Some(&self.hated_account) {
return Err(PSP22Error::Custom(String::from("I hate this account!")))
}
Ok(())
}
}
impl PSP22 for MyPSP22 {}
impl MyPSP22 {
#[ink(constructor)]
pub fn new(total_supply: Balance) -> Self {
ink_lang::codegen::initialize_contract(|instance: &mut MyPSP22| {
instance
._mint(instance.env().caller(), total_supply)
.expect("Should mint");
})
}
#[ink(message)]
pub fn set_hated_account(&mut self, hated: AccountId) {
self.hated_account = hated;
}
#[ink(message)]
pub fn get_hated_account(&self) -> AccountId {
self.hated_account.clone()
}
}
You can check an example of the usage of PSP22.
Also you can use extensions for PSP22 token:
PSP22Metadata: metadata for PSP22.
PSP22Mintable: creation of new tokens.
PSP22Burnable: destruction of own tokens.
PSP22Wrapper: token wrapper for PSP22.
PSP22FlashMint: extension which allows the user to perform flashloans on the token by minting and burning the token.
PSP22Capped: extension which adds a cap for total supply of PSP22 tokens.
Check out the utilities for PSP22 token:
PSP22TokenTimelock: utility for locking PSP22 tokens for a specified time.