How to Create Your Own Collectable Token on RSK Network

Written by RSKsmart | Published 2020/09/24
Tech Story Tags: ethereum | rsk | dapps | openzeppelin | erc721 | frontend | react | truffle

TLDR RSK Smart Contract Platform is an open source platform for smart contracts based on the Bitcoin network. We will create a dapp inspired by Cryptokitties, a popular blockchain game where you can collect and breed digital cats. In this tutorial, instead of collecting felines in our app, we are going to collect exclusive color tokens. The most important properties for this kind of asset is to have a way to check who owns what and how to move things around. It is easy to create new ERC721-compliant contracts by importing it from the OZ library and we will do so in this tutorial.via the TL;DR App

RSK is an open source platform for Ethereum compatible smart contracts based on the Bitcoin network. 
In this tutorial, you will learn about blockchain programming from scratch by building a fully decentralized application (DApp), step by step. You will also learn how to create your own collectable token on the RSK blockchain network using the Truffle framework, Open Zeppelin (OZ) libraries, and build a front end with React, using `create-react-app`.
We will create a dapp inspired by Cryptokitties , a popular blockchain game where you can collect and breed digital cats. In this tutorial, instead of collecting felines in our app, we are going to collect exclusive color tokens.
Fungible vs Non-Fungible Token
A fungible token represents an asset that can be exchanged for any other asset of equal value in its class.
A currency is an example of a fungible asset.
A $100 bill is equal to any other $100 bill, you can freely exchange these bills with one another because they have the same value, no matter the serial number on the specific $100 bill. They are fungible bills.
On the other hand, a Non-Fungible Token (NFT) is a unique token. So collectible items are non-fungible assets, and can be represented by NFTs.
ERC-721
ERC-721 was the first standard, and currently still the most popular standard, for representing non-fungible digital assets. The most important properties for this kind of asset is to have a way to check who owns what and a way to move things around.
It is easy to create new ERC721-compliant contracts by importing it from the OZ library and we will do so in this tutorial.
The interface for ERC-721 provides two methods:
  • `ownerOf`: to query a token's owner
  • `transferFrom`: to transfer ownership of a token
And this is enough to represent an NFT!
Colors
In this tutorial, we are going to create an NFT to represent our collectible color tokens.
You will be able to create new color tokens and claim them so that they can be held in a digital blockchain wallet.

Overview

Here is a summary of the steps to be taken to build our token:
  1. Installation requirements;
  2. Initialize a project using Truffle and Open Zeppelin;
  3. Configure Truffle to connect to RSK testnet;
  4. Get a wallet with some testnet R-BTCs;
  5. Initialize the client side application;
  6. Add more configurations to Truffle; 
  7. Create the smart contract and compile it;
  8. Create deploy instructions file in Truffle;
  9. Deploy a smart contract on RSK Testnet using Truffle;
  10. Create client side application;
  11. Interact with the smart contract.
Steps 1 to 4 are explained in detail in the tutorial link below: How To Set Up An RSK Project Using Truffle And Open Zeppelin

Requirements

  1. POSIX compliant shell
  2. Curl
  3. Node.js and NPM (Node Package Manager)
  4. Code editor: Visual Studio Code (VSCode) or any other editor of your choice
  5. Truffle
  6. Metamask - google chrome extension
The requirements 1 to 5 are explained in detail in the tutorial links below: How To Set Up An RSK Project Using Truffle And Open Zeppelin
For requirement 6, installing Metamask, connecting to RSK testnet, and getting some tR-BTCs, this is explained step-by-step in the tutorial link below: How To Use Remix and Metamask To Deploy Smart Contracts On The RSK Testnet 

Setup the Project

Create a new folder named colors.
mkdir colors
cd colors
Inside the folder colors, do the steps below, following instructions from the tutorial How To Set Up An RSK Project Using Truffle And Open Zeppelin
  1. Initialize an empty Truffle project;
  2. Initialize an npm project;
  3. Install OZ;
  4. Install HD wallet provider;
  5. Create a wallet mnemonic;
  6. Create .secret file;
  7. Get the current gas price at RSK testnet;
  8. Configure Truffle to connect to RSK testnet;
  9. Use Truffle console;
  10. Test the connection to RSK network;
  11. Get addresses;
  12. Check balance;
  13. Get some testnet R-BTCs at faucet;

Initialize the client side application

We have 3 requirements to build the frontend:
  1. Create React App
  2. Web3.js
  3. Bootstrap
Create React App
This is the official template to create single-page React applications. It offers a build setup with no configuration.
To learn more: create react app
In the project folder, at terminal, run:
npx create-react-app app --use-npm
The option --use-npm is to select npm as package manager. npx comes with npm 5.2+ and higher, see instructions for older npm versions. This is a large package and this might take a couple of minutes to show the message of successful installation:
Now you have a new folder named app and we will customize our frontend later.
Web3.js
Web3.js helps us to develop websites or clients that interact with the blockchain - writing code that reads and writes data from the blockchain with smart contracts.
The web3.js library is an Ethereum Javascript API which connects using the generic JSON-RPC spec. As RSK’s virtual machine implementation is compatible with the Ethereum Virtual Machine (EVM), it is possible to use web3.js to interact with the front end and the RSK local node.
To install web3.js, input the command below into the terminal and press enter at folder app
cd app
npm install -E web3@1.2.7
The option -E is to save dependencies with an exact version rather than using npm’s default.
Bootstrap
We also need to install Bootstrap.
cd app
npm install -E bootstrap@4.4.1
As I said before, the option -E is to save dependencies with an exact version rather than using npm’s default.

Configure Truffle

Come back to the project folder, open truffle-config.js file in VS Code and overwrite it with the following code:
const HDWalletProvider = require('@truffle/hdwallet-provider');
 
const fs = require('fs');
const mnemonic = fs.readFileSync(".secret").toString().trim();
if (!mnemonic || mnemonic.split(' ').length !== 12) {
  throw new Error('unable to retrieve mnemonic from .secret');
}
 
const gasPriceTestnetRaw = fs.readFileSync(".gas-price-testnet.json").toString().trim();
const gasPriceTestnet = parseInt(JSON.parse(gasPriceTestnetRaw).result, 16);
if (typeof gasPriceTestnet !== 'number' || isNaN(gasPriceTestnet)) {
  throw new Error('unable to retrieve network gas price from .gas-price-testnet.json');
}
console.log("Gas price Testnet: " + gasPriceTestnet);
 
const path = require("path");
 
module.exports = {
  networks: {
    testnet: {
      provider: () => new HDWalletProvider(mnemonic, 'https://public-node.testnet.rsk.co/2.0.1/'),
      network_id: 31,
      gasPrice: Math.floor(gasPriceTestnet * 1.1),
      networkCheckTimeout: 1e9
    },
  },
 
  contracts_build_directory: path.join(__dirname, "app/src/contracts"),
 
  compilers: {
    solc: {
      version: "0.5.7",
    }
  }
}
It looked like this:
About contracts_build_directory
We add the library path to use with a new parameter contracts_build_directory that defines the locale where files for contracts artifacts, like abi and deployed addresses are saved.
It will be located in a different folder: app/src/contracts.

Smart contract architecture

We will create a smart contract named Color.sol that will inherit the ERC721 definition from the OZ library.

Create the smart contract Color.sol

In the Contracts folder, create a new file named Color.sol.

File Color.sol

Copy and paste the smart contract from the following gist, or inline below:
It looked like this:

Understanding the smart contract

To create our ERC-721 Token, we will import `ERC721Full` from OZ. This library itself imports several other libraries such as `SafeMath.sol`, the standards for this kind of token and some extra features, like enumeration and metadata.
With metadata we can customize our token by giving it a name and a symbol at constructor.
This function gets run only once; whenever the smart contract is created the first time, i.e., deployed to the blockchain.
We are calling the constructor function of the parent smart contract `ERC721Full` and passing in custom arguments like the name `Color` and the symbol `COLOR`.
The color management is performed with the variable `colors`, which is an array of colors and `_colorExists`, which is a fast "lookup" to know when a color is already minted.
Also we have a function to create new color tokens.  This is the basic structure of the function. It will accept 1 argument of the `bytes3` type, which will be a hexadecimal code that corresponds to the token's color.
For example, if we want to create a green token, we will pass "#00FF00" when we call this function. Or if we want to create a red token, we'll use "#FF0000".

Compile a smart contract

In the terminal, run this command:
truffle compile
It will compile the smart contract color.sol and all imported libraries.
It looked like this:

Deploy a smart contract at RSK Testnet

First of all, we need to create a file in Truffle structure with instructions to deploy the smart contract.

Create file 2_deploy_contracts.js

Folder migrations has JavaScript files that help you deploy contracts to the network. These files are responsible for staging your deployment tasks, and they’re written under the assumption that your deployment needs will change over time. A history of previously run migrations is recorded on-chain through a special Migrations contract which is automatically created by Truffle. (source: running migrations)
In the migrations folder, create the file 2_deploy_contracts.js
Copy and paste this code:
const Color = artifacts.require("Color");
 
module.exports = function(deployer) {
  deployer.deploy(Color);
};
It looked like this:

Migrate

In the terminal, run this command:
truffle migrate --network testnet
Wait a few minutes while the transactions for the smart contract deployments are sent to the blockchain.
The migrate command will compile the smart contract again if necessary.
First, it deploys the smart contract Migrations.sol, file generated by Truffle:
This is the transaction at RSK testnet:
And then it deploys our smart contract Color.sol:
This is the transaction at RSK testnet:
A tip: if there is a communication problem with the testnet between the publication of Migrations.sol and Color.sol, just run the migrate command again, it will deploy only what is missing.
Congratulations!
Our NFT Color is published at RSK Testnet.
Save the contract address of token, it can be used later:
tokenAddress = "0x5505a54a8F3e63D37095c37d9f8AcF0f4900B61F"

Client side application

Now let’s start building out the front end that will interact with the smart contract. It will allow us to create new color tokens, and list out all of the existing tokens in your wallet.
In the app folder, we need to customize some files.
index.html
In the app\public folder, open index.html file.
At head section, update the title:
<title>NFT Colors</title>
index.js
In the app\src folder, open index.js file and add a line to use bootstrap in out project
import 'bootstrap/dist/css/bootstrap.css';
Also remove this line:
import './index.css';
The final index.js is this. You can overwrite it with the code from following gist, or inline below:
import React from 'react';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
At this point, your completed index.js file should looks like this:
App.css
Open App.css file and overwrite it with the code from the following gist, or copy and paste the code below:
.token {
  height: 150px;
  width: 150px;
  border-radius: 50%;
  display: inline-block;
}
This code customizes the appearance for tokens.
This is the result:

App.js

Open App.js file and overwrite it with the code from following gist, or copy and paste the code below:
import React, { Component } from 'react';
import Web3 from 'web3';
import './App.css';
import Color from './contracts/Color.json';
function colorHexToString(hexStr) {
  return '#' + hexStr.substring(2);
}
function colorStringToBytes(str) {
  if (str.length !== 7 || str.charAt(0) !== '#') {
    throw new Error('invalid color string');
  }
  const hexStr = '0x' + str.substring(1);
  return Web3.utils.hexToBytes(hexStr);
}
class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      account: '',
      contract: null,
      totalSupply: 0,
      colors: [],
    };
  }
  async componentWillMount() {
    await this.loadWeb3();
    await this.loadBlockchainData();
  }
  async loadWeb3() {
    if (window.ethereum) {
      // current web3 providers
      window.web3 = new Web3(window.ethereum);
      await window.ethereum.enable();
    }
    else if (window.web3) {
      // fallback for older web3 providers
      window.web3 = new Web3(window.web3.currentProvider);
    }
    else {
      // no web3 provider, user needs to install one in their browser
      window.alert(
        'No injected web3 provider detected');
    }
    console.log(window.web3.currentProvider);
  }
  async loadBlockchainData() {
    const web3 = window.web3;
    // Load account
    const accounts = await web3.eth.getAccounts();
    console.log ('account: ', accounts[0]);
    this.setState({ account: accounts[0] });
    const networkId = await web3.eth.net.getId();
    const networkData = Color.networks[networkId];
    if (!networkData) {
      window.alert('Smart contract not deployed to detected network.');
      return;
    }
    const abi = Color.abi;
    const address = networkData.address;
    const contract = new web3.eth.Contract(abi, address);
    this.setState({ contract });
    const totalSupply = await contract
      .methods.totalSupply().call();
    this.setState({ totalSupply });
    // Load Colors
    for (var i = 1; i <= totalSupply; i++) {
      const colorBytes = await contract
        .methods.colors(i - 1).call();
      const colorStr = colorHexToString(colorBytes);
      this.setState({
        colors: [...this.state.colors, colorStr],
      });
    }
  }
  mint = (colorStr) => {
    const colorBytes = colorStringToBytes(colorStr);
    this.state.contract.methods
      .mint(colorBytes)
      .send({ from: this.state.account })
      .once('receipt', (receipt) => {
        console.log ('transaction receipt: ', receipt)
        this.setState({
          colors: [...this.state.colors, colorStr],
        });
      });
  }
  render() {
    return (
      <div>
        <nav className="navbar navbar-dark fixed-top bg-dark flex-md-nowrap p-0 shadow">
          <span className="navbar-brand col-sm-3 col-md-2 mr-0">
            Color Tokens
          </span>
          <ul className="navbar-nav px-3">
            <li className="nav-item text-nowrap d-none d-sm-none d-sm-block">
              <small className="text-white"><span id="account">{this.state.account}</span></small>
            </li>
          </ul>
        </nav>
        <div className="container-fluid mt-5">
          <div className="row">
            <main role="main" className="col-lg-12 d-flex text-center">
              <div className="content mr-auto ml-auto">
                <h1>Issue Token</h1>
                <form onSubmit={(event) => {
                  event.preventDefault();
                  const colorStr = this.color.value;
                  this.mint(colorStr);
                }}>
                  <input
                    type='text'
                    className='form-control mb-1'
                    placeholder='e.g. #FF00FF'
                    ref={(input) => { this.color = input }}
                  />
                  <input
                    type='submit'
                    className='btn btn-block btn-primary'
                    value='MINT'
                  />
                </form>
              </div>
            </main>
          </div>
          <hr/>
          <div className="row text-center">
            { this.state.colors.map((colorStr, key) => {
              return (
                <div key={key} className="col-md-3 mb-3">
                  <div className="token" style={ { backgroundColor: colorStr } }></div>
                  <div>{colorStr}</div>
                </div>
              );
            })}
          </div>
        </div>
      </div>
    );
  }
}
export default App;

Understanding App.js

Import web3 here:
import Web3 from 'web3'
This part is connected to the RSK Testnet using the injected web3 provider, in this case, MetaMask:
async loadWeb3() {
    if (window.ethereum) {
      // current web3 providers
      window.web3 = new Web3(window.ethereum);
      await window.ethereum.enable();
    }
    else if (window.web3) {
      // fallback for older web3 providers
      window.web3 = new Web3(window.web3.currentProvider);
    }
    else {
      // no web3 provider, user needs to install one in their browser
      window.alert(
        'No injected web3 provider detected');
    }
    console.log(window.web3.currentProvider);
  }
To load the instance of smart contract Color already published, we need to load the informations from Truffle deploy:
import Color from './contracts/Color.json';
And after connecting successfully, the function loadBlockchainData loads accounts, network information and the smart contract Color.
async loadBlockchainData() {
    const web3 = window.web3;
    // Load account
    const accounts = await web3.eth.getAccounts();
    console.log ('account: ', accounts[0]);
    this.setState({ account: accounts[0] });
    const networkId = await web3.eth.net.getId();
    const networkData = Color.networks[networkId];
    if (!networkData) {
      window.alert('Smart contract not deployed to detected network.');
      return;
    }
    const abi = Color.abi;
    const address = networkData.address;
    const contract = new web3.eth.Contract(abi, address);
    this.setState({ contract });
    const totalSupply = await contract
      .methods.totalSupply().call();
    this.setState({ totalSupply });
    // Load Colors
    for (var i = 1; i <= totalSupply; i++) {
      const colorBytes = await contract
        .methods.colors(i - 1).call();
      const colorStr = colorHexToString(colorBytes);
      this.setState({
        colors: [...this.state.colors, colorStr],
      });
    }
  }
Also we have a mint function at App.js which sends a transaction to the network calling the mint function in the smart contract.
mint = (colorStr) => {
    const colorBytes = colorStringToBytes(colorStr);
    this.state.contract.methods
      .mint(colorBytes)
      .send({ from: this.state.account })
      .once('receipt', (receipt) => {
        console.log ('transaction receipt: ', receipt)
        this.setState({
          colors: [...this.state.colors, colorStr],
        });
      });
  }
Finally, the render() function is responsible for the HTML code for the application. It has 3 primary functions:
  • Displays the current address on the Navbar
  • Provides a form to mint new tokens
  • Displays all the existing tokens on the page in a grid, showing the color and the code for each token

Running

In the app folder, at terminal, run:
npm start
It will automatically open the default browser at http://localhost:3000/
If it does not open, you can enter the local url manually in the browser.
Metamask automatically detects that our app would like to connect, authorize this action by clicking on the Connect button.

Interact with the smart contract

The colors are saved with hexadecimal representation for each.
To know more about color and hex color codes:
Some color codes:
Mint
Choose a color and enter your hexadecimal representation in the info text field, and click on the MINT button.
It will call the mint() function at the smart contract instance Color, with the color that you defined.
I will enter the color red, value #FF0000.
Do not forget to use the symbol #
Click on the confirm button.
Great! Now I have my first color collectable token:
I would like to mint the blue color: #0000FF
Wait for a few seconds for your transaction to be mined…
And now I have two colors in my collection!
My collection is growing!

Congratulations!

Hope it was easy for you to create a NFT!
I showed you how to connect Truffle to the RSK network and deploy your own NFT using the OZ libraries, and that they work on the RSK network.
This tutorial was inspired by Gregory McCubbin’s tutorial, from dApp University. Check out the original article.
Our goal is to join forces and give options to people who believe in smart contracts based on Ethereum, and also believe in the power of Bitcoin, through RSK.
I hope this tutorial has been helpful and I’d appreciate your feedback. Happy with this tutorial? Share it if you like it :)

The end

Author: Solange Gueiros
Reviewers: Brendan Graetz and Owanate Amachree

Written by RSKsmart | Smart Contract Platform On Top of Bitcoin
Published by HackerNoon on 2020/09/24