Solidity – Proper Way to Implement ‘Buyable’ ERC721 Tokens

erc-721marketssolidity

I have a contract that implements the ERC721 interface, and I want to allow token owners to set their tokens as "buyable", so that any other address can buy their token without any action on the part of the owner, given constraints are met (proper price, etc).

It seems that approve and transferFrom are only callable by the owner of the transferred token, not a to-be buyer. So there would not be a straightforward way to let a buyer take ownership of a token without a transaction from the seller first.

Whats the standard way to implement this? Examples such as this marketplace implementation seem to suggest that marking a token as "buyable" results in approving a third party contract that takes control of asset, and does the transfer on behalf of the owner. Curious if there's an easier way or if this is the process implied by the spec.

Best Answer

So I ended up going with a solution that used my contract as an intermediate third party. The flow goes as follows:

  1. The seller approves the contract address to act on their behalf.
  2. Then, when the buyer wants to buy the token, the contract transfers the token from the buyer to the seller, and transfers the payment as well

Something along the lines of:

function setForSale(uint256 _tokenId) external {
    address owner = ownerOf(_tokenId);

    require(isValidToken(_tokenId));
    require(owner == msg.sender || authorized[owner][msg.sender]);

    allowance[_tokenId] = address(this);
    tokensForSale.push(_tokenId);
    // set the sale price etc

    emit Approval(owner, address(this), _tokenId);
}

function buy(uint256 _tokenId) external payable {
    address buyer = msg.sender;
    uint payedPrice = msg.value;

    require(isValidToken(_tokenId));
    require(getApproved(_tokenId) == address(this));
    // require payedPrice >= salePrice

    // pay the seller
    // remove token from tokensForSale

    transferFrom(ownerOf(_tokenId), buyer, _tokenId);
}

Obviously you can do this with a third-party contract, but I found this easy and fairly straightforward using the given interface.