1

I'm trying to create a soulbound (non-transferable NFT) token on Solidity. Here is my code so far.

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.17 <0.9.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

contract SoulboundToken is ERC721 {
   uint256 private _tokenIdCounter;

   constructor() ERC721("SoulboundToken", "SBT") {
       _tokenIdCounter = 0;
   }

   function mint(address to) public {
       _mint(to, _tokenIdCounter);
       _tokenIdCounter++;
   }

   function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal override {
       require(from == to, "Soulbound tokens cannot be transferred");
       super._beforeTokenTransfer(from, to, tokenId);
   }
}

I keep getting these errors:

TypeError: Function has override specified but does not override anything.
  --> Soulbound token.sol:18:86:
   |
18 |    function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal override {
   |                                                                                      ^^^^^^^^

and

TypeError: Member "_beforeTokenTransfer" not found or not visible after argument-dependent lookup in type(contract super SoulboundToken).
  --> Soulbound token.sol:20:8:
   |
20 |        super._beforeTokenTransfer(from, to, tokenId);
   |        ^^^^^^^^^^^^^^^^^^^^^^^^^^

I've tried fixing these two errors. Nothing works. Please assist.

3 Answers 3

1

Seems like you are using version 5 of OZ contracts.

In version 5 they have removed the _beforeTokenTransfer and _afterTokenTransfer functions. And introduced a new single function, _update.

enter image description here

Now you need to override this function only, and include whatever logic is needed.

1
  • Wow, I didn't notice this. The code seems more cleaner now. Commented Nov 24, 2023 at 9:44
0

If OpenZeppelin Version V4.

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.17 <0.9.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

contract SoulboundToken is ERC721 {
    uint256 private _tokenIdCounter;

    constructor() ERC721("SoulboundToken", "SBT") {
        _tokenIdCounter = 0;
    }

    function mint(address to) public {
        _mint(to, _tokenIdCounter);
        _tokenIdCounter++;
    }

    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 firstTokenId,
        uint256 batchSize
    ) internal override {
        if (from != address(0)) {
            require(from == to, "Soulbound tokens cannot be transferred");
        }

        super._beforeTokenTransfer(from, to, firstTokenId, batchSize);
    }
}
0

Simply override the _update from ERC721. When a transfer is attempted, the if statement checks if the token has an existing owner (from != address(0) ) and is not being sent to a zero address (to != address(0) ). If both conditions are met, it implies a valid transfer attempt. This is version >5.0.0

function _update(address to, uint256 tokenId, address auth)
    internal
    override(ERC721, ERC721Pausable)
    returns (address)
{
    address from = _ownerOf(tokenId);
    if (from != address(0) && to != address(0)) {
        revert("Soulbound: Transfer failed");
    }

    return super._update(to, tokenId, auth);
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.