NFT 2.0: Creating a Soulbound Token
A Step-by-step Guide To Creating And Deploying A Non-Transferable (Soulbound) NFT Smart Contract
We laud NFTs for their transferability but Vitalik's extended idea in a blog post titled "Soulbound"↗, over the long haul is showing up in literally every conversation happening around identity, reputation, and individuation in web3. If you haven't checked it out yet, go give it a read. If you've already and need a refresher, here's a TLDR:
Soulbound Tokens (SBT) are just non-fungible tokens (NFTs) that, "once picked up, cannot be transferred or sold to anyone else."
Provoking name and the Kardashian clan's shenanigans aside, there are (IRL) some things you just cannot sell. Think of your driver’s license, university degree, and formal identification. What if all of it could be encoded on the blockchain and verified by the token, coined as soulbound?
Sounds interesting? Let’s SHIPPPPP one!
Building a Soulbound NFT
In this tutorial, we will learn how to:
Create a non-transferable NFT smart contract on Solidity
Deploy it on Ethereum Rinkeby Testnet
and display the minted Soulbound token on Opensea
If you followed along the tutorial I published about building an ERC721 Smart Contract↗, then you already know most of the code necessary to build them. If not, you can always refer to that video to explore more. So, let's get building!
Step 1: Writing NFT Smart Contract
Before we implement the non-transferring logic in our smart contract, we would need a base ERC-721 code for NFTs. To start, we’ll navigate to the OpenZeppelin Wizard↗, select “ERC721” as our contract type and opt for a few features like "Mintable", "Auto-increment IDs" and "Base URI" from the sidebar as shown.
Step 2: Customizing Code to Make it Soulbound
Now that we have the base code ready, let's head over to Remix Solidity Compiler↗ by clicking, "Open in Remix".
Once we're on Remix, you should be able to see the following code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts@4.7.0/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts@4.7.0/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts@4.7.0/access/Ownable.sol";
import "@openzeppelin/contracts@4.7.0/utils/Counters.sol";
contract SoulBoundTest is ERC721, ERC721URIStorage, Ownable {
using Counters for Counters.Counter;
Counters.Counter private _tokenIdCounter;
constructor() ERC721("SoulBoundTest", "SBT") {}
function safeMint(address to, string memory uri) public onlyOwner {
uint256 tokenId = _tokenIdCounter.current();
_tokenIdCounter.increment();
_safeMint(to, tokenId);
_setTokenURI(tokenId, uri);
}
// The following functions are overrides required by Solidity.
function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
super._burn(tokenId);
}
function tokenURI(uint256 tokenId)
public
view
override(ERC721, ERC721URIStorage)
returns (string memory)
{
return super.tokenURI(tokenId);
}
}
Now let's go ahead and add a new code snippet to the existing smart contract in order for us to block the transfer of NFT and eventually making it "Soulbound." There already exist an API in the OpenZeppelin's ERC721 Contract, using which we can actually control the transferability of our tokens.
If you navigate to the OpenZeppelin API Docs, you will find the following functions:
- _beforeTokenTransfer(address from, address to, uint256 tokenId)
To block token transfers, add the following to your code:
File: openzeppelin-contracts/contracts/token/ERC721/ERC721.sol
function _beforeTokenTransfer( address from, address to, uint256 tokenId ) internal override virtual { require(from == address(0), "Err: token transfer is BLOCKED"); super._beforeTokenTransfer(from, to, tokenId); }
Now let's understand the logic behind this code↗.
Every time this code will run, the require statement will check: if the from address
parameter in the function is set to zero. If yes, it will allow the action to happen and block all the other transfers to make it a non-transferable token.
ℹ️ Note: When the
address from == 0
, it means the token is being issued or minted and not transferred.
Step 3: Deploying The Soulbound NFT Smart Contract
To deploy the smart contract directly from remix, head over to side bar and select "Deploy and Run Transaction" from the tab. Set Environment to Injected Web3, select the Soulbound smart contract we just created from contracts dropdown and click Deploy.
- Injected Provider - Provider Name: For connecting Remix to an injected web3 provider. The most common injected provider is Metamask.
Make sure your MetaMask is connected to the Ethereum Rinkeby Testnet and you've some test ether to make transactions happen.
Step 4: Adding NFT Asset To IPFS Via Pinata
Once the contract is successfully deployed↗, the next thing we will need to do is minting a Soulbound NFT. To do so, we are going to first set the NFT asset and metadata using Pinata.
If you don’t have a Pinata account, sign up for a free account here and complete the steps to verify your email.
Once you have verified your account on the Pinata, navigate to the "My Files" page and click the "Upload" button
Upload your NFT art and you'll see your image asset file under the Files page along with the CID column
Copy the CID by clicking the copy button next to it and use it as a postfix to this URL "gateway.pinata.cloud/ipfs" to view your NFT art.
Here's the image↗ that I used.
Now, we’re going to upload one more document to Pinata - JSON metadata. To do that, create a JSON file and add the following code.
{
"attributes" : [ {
"trait_type" : "NFT",
"value" : "Soulbound"
}, {
"trait_type" : "Asset",
"value" : "Non-transferable Badge"
}, {
"trait_type" : "Badge",
"value" : "Minting First Soulbound Token"
}, {
"trait_type" : "Type",
"value" : "Demo"
} ],
"description" : "This is a demo soulbound token mint",
"image" : "https://gateway.pinata.cloud/ipfs/QmTY1jMAqabHUZFCj91ckwnCu3CPtJChpg345jdkxzj3ac",
"name" : "Soulbound Token"
}
Feel free to add or remove the attributes. Also, make sure you are providing your own IPFS image URL!
Once you’re done editing the JSON file, save it and upload it to Pinata, following the same steps we followed for uploading the image and copy the CID for the JSON file (we will need that in the next step).
Step 5: Minting The Soulbound NFT
In this step, we are finally going to mint the NFT and display it on Opensea. To mint the NFT, head back to Remix IDE, find "Deployed Contracts" button, and click on the "SafeMint" function.
Under SafeMint, input your wallet address in to and set the JSON file's URL in URI, following the format as "gateway.pinata.cloud/ipfs/{CID_of_JSON_file}". Here's the IPFS link↗ to the Metadata I uploaded to the Pinata.
Once the configurations is set, hit transact, confirm the transaction and wait a few seconds for the NFT to be minted.
Now, we are going to head to Opensea↗ (Testnet) to view the minted Soulbound NFT↗.
Since we have blocked all the transfer action, if you try to send this NFT to some other wallet it will prompt you an error. This way, we can confirm that the NFT we just minted is now bounded to our souls (realistically, to our wallet address) and can never be transferred or sold to anyone else.
Full Code Repository
Wohoo! We’ve finally deployed our very first Soulbound NFT on Rinkeyby Testnet. If you want to take a look at the full source code, find the repo below.
What’s Next?
A fun project would be to create a platform that lets course creators issue a Soulbound NFT as a credential for students who complete the course.
PS: If you work on this idea, feel free to comment your creation in the comment section below.
You can also drop me a "👋" on Twitter @kayprasla or find me in the Developer DAO discord!
Other Articles I’ve Written
%[ankr.hashnode.dev/how-to-deploy-your-own-er.. %[ankr.hashnode.dev/deploy-and-mint-a-cryptok..