PSP22 — Fungible Token Standard for Substrate’s contracts pallet

Sequaja
5 min readMar 25, 2023

--

Introduction:

When it comes to developing WASM (ink!) smart contracts , OpenBrush is a library that offers a range of useful features to help developers build contracts. One of the most significant advantages of OpenBrush is that it implements standard contracts based on PSP (Polkadot Standards Proposals).

A Polkadot Standards Proposal (PSP) describes standards for the Polkadot ecosystem. The Polkadot Standards Proposal GitHub is a community-based initiative. PSP process is not supposed to be a substitute for Polkadot Governance process and is meant to focus only on commonly agreed usage patterns rather than protocol adjustments.”

But what exactly is PSP22, and why is it important for ink! developers?

PSP22 is a standard implementation for fungible tokens on Substrate’s contracts pallet. It provides a uniform way for developers to create smart contracts that handle the transfer of tokens in a secure and seamless manner. Similar to Ethereum’s EIP-20, PSP22 defines a standard protocol for fungible tokens.

For ink! developers, OpenBrush provides an implementation of PSP22 that allows them to easily create contracts that support fungible tokens. This saves developers time and effort in building secure and efficient smart contracts on the Astar Blockchain. OpenBrush also offers a variety of useful contracts and macros to further simplify the development process. Overall, OpenBrush is an essential tool for ink! developers building smart contracts on the Astar Blockchain.

Tutorial:

In the following guide we will create an PSP22 contract using openbrush standard and make some minor changes to the code.

The tutorial assumes that the reader has already installed Rust programming language and completed all the necessary configurations for developing with Ink!. If this is not the case, the Astar Network Guide can be followed to set up the required environment: https://docs.astar.network/docs/build/wasm/ink-dev/

You can get the code for the PSP22 contract from the Openbrush website: https://openbrush.io/

Scroll down and there you can see the psp22, psp37 and psp34 implementation. Select the psp22 implementation and add the “Metadata” and “Burnable” extension and click on Download.

Unzip the file and open it in your favorite code editor. You will have two files. The Cargo.toml file and the lib.rs file. The Cargo.toml file contains the rust dependencies and Ink! Configuration and the lib.rs contains the contracts source code.

If you open the lib.rs file you see the source code for the contract.

The code defines an ink! smart contract module that implements a PSP22 token standard. It includes extensions for burnable and metadata functionality. The contract includes a constructor function that allows for the minting of tokens, setting of the token name, symbol, and decimal. The PSP22, PSP22Burnable, and PSP22Metadata traits are implemented by the contract.

To set the initial supply, name, symbol, and decimal using the constructor, you can modify the code as follows:

lib.rs

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

#[openbrush::contract]
pub mod my_psp22 {

// imports from openbrush
use openbrush::traits::String;
use openbrush::traits::Storage;
use openbrush::contracts::psp22::extensions::burnable::*;
use openbrush::contracts::psp22::extensions::mintable::*;
use openbrush::contracts::psp22::extensions::metadata::*;

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

// Section contains default implementation without any modifications
impl PSP22 for Contract {}
impl PSP22Burnable for Contract {}
impl PSP22Metadata for Contract {}

impl Contract {
#[ink(constructor)]
pub fn new(name: Option<String>, symbol: Option<String>, decimal: u8) -> Self {
let mut _instance = Self::default();
_instance._mint_to(_instance.env().caller(), 1_000_000).expect("Should mint");
_instance.metadata.name = Some(String::from("Token Name"));
_instance.metadata.symbol = Some(String::from("Tok"));
_instance.metadata.decimals = 18;
_instance
}

}
}

Cargo.toml

[package]
name = "mypsp22"
version = "1.0.0"
edition = "2021"
authors = ["The best developer ever"]

[dependencies]

ink = { version = "~4.0.0", default-features = false }

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

# Include brush as a dependency and enable default implementation for PSP22 via brush feature
openbrush = { tag = "3.0.0", git = "https://github.com/727-Ventures/openbrush-contracts", default-features = false, features = ["psp22"] }

[lib]
name = "mypsp22"
path = "lib.rs"
crate-type = [
# Used for normal contract Wasm blobs.
"cdylib",
]

[features]
default = ["std"]
std = [
"ink/std",
"scale/std",
"scale-info/std",

"openbrush/std",
]
ink-as-dependency = []

Inside the constructor, it first creates a new instance of the Contract struct by calling the default() method. Then, it calls the _mint_to() method to mint 1 million tokens to the caller of the constructor, and assigns the token name, symbol, and decimal.

You can now run the following contract to build the contract.

cargo contract build

After the contract is compiled you can upload the code to test it on the following website: https://polkadotjs-apps.web.app/?rpc=wss%3A%2F%2Frpc.shibuya.astar.network#/contracts

Connect to the “Shibuya Testnet” and click on “Developer” and then on “Contracts”.

Then you can upload your contract under “Upload & deploy code”. You can find your compiled contract in your folder under “projectname>target>ink>mypsp22.contract”.

Give it a name under “code bundle name”. And leave out all the other options as we have defined them in the constructor. After you have deployed the contract it should look like this:

Now you can query the total Supply and the balance of the contract owner, which are the same value in our example. And you can further query the tokenName, tokenDecimals and tokenSymbol, which we have defined.

Example query with the “psp22Metadata::tokenName”: You can find the value “Token Name” which we have defined in our contract.

--

--