How to operate with ERC721 token within another contract?

I would like to utilize the blockchain for my project. I would like put logic of exchanging goods&services into the smart contract to keep all parties safe about their intents and reduce fraud.

I successfully design, deploy and mint my custom token which extends ERC721 standard (I use openzeppelin implementation). At this moment I am working on the contract which receives offers and demands and do exchange between users in case all criteria meets. The issue is how to operate with ERC721 token within this contract (aka marketplace) when you have received just an address of this token.

Remix has similar use case under EnglishAuction name.

    IERC721 public nft;
    uint public nftId;

    address payable public seller;
    uint public endAt;
    bool public started;
    bool public ended;

    address public highestBidder;
    uint public highestBid;
    mapping(address => uint) public bids;

    constructor(
        address _nft,
        uint _nftId,
        uint _startingBid
    ) {
        **nft = IERC721(_nft);**

This sample code is able to recreate token by an address of it. I had checked and didn't found this or similar functionality in RFC721 openzeppelin implementation.

Could you please show the right way to operate with openzeppelin token? Is there an alternative way to achieve my goal?

Thanks!

:1234: Code to reproduce

pragma solidity ^0.8.4;

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

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

    Counters.Counter private _tokenIdCounter;

    constructor() ERC721("SwapValue", "SWV") {}

    function _baseURI() internal pure override returns (string memory) {
        return "";
    }

    function safeMint(address to, string memory uri) public onlyOwner {
        uint256 tokenId = _tokenIdCounter.current();
        _tokenIdCounter.increment();
        _safeMint(to, tokenId);
        _setTokenURI(tokenId, uri);
    }

    // The following functions are overrides required by Solidity.

    function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
        super._burn(tokenId);
    }

    function tokenURI(uint256 tokenId)
        public
        view
        override(ERC721, ERC721URIStorage)
        returns (string memory)
    {
        return super.tokenURI(tokenId);
    }
}
pragma solidity ^0.8.2;

// import "./Model.sol"
import { User } from "./Model.sol";
import "../contract/ISwapChain.sol";
import "../contract/SwapValue.sol";

contract SwapChain is ISwapChain, {

    // mapping (address => User) users;
    uint256 _usersCounter = 0;
    mapping(address => SwapValue[]) _users;
    
    constructor() {}

    function registerUser(address user) public override(address) returns (bool) {
        require(_users[user] == [], "User should not be registered yet or have any tokens.");
        _users[user] = [];
        _usersCounter++;
        return true;
    }

    function registerOffer(address user, address nft) public external override(address, address) returns (bool) {
        // TODO add SwapValue based on nft address to the _users[user] 
    }
}

:computer: Environment

Remix 0.25.1 , solidity 0.8.2, 0.8.4

Yesterday I was working on docs sample:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.2 <0.9.0;

contract InfoFeed {
    function info() public payable returns (uint ret) { return 42; }
}

contract Consumer {
    InfoFeed feed;
    function setFeed(InfoFeed addr) public { feed = addr; }
    function callFeed() public { feed.info{value: 10, gas: 800}(); }
}

which failed to calFeed() with exception:

[vm]
from: 0xAb8...35cb2
to: Consumer.callInfoPayable() 0x7EF...8CB47
value: 0 wei
data: 0x574...3ed13
logs: 0
hash: 0xbd4...90453

transact to Consumer.callInfoPayable errored: VM error: revert.

revert
    The transaction has been reverted to the initial state.
Note: The called function should be payable if you send value and the value you send should be less than your current balance.
Debug the transaction to get more information.

Error message points out on error payable modifier requirement which function already has. Today by strip the sample to simple external call I was able to execute the call:

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

contract InfoFeed {
    function info() public pure returns (uint) { return 42; }
    function infoPayable() public payable returns (uint) { return 42; }
}
// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

import "./InfoFeed.sol";

contract Consumer {

    InfoFeed feed;

    function setInfoFeed(address addr) public { feed = InfoFeed(addr); }

    function getInfo() public view returns (uint) { return feed.info(); }

}

Now the question is to fix contract call for payable function. Especially when this sample from the official docs and should work "as is".

Does you Consumer contract have Eth tokens? If not, that might be why it reverted when you tried sending value.