Code a Soulbound Token (SBT) — Tutorial With Full Source Code


Code a Soulbound Token (SBT) — Tutorial With Full Source Code

As the name implies, a soulbound token (SBT) is a type of non-fungible token (NFT) token that is bounded to a person’s soul. This means, in blockchain context, this unique NFT token should not leave the holder’s wallet.

However, during the token minting process, you may wish to issue this token to yourself (the smart contract owner) initially and only transfer it to a user’s wallet subsequently. This tutorial will guide you to do exactly that.

1. Ensure You Have Goerli Testnet and Test Tokens

Before we begin, ensure you already have Goerli testnet on your MetaMask. If you don’t have some Goerli test tokens yet, please go and get some.

Sorry, but to get Goerli test tokens you need to free sign-up on Alchemy because it’s the most accessible Goerli faucet compared to the rest from Polygon (not working at the time of this writing) or Quicknode (requires real ETH 0.01).

2. Use OpenZeppelin Wizard to Rip ERC721 Smart Contract Code

In the spirit of not reinventing the wheel, we shall be using OpenZeppelin Wizard to auto-generate the ERC721 smart contract code, as shown below.

OpenZeppelin ERC721 Code Generator

Please ensure you ticked the features in the red box as shown above.

Ultimately, we want an ERC721 smart contract that allows us to mint new SBT tokens (via Mintable and Auto Increment Ids) with our own payload (via URI Storage). We also wish to allow the token holder to burn the SBT tokens (via Burnable) as their hearts desire.

The most critical feature is the Ownable modifier. Quoting from Alchemy

A modifier is a special type of Solidity function that is used to modify the behavior of other functions. For example, developers can use a modifier to check that a certain condition is met before allowing the function to execute.

Hence, Ownable is a special function that is used to check if a token holder can transfer an SBT token.

Inside Ownable.sol, you will find the function below. It checks to see if the wallet/token holder is the owner of the smart contract.

* @dev Throws if called by any account other than the owner.
modifier onlyOwner() {
 require(isOwner(), "Ownable: caller is not the owner");

We will learn how to add it to our code next.

3. Stop Transfer at the Transfer Function Door

As a contract owner, we need to prevent the token recipient from calling the transfer function in the ERC721 smart contract. But what does this transfer function do?

In the context of ERC721, the NFT code standard, the transfer function is a method defined in the ERC721 smart contract standard that allows the transfer of an NFT from one wallet address to another.

Here’s an example of how the transfer function can be implemented in ERC721 code.

contract ERC721 {
    // Mapping from token ID to owner address
    mapping(uint256 => address) private _tokenOwners;
    // Transfer function
    function transfer(address _to, uint256 _tokenId) public {
        require(_tokenOwners[_tokenId] == msg.sender, "You don't own this token");

        _tokenOwners[_tokenId] = _to;

In the code above, the transfer function takes two parameters: _to (the address of the recipient) and _tokenId (the unique identifier of the NFT being transferred). The function checks if the caller (msg.sender) is the current owner of the token, and if so, updates the _tokenOwners mapping to reflect the transfer of ownership to the new address _to.

4. Modify the OpenZeppelin Code to override transferFrom function

At the OpenZeppelin Wizard, click on the Open in Remix button at the top right corner. It will redirect you to the Remix IDE. This is where we will modify our code.

Now, we need to override the transferFrom function by entering the following code.

function transferFrom(address from, address to, uint256 tokenId) public onlyOwner override {
  _transfer(from, to, tokenId);

The important keyword above is onlyOwner where it prevents anyone, including the token holder or smart contract owner to transfer to token out from the current wallet. You can learn more about Ownable.

But the token holder could still burn the SBT token if they want to.

5. Compile The Soulbound Token Smart Contract

On Remix, click on the third icon on the left menu (underneath the magnifier), and hit Compile MyToken.sol (your smart contract may be named differently).

Please ensure you select the right compiler version as shown in the source code.

6. Deploy SBT Contract on Goerli Ethereum Testnet

Before you can deploy, read the prerequisites below.

  • Make sure you already selected Goerli testnet on your MetaMask.
  • Make sure you already have some Goerli test tokens.

Go to the last icon on the left side of Remix to deploy your contract. See the screenshot below for details on how to deploy your SBT contract.

When you hit the orange Deploy button, MetaMask will pop up. You just need to follow its instructions and press Confirm.

After you have successfully deployed your contract, you should be able to see your contract address in the bottom left corner.

7. Let’s Mint an SBT Token Shall We

Click on your contract address at the bottom left and you will see a bunch of contract functions. See the screenshot below for more details.

To mint your first contract, you just need to select the SafeMint function and enter the wallet address that you used to deploy the contract. Because this is the owner of the contract.

Then, enter the URI that points to your token payload. This is where you stored your token data — such as an NFT image. You may use (provided by Filecoin) to upload your payload for free. Once you uploaded say an image, you will get a URL like this. Copy the link and paste it into the URI box in the function.

Finally, press the transact button, to mint your SBT. If all goes well, your token shall be minted and given the token ID of 0. The token IDs will be auto-incrementing from 0 and so on.

Now you can view your SBT token by going to the ownerOf function and entering the token ID of 0. Your wallet address shall appear below the function.

8. Transfer The SBT Token to a Wallet

Last but certainly not the least, let’s transfer our SBT token to someone’s wallet. Remember that once the SBT token is received, the recipient can’t further transfer the token out, hence, making it soulbound.

For this tutorial, you’re recommended to send the SBT to another wallet in your MetaMask

Go to the transferFrom function and enter your wallet address in the from field and recipient’s wallet in the to field. We will transfer the token with ID 0. See the snapshot below.

You may also use the safeTransferFrom function. The difference is that the safe version would check if the recipient wallet is compatible to properly receive your SBT token (ERC721).

Once you hit transact, your SBT token will be sent to the recipient’s wallet.

9. Let’s Testdrive The Code

Now, go to the SBT recipient wallet on your MetaMask and add the NFT. Just to recap, an SBT is actually an NFT without the ability for the token holder to transfer it out.

For this step, make sure your MetaMask is pointing to your recipient’s account

Click on the NFTs menu item and on that screen, press Import NFT. See below.

In the next screen, enter your SBT contract address and the token ID (which is 0).

You will then see your smart contract and your SBT token (called Unnamed) under the screen. Click on the unnamed token and you will see a screen like below.

Hit the Send button and try to send it to any other wallet.

By the way, you must have some test tokens for this recipient account in order to send. I often encounter this mistake. Sigh…

You will get an error saying — Fail with error ‘Ownable: caller is not the owner’ as shown on Etherscan below. The error message speaks for itself.

Extra: An Easier and Code-Free Alternative

If you find the above is too much to handle, consider using Mintnite to create your SBT tokens without having to touch a single line of code. This tool actually fast-tracked the entire process above and pack it into an easy-to-use UI for you to indulge in. The smart contract still belongs to you and you only. Feel free to test-drive it with a testnet before committing.