WASM - Implement PSP22 with OpenBrush
​

WASM - Implement PSP22 in your contract with OpenBrush

This guide is a step-by-step guide to deploy a WebAssembly (WASM) smart contract bsed on Polkadot standards.
In this tutorial, we will create a fungible token based on PSP22 Fungible Token standard.

Setup

Please refer to the ink! repo for environment setup.

Implement PSP22 in your contract

For latest version of this tutorial 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.
1
[dependencies]
2
ink_primitives = { tag = "v3.0.0-rc4", git = "https://github.com/Supercolony-net/ink", default-features = false }
3
ink_metadata = { tag = "v3.0.0-rc4", git = "https://github.com/Supercolony-net/ink", default-features = false, features = ["derive"], optional = true }
4
ink_env = { tag = "v3.0.0-rc4", git = "https://github.com/Supercolony-net/ink", default-features = false }
5
ink_storage = { tag = "v3.0.0-rc4", git = "https://github.com/Supercolony-net/ink", default-features = false }
6
ink_lang = { tag = "v3.0.0-rc4", git = "https://github.com/Supercolony-net/ink", default-features = false }
7
ink_prelude = { tag = "v3.0.0-rc4", git = "https://github.com/Supercolony-net/ink", default-features = false }
8
scale = { package = "parity-scale-codec", version = "2.1", default-features = false, features = ["derive"] }
9
scale-info = { version = "0.6.0", default-features = false, features = ["derive"], optional = true }
10
# These dependencies
11
psp22 = { tag = "v1.0.0", git = "https://github.com/Supercolony-net/openbrush-contracts", default-features = false }
12
brush = { tag = "v1.0.0", git = "https://github.com/Supercolony-net/openbrush-contracts", default-features = false }
13
[features]
14
default = ["std"]
15
std = [
16
"ink_primitives/std",
17
"ink_metadata",
18
"ink_metadata/std",
19
"ink_env/std",
20
"ink_storage/std",
21
"ink_lang/std",
22
"scale/std",
23
"scale-info",
24
"scale-info/std",
25
# These dependencies
26
"psp22/std",
27
"brush/std",
28
]
Copied!

Step 2: Add imports

Replace ink::contract macro by brush::contract. Import everything from psp22::traits.
1
#[brush::contract]
2
pub mod my_psp22 {
3
use psp22::traits::*;
4
use ink_storage::Lazy;
5
use ink_prelude::{string::String, vec::Vec};
Copied!

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.
1
#[ink(storage)]
2
#[derive(Default, PSP22Storage, PSP22MetadataStorage)]
3
pub struct MyPSP22 {
4
#[PSP22StorageField]
5
psp22: PSP22Data,
6
#[PSP22MetadataStorageField]
7
metadata: PSP22MetadataData,
8
}
Copied!

Step 4: Inherit logic

Inherit implementations of PSP22 and PSP22Metadata traits. You can customize (override) methods in this impl block.
1
impl PSP22 for MyPSP22 {}
2
impl PSP22Metadata for MyPSP22 {}
Copied!

Step 5: Define constructor

Define constructor. Your basic version of PSP22 contract is ready!
1
impl MyPSP22 {
2
#[ink(constructor)]
3
pub fn new(_total_supply: Balance, name: Option<String>, symbol: Option<String>, decimal: u8) -> Self {
4
let mut instance = Self::default();
5
Lazy::set(&mut instance.metadata.name, name);
6
Lazy::set(&mut instance.metadata.symbol,symbol);
7
Lazy::set(&mut instance.metadata.decimals,decimal);
8
instance._mint(instance.env().caller(), _total_supply);
9
instance
10
}
11
}
Copied!

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.
1
#[ink(storage)]
2
#[derive(Default, PSP22Storage, PSP22MetadataStorage)]
3
pub struct MyPSP22 {
4
#[PSP22StorageField]
5
psp22: PSP22Data,
6
#[PSP22MetadataStorageField]
7
metadata: PSP22MetadataData,
8
// fields for hater logic
9
hated_account: AccountId,
10
}
11
impl PSP22 for MyPSP22 {
12
// Let's override method to reject transactions to bad account
13
fn _before_token_transfer(&mut self, _from: AccountId, _to: AccountId, _amount: Balance) {
14
assert!(_to != self.hated_account, "{}", PSP22Error::Custom(String::from("I hate this account!")).as_ref());
15
}
16
}
17
impl PSP22Metadata for MyPSP22 {}
18
impl MyPSP22 {
19
#[ink(constructor)]
20
pub fn new(_total_supply: Balance, name: Option<String>, symbol: Option<String>, decimal: u8) -> Self {
21
let mut instance = Self::default();
22
Lazy::set(&mut instance.metadata.name, name);
23
Lazy::set(&mut instance.metadata.symbol,symbol);
24
Lazy::set(&mut instance.metadata.decimals,decimal);
25
instance._mint(instance.env().caller(), _total_supply);
26
instance
27
}
28
#[ink(message)]
29
pub fn set_hated_account(&mut self, hated: AccountId) {
30
self.hated_account = hated;
31
}
32
#[ink(message)]
33
pub fn get_hated_account(&self) -> AccountId {
34
self.hated_account.clone()
35
}
36
}
Copied!
Last modified 20d ago