# Soulbound NFT migration from v4.9 to v5.0

**URL:** <https://forum.openzeppelin.com/t/soulbound-nft-migration-from-v4-9-to-v5-0/38931>\
**Category:** Smart Contracts\
**Tags:** erc721\
**Created:** [December 18, 2023, 3:26pm UTC](https://forum.openzeppelin.com/t/soulbound-nft-migration-from-v4-9-to-v5-0/38931 "2023-12-18T15:26:01Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![p10x](https://sea2.discourse-cdn.com/flex022/user_avatar/forum.openzeppelin.com/p10x/32/20131_2.png) [@p10x](https://forum.openzeppelin.com/u/p10x)\
**Post date:** [December 18, 2023, 3:26pm UTC](https://forum.openzeppelin.com/t/soulbound-nft-migration-from-v4-9-to-v5-0/38931/1 "2023-12-18T15:26:01Z")

</div>

I am trying to migrate the current soulbound functionality of my NFT-ERC721 contract from v.4.9. to v5.0. Q: **Can you give me a hint on how to ensure the soulbound functionaility in v5.0 ?**

On openzeppelin version 4.9. Ive used:

```auto
function _beforeTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal override virtual {
  require(from == address(0) || to == address(0), "Token transfer not allowed");
  super._beforeTokenTransfer(from, to, firstTokenId, batchSize);
}

function _afterTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal override virtual {
   if (from == address)) {
       emit Attest(to, firstTokenId);
   } else if ( to == address(0)) {
       emit Revert(to, firstTokenId);
}
    super._afterTokenTransfer(from, to, firstTokenId, batchSize);
}

```

The new v5.0 introduced the \_update function. The documentation (press release?: [source](https://blog.openzeppelin.com/introducing-openzeppelin-contracts-5.0)) mentions: "In 5.0, token hooks were removed from ERC20, ERC721, and ERC1155, favoring a single `_update` function. This can also be overridden for custom behaviors in any `mint`, `transfer`, or `burn` operation."

With the example:

 ![image](https://us1.discourse-cdn.com/flex022/uploads/zeppelin/original/3X/5/f/5f48169f964bf3784d0406c21b8d09dab2e734ac.jpeg)

I dont know how to ensure the soulbound functionality, additionally I dont understand the auth functionalitiy by ERC721.

---

<div class="post-metadata">

**Author:** ![saditya9211](https://sea2.discourse-cdn.com/flex022/user_avatar/forum.openzeppelin.com/saditya9211/32/17029_2.png) [@saditya9211](https://forum.openzeppelin.com/u/saditya9211)\
**Post date:** [February 4, 2024, 10:39am UTC](https://forum.openzeppelin.com/t/soulbound-nft-migration-from-v4-9-to-v5-0/38931/2 "2024-02-04T10:39:35Z")

</div>

Hey, I am also stuck in a situation similar to this. Did you get ahead of this blocker, if yes, could you please share how to resolve this?

Thanks!

---

<div class="post-metadata">

**Author:** ![gabrielstoica](https://sea2.discourse-cdn.com/flex022/user_avatar/forum.openzeppelin.com/gabrielstoica/32/18594_2.png) [@gabrielstoica](https://forum.openzeppelin.com/u/gabrielstoica)\
**Post date:** [February 4, 2024, 1:47pm UTC](https://forum.openzeppelin.com/t/soulbound-nft-migration-from-v4-9-to-v5-0/38931/3 "2024-02-04T13:47:35Z")

</div>

One solution would be to override the `transferFrom()` method and revert, since starting from `v5.0`, the `safeTransferFrom()` method also relies on it under the hood - e.g.:

```auto
function transferFrom(address from, address to, uint256 tokenId) public virtual override(ERC721, IERC721) {
  revert ERC721TransferNotAllowed();
}

```

FYI: have a look at [these lines of code](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol#L152-L162).

---

<div class="post-metadata">

**Author:** ![0xPat](https://sea2.discourse-cdn.com/flex022/user_avatar/forum.openzeppelin.com/0xpat/32/20394_2.png) [@0xPat](https://forum.openzeppelin.com/u/0xPat)\
**Post date:** [February 4, 2024, 2:40pm UTC](https://forum.openzeppelin.com/t/soulbound-nft-migration-from-v4-9-to-v5-0/38931/4 "2024-02-04T14:40:29Z")

</div>

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.

In our scenario, the function is configured to fail any transfer attempt to a non-zero address, as indicated by the revert("Soulbound: Transfer failed"); statement. This ensures that transfers will be successful only when directed to the zero address, aligning with the burnable functionality of the token.

In other words, you will have a soulbound token which is burnable.

```auto
    /**
     * @dev Internal function to handle token transfers.
     * Restricts the transfer of Soulbound tokens.
     */

    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);
    }

```

If you don't want to have a burnable token, you can adjust the conditions so that a mint is enabled but any other transfer fails.

```auto
    if (from != address(0)) {
        revert("Soulbound: Transfer failed"); // Prevent all transfers, making the token soulbound and non-burnable
    }

```

---

<div class="post-metadata">

**Author:** ![JBuilds](https://avatars.discourse-cdn.com/v4/letter/j/a9adbd/32.png) [@JBuilds](https://forum.openzeppelin.com/u/JBuilds)\
**Post date:** [September 10, 2024, 3:24pm UTC](https://forum.openzeppelin.com/t/soulbound-nft-migration-from-v4-9-to-v5-0/38931/5 "2024-09-10T15:24:58Z")

</div>

This is what I thought as well. I can't think of any reason to override \_update when you can just override transferFrom

---

<div class="post-metadata">

**Author:** ![gabrielstoica](https://sea2.discourse-cdn.com/flex022/user_avatar/forum.openzeppelin.com/gabrielstoica/32/18594_2.png) [@gabrielstoica](https://forum.openzeppelin.com/u/gabrielstoica)\
**Post date:** [September 11, 2024, 9:26am UTC](https://forum.openzeppelin.com/t/soulbound-nft-migration-from-v4-9-to-v5-0/38931/7 "2024-09-11T09:26:40Z")

</div>

Actually, the most efficient and cleanest solution is the one @0xPat suggested. By updating the `_update` method, you handle both `transferFrom` and `safeTransferFrom`, as both rely on it.

---

<div class="post-metadata">

**Author:** ![JBuilds](https://avatars.discourse-cdn.com/v4/letter/j/a9adbd/32.png) [@JBuilds](https://forum.openzeppelin.com/u/JBuilds)\
**Post date:** [September 11, 2024, 3:46pm UTC](https://forum.openzeppelin.com/t/soulbound-nft-migration-from-v4-9-to-v5-0/38931/8 "2024-09-11T15:46:16Z")

</div>

This is true, but in 5.0 doesnt safeTransferFrom also route to transferFrom? I overrode transferFrom to disable transnfers and was not able to get a token to transfer using any NFT function I could think of (including safeTransferFrom) but maybe there's something Im missing and there's still a way to transfer a token when trasnferFrom is overriden to revert?
