A Souldbound or non-transferable NFTs are a special type of digital asset that can’t be freely bought, sold, or traded like most other NFTs. Instead, these tokens come with strict rules that limit who can own them and prevent them from changing hands.
A great example of usage would the Buildspace’s NFT collection where they incentivise NFTs to people who registered and finished their series of tutorials/challenges online about blockchain and use it as a badge in the community. GTFOL!
I assume you are aware on how to write, deploy and are already familiar with smart contracts so lets head on to it.
First of, we’ll choose ERC-721 as our standard for the Soulbound NFT collection and create a basic NFT smart contract.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract SoulboundNFT is ERC721, Ownable {
constructor() ERC721("SoulboundNFT", "SBNFT") {}
function mint(address to, uint256 tokenId) external onlyOwner {
_mint(to, tokenId);
}
}
Now that we have our smart contract which has a simple mint function we can now add in our secret ingredient to make the NFTs not transferable
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId,
uint256 batchSize
) internal override virtual { // Custom logic here, e.g., checking special conditions
require(from == address(0), "Err: token transfer is BLOCKED");
super._beforeTokenTransfer(from, to, tokenId, batchSize);
}
The _beforeTokenTransfer
function is a hook provided by the OpenZeppelin library for ERC-20 and ERC-721 tokens. It allows you to add custom logic that will be executed before a token transfer occurs. This hook is often used to implement additional checks or actions before tokens are moved from one address to another.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract SoulboundNFT is ERC721, Ownable {
constructor() ERC721("SoulboundNFT", "SBNFT") {}
function mint(address to, uint256 tokenId) external onlyOwner {
_mint(to, tokenId);
}
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId,
uint256 batchSize
) internal override virtual {
require(from == address(0), "Err: token transfer is BLOCKED");
super._beforeTokenTransfer(from, to, tokenId, batchSize);
}
}
The final smart contract should look like this.
In summary, non-transferable NFTs enforce their non-transferable nature through smart contract code that overrides standard transfer functions. Any attempt to transfer the NFT results in a failed transaction, ensuring that the token remains with its current owner.