Blockchain Research Lab | Creating Ink Smart Contracts Based on Substrate

Parity's Substrate blockchain framework has made good progress in the final version development. Ink is Parity's solution for writing smart contracts for Substrate-based blockchains.

Like Substrate, Ink is built on top of Rust, so it follows the Rust language rules and syntax. This article will introduce an example of copying an ERC721 token in a smart contract, commonly referred to as the ERC721 token on the Ethereum blockchain.

This is the first part of a series that will cover the process of creating and deploying this Ink smart contract. Specifically, we will introduce:

  1. How to install Substrate and Ink and their dependencies;
  2. Write a simple irreplaceable token contract that supports three main functions: creating a token, transferring a token, and approving another account to send a token;
  3. How to build and deploy smart contracts on the Substrate blockchain and test the functionality on the chain using the Polkadot JS application;

Note: The Polkadot JS application is not only designed to manage Polkadot itself, but also to manage any Substrate chain. The interface is dynamic because the available management options depend on the capabilities supported by the Substrate chain.

In this article, we will introduce the installation process of Ink and its required dependencies, and introduce the ERC721 token example contract attached to this series, discuss the structure of the Ink contract and its difference from the Solidity-based contract, and the two Similarity between the two.

Non-homogeneous token

The non-homogeneous token NFT is different from the ERC20 token, and each token is unique. In our contract, each token has a unique ID that represents the token. From here on, each token can have its own value, its own metadata, or a specific purpose in an application or value system.

Non-owner accounts that approve transfer or management tokens are also different and must be completed using NFT on a per token basis. Encrypted cats are the most famous example of which non-homogenized tokens are implemented – each token represents a kitten on the platform.

NFT poses different challenges for standard tokens, so we can explore more based on Ink syntax and functionality. Let's next install Substrate, Ink and the required packages to write our smart contract.

System installation

Starting to write an Ink contract requires some dependencies. After installation, we can run a Substrate chain in the terminal window – we will use this chain as a way to deploy smart contracts.


Substrate and Ink depend on rust. Use the following command to install Rust. There are also two commands below to ensure that your Nightly Rust version (the latest available Rust version) is up to date, and that the Webassembly build supports Nightly Rust: the Ink contract is compiled as a .wasm file, so it is deployed as a Webassembly to the Substrate chain.

Note 1: Cargo is Rust's package manager, and its package is called Crates.

Note 2: Referring to the last command, target refers to the target folder in the rust project directory that contains the program build. We will build a smart contract file here.


Install Substrate using the following command:

Wasm utilities

Ink smart contracts are compiled into WebAssembly before being uploaded to the Substrate chain. To do this, we need to install some utilities:


We can now install Ink through the box and run the following command:

Running the substrate chain locally

We will test on the local Substrate blockchain running on your machine. Now that Substrate is installed, run the following command to initialize the chain:

The –dev flag initializes the development blockchain for you, localizing and initializing some user accounts only on your computer.

This setting is purely for testing purposes; you can think of it as Truffle's Ganache program, which runs a local Ethereum-based chain that has been tweaked for testing purposes.

Now you will notice that the running node is verifying the new block. Pressing CTRL+C or closing the terminal window will stop the node execution, but the chain status will persist until the next time you run the chain. In this note, if you want to start a new chain, do the following to clear your chain:

With the operation of our Substrate chain and the preparation of Ink, we can write contracts. Before doing so, you need to mention editor support. I personally found that Visual Studio Code is the best editorial work, not only for Ink development, but also for Rust development. Quick setup method:

If you don't already have an editor installed, install VS Code from here to install the RLS (Rust Language Support) extension and the WebAssembly extension. This can be done directly in the editor under the Extensions tab, or by pressing Shift + CMD + X to jump directly to it.

Set up the Ink project

Now that we have installed all the dependencies, let's set up the project now.

The easiest way to do this is to install Ink's "Hello World" contract called Flipper. After installing Flipper, we can build what we already have, without having to worry about configuring and compiling scripts – these are all available in Flipper.

Note: Substrate and Ink are both in a rapid development phase and have not yet completed all of the functionality, so the smart contract environment and smart contract code itself are likely to change as Parity gets closer to the final version of the framework.

To start our Ink project, use cargo to get Flipper:

Flipper provides us with a sample of the projects needed to start writing smart contracts, including:

  1. A simple Flipper contract in src / that simply "flips" a boolean between true and false via the flip() method and gets the value on the chain using the get() method. We will replace this file with a NFT contract.
  2. Rust specific Cargo.toml file that outlines project dependencies and module metadata, .gitignore files and files. The file is the JSON abstraction we run to compile our smart contract, resulting in the contract's compiled .wasm file, contract, and so on. We will further explore the completion contract.

Let's change flipper to a more appropriate name: nftoken. Modify the following:

  1. / flipper folder name is /nftoken
  2. Cargo.toml: Change the [package] name and [lib] name to nftoken
  3. modify PROJNAME = nftoken

Also, make sure we have permission to run nftoken /

Finally, add the /nftoken folder to the vs code workspace and we're ready to start writing.

About Ink

Ink has multiple levels of abstraction, with higher levels abstracting lower levels. We will use the highest level, the language level or the lang level. These levels are also divided into modules that can be explored here.

Below the lang module are models and core modules that focus on intermediate abstractions and core utilities, respectively. Below the core module, we can also expect a CLI specifically for creating and managing Ink smart contracts.

Although there is hardly any content on how to use these modules at the time of writing, we do have raw API documentation available for browsing, including core modules and model modules. If you're reading this article, you can now browse through these documents, although our contract below will use some of these APIs to show how to use them in a lang-level context with non-interchangeable token contracts.

With this in mind, let's take a look at what the structure of our lang-derived contract looks like and compare it to our expectations for a smart-based smart contract.

Smart contract architecture

Constructing an ink smart contract is similar to a solidity smart contract, where the main components of solidity we expect are also consistent in ink: contract variables, events, public and private functions, and environment variables that get the caller's address.

Here's an abstraction of how the NFOTek contract is constructed:

Let's briefly access these sections and how they differ from our expectations for Solidity contracts. Ink is built on Rust, so all the syntax here is a valid Rust syntax.

Our module declaration section is where we introduce external functionality into the contract, which is similar in nature to the Solidity usage statement.

Events are declared in the Event enumeration, and with Solidity we define events separately, typing each event into an event:

If the solidity contract is embedded in the interface block, the ink contract is embedded in the contract! Macro. Our events are declared outside of this macro, and events are declared inside the solidity interface. As described below.

Note: The macro in rust is a declaration that represents the grammar block that the wrapper expression will be surrounded. Macros are abstract at the syntactic level, so smart contracts! Macros are wrapping their content with more syntax.

With Ink, our contract variables are written in the structure of our contract name. A hash map derived from Rust's HashMap type replaces the mapping type of Solidity, providing a list of key => values.

How Substrate stores values

Any data that is persisted on the Substrate chain is called extrinsic data, and Ink provides us with a way to store external data on the chain through a storage module, which is located in the core module of the language. In other words, all the contract variables you plan to keep on the chain will use the type in the store. Instead, the memory module can also be used for data structures that operate on the memory.

On the other hand, Solidity has adopted a different approach. Starting with Solidity 0.5, storage and memory reference types are introduced into function arguments or function variables, so the contract knows where to reference these variables. However, this is not required for contract variables in Solidity.

The primitive type is also available and consistent in both languages; Rust uses u64, and Solidity uses a more detailed uint64 type declaration. In general, it is very simple to implement the same contract variable structure between the two languages.

In the above example, the type of value processed by the storage object is passed in angle brackets instead of the generic type.

The concept of an initialization function exists in Ink and Solidity, although implemented in different ways. Using Ink, we explicitly define the deploy() method in the Deploy {} implementation block. The parameters we define for this method represent the data we sent when we initialized the contract. E.g. For our irreplaceable tokens, we will provide the initial number of tokens:

Public and private methods are also defined in the impl block, where public methods are explicitly defined using pub (external). Similarly, when comparing this grammar to the syntax of Solidity, internal and external are used to define private or public resources.

Note: In Rust, functions, modules, features, and structures are private by default and must be defined using the pub keyword so that they can be accessed externally. The (external) extension of pub here is specific to Ink and must contain public Ink functionality.

Similarly, we separate the private and public functions in separate impl blocks and include pub(external) for the defined public functions.

As the last building block of the contract, a test module was defined that asserts various conditions when testing our functions. In the test module, we can test our contract logic without having to compile and deploy it to the Substrate chain, so we can quickly resolve the error and verify that the contract works as expected.


We have now discussed the building blocks of the Ink smart contract. In the next part of this series, we'll delve deeper into the capabilities of non-replaceable token smart contracts, understand the Rust design patterns used, and how they fit into our smart contract logic. Then we will introduce the process of building a smart contract and deploying it to the local Substrate chain, and testing its functionality using Polkadot JS.

This article reprints the public number: blockchain research laboratory, focusing on blockchain technology, product community, economic model and other comprehensive knowledge system output.

Feel free to contact the author WeChat: csschan1120