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.
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 nft.storage (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.