Calling market contract on ERC721 _beforeTokenTransfer hook to delist NFT when transferred

I have my own ERC721 contract and marketplace contract to sell the NFTs.

I’m trying to find the best solution to this problem:

When I list an item on the marketplace, and this item is transferred outside the marketplace, or sold on a 3rd party marketplace, my item stays listed on my market, even if it technically can’t be bought anymore because the owner changed.

I’ve seen a few projects that have token and market in 1 contract, so it’s easy to delist an item on transfer.
I’m using 2 contracts, and I thought about calling the market contract to delist an item on the _beforeTokenTransfer hook in the token contract.

In my ERC721 token contract (OZ standards):

  function _beforeTokenTransfer(
    address from,
    address to,
    uint256 tokenId
  ) internal virtual override(ERC721Upgradeable, ERC721PausableUpgradeable) {
    // call market contract to delist
    _marketContractAddress.call(
      abi.encode(bytes4(keccak256("removeItemFromMarket(address,uint256)")),
      address(this),
      tokenId
    )
);
    super._beforeTokenTransfer(from, to, tokenId);
  }

And in my market contract:

  /**
    * @dev Remove a listed item from the market
    *  can only be removed by owner_of or the contract owner
    * @param token_address - nft erc721 address
    * @param token_id - nft token id
    */
  function removeItemFromMarket(
    address token_address,
    uint256 token_id
  )
    public
    whenNotPaused
    CompatibleERC721(token_address)
    ItemExists(token_id,token_address)
    OnlyItemHolder(token_id,token_address)
  {
    _removeItemFromMarket(token_address, token_id);
  }

    function _removeItemFromMarket(
    address token_address, uint256 token_id
  )
    internal
    returns (MarketItem memory)
  {
    address sender = _msgSender();
    MarketItem memory item = marketItemByTokenId[token_address][token_id];

    require(item.item_id != 0, "Item not listed");
    require(item.owner_of == sender || sender == owner(), "Unauthorized");

    bytes32 item_id = item.item_id;
    address owner_of = item.owner_of;
    delete marketItemByTokenId[token_address][token_id];

    emit ItemRemoved(
      item_id,
      token_id,
      token_address,
      owner_of
    );

    return item;
  }

I’ve debugged in Remix and the removeItemFromMarket is called on the market contract, but it reverts at the step bytes32 item_id = item.item_id; and goes back to the token contract to finish the transaction, which succeeds without deleting the market listing.

I’ve tried also with all requirement checks removed, but same result. From what I can see in the storage it reads the MarketItem object fine also.

My question: is this something ‘logical’ to do and could it work like this?
When the token contract calls the market contract, will _msgSender() in the market contract still be the _msgSender() of the token contract transaction?

I’ve searched many other NFT projects and marketplaces but cannot find the most used answer to this problem.
Could it be that most just leave the item listed forever and just solve it on the web3 side, with additional checks if the listing owner is still the same as the token owner?

Any feedback is very welcome, it’s been bugging my brain for a while how to deal with this :slight_smile: