What are Soul bound tokens(SBT)?

Aaron RebeloAaron Rebelo
4 min read

Soulbound tokens are a type of non-fungible token (NFT) that is permanently linked or "bound" to a specific user's account. In other words, once a soulbound token is created and assigned to a user, it cannot be transferred or sold to another user.

Soulbound tokens are typically used in gaming and virtual world environments to represent unique in-game items, such as weapons or armor, that are earned or awarded to a specific player. Because these items are bound to the player's account, they cannot be traded or sold, which helps to maintain the integrity of the game's economy and prevent cheating or fraud.
Another segment we can use SBT is the education field. where on completion of a course on Udemy or your degree certification. We can also build applications that create SBT associated with government-issued cards, such as a driving license or your Aadhar card.

I’ve just shown you the tip of the iceberg, the use cases are endless, If you want to understand more about here’s an excellent blog by the man himself Vitalik Buterin

In some cases, soulbound tokens may also be used to represent access to specific content or services, such as a subscription to a premium service or a ticket to a restricted event. In these cases, the token is bound to the user's account and cannot be transferred or resold to another user.

Soulbound tokens are created using smart contracts on a blockchain, which ensures that the tokens are secure, transparent, and tamper-proof. By binding the token to a specific user's account, soulbound tokens help to create a sense of ownership and exclusivity, which can be a powerful motivator for users in gaming and virtual world environments.

Understanding the crux of the blog

Today we’ll be writing and deploying an NFT smart contract which is an EIP4973 complaint i.e. Soulbound/Account bound token and we as the owner can mint and send the tokens to eligible people so that they can view it on Opensea, or any other marketplace.

For this, we’ll first create an ERC721 NFT smart contract and then modify it along the way to make it EIP4973 compliant

ERC721 Contract.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "@openzeppelin/contracts@4.7.3/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts@4.7.3/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts@4.7.3/access/Ownable.sol";
import "@openzeppelin/contracts@4.7.3/utils/Counters.sol";

contract SoulboundGov is ERC721, ERC721URIStorage, Ownable {
    using Counters for Counters.Counter;

    Counters.Counter private _tokenIdCounter;

    //Events
    event Attest(address indexed to, uint256 indexed tokenId);
    event Revoke(address indexed to, uint256 indexed tokenId);

    constructor() ERC721("SoulBound-AadharCard", "GOV") {}

    function _baseURI() internal pure override returns (string memory) {
        return "ipfs://bafkreidwx6v4vb4tkhhcjdjnmpndxqqcxp6bnowtkzehmncpknx3x2rfhi";
    }
    function safeMint(address to) public onlyOwner {
        uint256 tokenId = _tokenIdCounter.current();
        _tokenIdCounter.increment();
        _safeMint(to, tokenId);
    }

    // The following functions are overrides required by Solidity. 
    // And its internal, so that means it can be called only within this contract
    function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
        super._burn(tokenId);
    }
    function burn(uint256 tokenId) external {
        require(msg.sender==ownerOf(tokenId),"You dont have ownership of this token");
        _burn(tokenId);
    } 
    function tokenURI(uint256)
        public
        pure
        override(ERC721, ERC721URIStorage)
        returns (string memory)
    {
        return _baseURI();
    }
    //this is inbuilt, it acts basically like a hook that executes before the token is transfered;
    //The virtual keyword basically says that the child contract can override this contract using the override keyword
    function _beforeTokenTransfer(address from,address to,uint256 /*tokenId*/) override internal virtual {
        require(from==address(0)||to==address(0),"you cannot transfer this token as its soul bound");
    }
        function _afterTokenTransfer(
        address from,
        address to,
        uint256 firstTokenId
    ) internal override virtual {
        if(from == address(0)) {
            emit Attest(to, firstTokenId);
        } else if (to == address(0)) {
            emit Revoke(to, firstTokenId);
        }
    }
}

So I created a NFT and tried sending it to my other account and as expected, it failed.

Now although I've explained the above 2 main functions in the code, I'm gonna explain it again

    function _beforeTokenTransfer(address from,address to,uint256 /*tokenId*/) 
override
internal 
virtual {
        require(from==address(0)||to==address(0),"you     cannot transfer this token as its soul bound");

    }

So basically it says you can mint it or you can sent it back to the contract that minted it for you!. snding it anywhere else is not possible.
The above function is basically like a hook that runs before the NFT is sent. you dont have to worry about the inderlying details as its virtual, so you can override it.

    function _beforeTokenTransfer(address from,address to,uint256 /*tokenId*/) override internal virtual {
        require(from==address(0)||to==address(0),"you cannot transfer this token as its soul bound");
    }
        function _afterTokenTransfer(
        address from,
        address to,
        uint256 firstTokenId
    ) internal override virtual {
        if(from == address(0)) {
            emit Attest(to, firstTokenId);
        } else if (to == address(0)) {
            emit Revoke(to, firstTokenId);
        }
    }
}

The second function is similar to the first, except its run after the token is sent.
here its basically used to send a event.

Etherscan will given you what require failed.

0
Subscribe to my newsletter

Read articles from Aaron Rebelo directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Aaron Rebelo
Aaron Rebelo

Hi, I'm Aaron Rebelo, a MERN stack developer who's passionate about creating robust and engaging web applications. My expertise lies in using MongoDB, Express, React, and Node.js to build applications that are not only functional but also visually appealing. In addition to my work as a developer, I'm currently learning Solidity, a programming language used for developing smart contracts on the Ethereum blockchain. I believe that blockchain technology is the future, and I'm excited to be a part of this emerging industry. I'm also interested in digital marketing and believe that effective marketing strategies are key to the success of any project. That's why I'm committed to learning the latest marketing techniques and strategies to help my clients achieve their goals. In my spare time, I've started a video series on my YouTube channel, @thedecadehypothesis, where I document my progress on building new habits every 10 days. I believe that building new habits is essential for personal growth, and I'm excited to share my journey with others. Thank you for taking the time to learn more about me. I'm passionate about my work and excited about the opportunities that lie ahead. Please feel free to connect with me if you have any questions or if you're interested in working together.