Skip to main content

Implement PSP22 in your contract

For latest version please refer to OpenBrush Documentation

This example shows how you can reuse the implementation of psp22 token (in the same way you can reuse psp721 and psp1155). Also, this example shows how you can customize the logic, for example, to reject transferring tokens to hated_account.

Step 1: Include dependencies

Include dependencies to psp22 and brush in the cargo file.

[dependencies]
ink_primitives = { tag = "v3.0.0-rc4", git = "https://github.com/Supercolony-net/ink", default-features = false }
ink_metadata = { tag = "v3.0.0-rc4", git = "https://github.com/Supercolony-net/ink", default-features = false, features = ["derive"], optional = true }
ink_env = { tag = "v3.0.0-rc4", git = "https://github.com/Supercolony-net/ink", default-features = false }
ink_storage = { tag = "v3.0.0-rc4", git = "https://github.com/Supercolony-net/ink", default-features = false }
ink_lang = { tag = "v3.0.0-rc4", git = "https://github.com/Supercolony-net/ink", default-features = false }
ink_prelude = { tag = "v3.0.0-rc4", git = "https://github.com/Supercolony-net/ink", default-features = false }

scale = { package = "parity-scale-codec", version = "2.1", default-features = false, features = ["derive"] }
scale-info = { version = "0.6.0", default-features = false, features = ["derive"], optional = true }

# These dependencies
psp22 = { tag = "v1.0.0", git = "https://github.com/Supercolony-net/openbrush-contracts", default-features = false }
brush = { tag = "v1.0.0", git = "https://github.com/Supercolony-net/openbrush-contracts", default-features = false }

[features]
default = ["std"]
std = [
"ink_primitives/std",
"ink_metadata",
"ink_metadata/std",
"ink_env/std",
"ink_storage/std",
"ink_lang/std",
"scale/std",
"scale-info",
"scale-info/std",

# These dependencies
"psp22/std",
"brush/std",
]

Step 2: Add imports

Replace ink::contract macro by brush::contract. Import everything from psp22::traits.

#[brush::contract]
pub mod my_psp22 {
use psp22::traits::*;
use ink_storage::Lazy;
use ink_prelude::{string::String, vec::Vec};

Step 3: Define storage

Declare storage struct and declare the fields related to PSP22Storage and PSP22MetadataStorage traits. Then you need to derive PSP22Storage and PSP22MetadataStorage traits and mark corresponding fields with #[PSP22StorageField] and #[PSP22MetadataStorageField] attributes. Deriving these traits allows you to reuse the default implementation of PSP22 and PSP22Metadata.

#[ink(storage)]
#[derive(Default, PSP22Storage, PSP22MetadataStorage)]
pub struct MyPSP22 {
#[PSP22StorageField]
psp22: PSP22Data,
#[PSP22MetadataStorageField]
metadata: PSP22MetadataData,
}

Step 4: Inherit logic

Inherit implementations of PSP22 and PSP22Metadata traits. You can customize (override) methods in this impl block.

impl PSP22 for MyPSP22 {}

impl PSP22Metadata 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, name: Option<String>, symbol: Option<String>, decimal: u8) -> Self {
let mut instance = Self::default();
Lazy::set(&mut instance.metadata.name, name);
Lazy::set(&mut instance.metadata.symbol,symbol);
Lazy::set(&mut instance.metadata.decimals,decimal);
instance._mint(instance.env().caller(), _total_supply);
instance
}
}

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 PSP22 implementation. And we will add the hated_account: AccountId field to the structure.

#[ink(storage)]
#[derive(Default, PSP22Storage, PSP22MetadataStorage)]
pub struct MyPSP22 {
#[PSP22StorageField]
psp22: PSP22Data,
#[PSP22MetadataStorageField]
metadata: PSP22MetadataData,
// fields for hater logic
hated_account: AccountId,
}

impl PSP22 for MyPSP22 {
// Let's override method to reject transactions to bad account
fn _before_token_transfer(&mut self, _from: AccountId, _to: AccountId, _amount: Balance) {
assert!(_to != self.hated_account, "{}", PSP22Error::Custom(String::from("I hate this account!")).as_ref());
}
}

impl PSP22Metadata for MyPSP22 {}

impl MyPSP22 {
#[ink(constructor)]
pub fn new(_total_supply: Balance, name: Option<String>, symbol: Option<String>, decimal: u8) -> Self {
let mut instance = Self::default();
Lazy::set(&mut instance.metadata.name, name);
Lazy::set(&mut instance.metadata.symbol,symbol);
Lazy::set(&mut instance.metadata.decimals,decimal);
instance._mint(instance.env().caller(), _total_supply);
instance
}

#[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()
}
}