Assignment - Marketplace Contract Coding

Hello, I have adapted the provided contract interface slightly to my needs.

I managed to solve the assignment without keeping inactive offers in the contract. This allowed me to avoid the invisible kitty id=0 hack mentioned in the course.

Feedback is welcome!

Code

IKittyMarketplace.sol
pragma solidity ^0.8.0;

/*
 * Marketplace to trade kitties (should **in theory** be used for any ERC721 token)
 * It needs an existing Kitty contract to interact with
 * Note: it does not inherit from the kitty contracts
 * Note: The contract needs to be an operator for everyone who is selling through this contract.
 */
interface IKittyMarketplace {
  event MarketTransaction(
    string txType,
    uint256 indexed tokenId,
    address indexed caller
  );

  /**
   * Set the current KittyContract address and initialize the instance of Kittycontract.
   * Requirement: Only the contract owner can call.
   */
  function setKittyContract(address kittyContractAddress) external;

  /**
   * Get the details about a offer for tokenId. Throws an error if there is no active offer for tokenId.
   */
  function getOffer(uint256 tokenId)
    external
    view
    returns (address seller, uint256 price);

  /**
   * Get all tokenIds that are currently for sale. Returns an empty arror if none exist.
   */
  function getAllTokenOnSale()
    external
    view
    returns (uint256[] memory listOfOffers);

  /**
   * Creates a new offer for tokenId for the price price.
   * Emits the MarketTransaction event with txType "Create offer"
   * Requirement: Only the owner of tokenId can create an offer.
   * Requirement: There can only be one active offer for a token at a time.
   * Requirement: Marketplace contract (this) needs to be an approved operator when the offer is created.
   */
  function setOffer(uint256 price, uint256 tokenId) external;

  /**
   * Removes an existing offer.
   * Emits the MarketTransaction event with txType "Remove offer"
   * Requirement: Only the seller of tokenId can remove an offer.
   */
  function removeOffer(uint256 tokenId) external;

  /**
   * Executes the purchase of tokenId.
   * Sends the funds to the seller and transfers the token using transferFrom in Kittycontract.
   * Emits the MarketTransaction event with txType "Buy".
   * Requirement: The msg.value needs to equal the price of tokenId
   * Requirement: There must be an active offer for tokenId
   */
  function buyKitty(uint256 tokenId) external payable;
}

KittyMarketplace.sol
pragma solidity ^0.8.0;

import "./IKittyMarketplace.sol";
import "./Kitties.sol";

contract KittyMarketplace is IKittyMarketplace, Ownable {
Kitties private _kittyContract;

struct Offer {
  address seller;
  uint256 price;
  uint256 structPointer; // index of _offeredTokenIds
}
mapping(uint256 => Offer) private _tokenIdToOffer;
uint256[] private _offeredTokenIds;

constructor(address kittyContractAddress) {
  _kittyContract = Kitties(kittyContractAddress);
}

// Sets the Kitties contract instance.
function setKittyContract(address kittyContractAddress)
  external
  override
  onlyOwner
{
  _kittyContract = Kitties(kittyContractAddress);
}

// Gets details about the offer for tokenId.
function getOffer(uint256 tokenId)
  external
  view
  override
  returns (address seller, uint256 price)
{
  require(_offerExists(tokenId), "No offer for this token.");

  Offer storage offer = _tokenIdToOffer[tokenId];
  seller = offer.seller;
  price = offer.price;
}

// Gets all tokenIds that are currently for sale.
function getAllTokenOnSale()
  external
  view
  override
  returns (uint256[] memory listOfOffers)
{
  listOfOffers = _offeredTokenIds;
}

// Offers tokenId for price.
function setOffer(uint256 price, uint256 tokenId) external override {
  require(
    msg.sender == _kittyContract.ownerOf(tokenId),
    "Caller isn't owner of this token."
  );
  require(
    _kittyContract.isApprovedForAll(msg.sender, address(this)),
    "Contract must be approved as operator by caller."
  );

  uint256 index;
  if (_offerExists(tokenId)) {
    index = _tokenIdToOffer[tokenId].structPointer;
  } else {
    index = _offeredTokenIds.length;
    _offeredTokenIds.push(tokenId);
  }

  _tokenIdToOffer[tokenId] = Offer(msg.sender, price, index);
  emit MarketTransaction("Create offer", tokenId, msg.sender);
}

// Removes existing offer for tokenId.
function removeOffer(uint256 tokenId) external override {
  require(_offerExists(tokenId), "No offer for this token.");
  require(
    msg.sender == _tokenIdToOffer[tokenId].seller,
    "Caller isn't owner of this token."
  );

  _removeOffer(tokenId);
  emit MarketTransaction("Cancel offer", tokenId, msg.sender);
}

// Executes purchase of tokenId.
function buyKitty(uint256 tokenId) external payable override {
  require(_offerExists(tokenId), "No offer for this token.");
  address seller = _tokenIdToOffer[tokenId].seller;
  uint256 price = _tokenIdToOffer[tokenId].price;
  require(msg.sender != seller, "Caller is owner of this token.");
  require(msg.value == price, "Tx value doesn't match token price.");

  _removeOffer(tokenId);
  emit MarketTransaction("Buy", tokenId, msg.sender);

  // for sake of simplicity push payment pattern instead of recommended pull payment pattern
  payable(seller).transfer(msg.value);
  _kittyContract.safeTransferFrom(seller, msg.sender, tokenId);
}

// ----- NONPUBLIC FUNCTIONS -----

function _offerExists(uint256 tokenId) private view returns (bool) {
  return _tokenIdToOffer[tokenId].seller != address(0);
}

function _removeOffer(uint256 tokenId) private {
  require(_offerExists(tokenId), "No offer for this token.");

  Offer storage offer = _tokenIdToOffer[tokenId];
  uint256 removeTokenIndex = offer.structPointer;
  uint256 moveTokenId = _offeredTokenIds[_offeredTokenIds.length - 1];

  // reset offer data to zeros
  delete _tokenIdToOffer[tokenId];

  if (tokenId != moveTokenId) {
    // move last element of _offeredTokenIds from end position to removeTokenIndex
    _offeredTokenIds[removeTokenIndex] = moveTokenId;
    _tokenIdToOffer[moveTokenId].structPointer = removeTokenIndex;
  }

  // remove last element of _offeredTokenIds
  _offeredTokenIds.pop();
  emit MarketTransaction("Remove offer", tokenId, msg.sender);
}
}
1 Like

Says I have an error but for the life of me I cannot find it :frowning: its saying IMarketPlace is not found but I don’t understand why?..

Here is my code.
Marketplace.sol

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

import "./Kittycontract.sol";
import "./IMarketplace.sol";
import "./Ownable.sol";


contract Marketplace is Ownable, IMarketPlace{

    Kitties private _kittyId;

    struct Offer {
        address payable seller;
        uint256 price;
        uint256 index;
        uint256 tokenId;
        bool active;
    }

    Offer[] offers;

    event MarketTransaction(string TxType, address owner, uint256 tokenId);

    mapping (uint256 => Offer) tokenIdToOffer;

    constructor (address _kittyContractAddress) public {
        setKittyContract(_kittyContractAddress);
    }

    //Sets address for tokenContract and initializes it
    function  setKittyContract(address _KittyContractAddress) public OnlyOwner {
        _kittycontract = GetKitty(_KittyContractAddress);
    }

    function getOffer(uint256 _tokenId) 
        public 
        view 
        returns
    (
        address seller, 
        uint256 price,
        uint256 index,
        uint256 tokenId,
        bool active
    ) {
        Offer storage offer = tokenIdToOffer[_tokenId];

        return (
            offer.seller, 
            offer.price, 
            offer.index, 
            offer.tokenId, 
            offer.active
        );
    }

    function getAllTokenOnSale() public returns(uint256[] memory listOfOffers){

        uint256 totalOffers = offers.length; //Gets total number of offers

        if(offers.length == 0){
            return new uint256[](0);
        } else {

            uint256[] memory result = new uint256[](totalOffers);

            uint256 offerId;

            for (offerId = 0; offerId < totalOffers; offerId++){
                if(offers[offerId].active){
                    result[offerId] = offers[offerId].tokenId;
                }
            } 
            return result;
        }
    } //End of getAllTokenOnSale()

    function _ownsKitty(address _address, uint256 _tokenId) 
        internal
        view
        returns ( bool )
    {
            return (_KittyContractAddress.ownerOf(_tokenId) == _address);
    }

    function setOffer(uint256 _price, uint256 _tokenId) public {
        require(
            _ownsKitty(msg.sender, _tokenId), 
            "You must own the bear you want to sell!"
        );
        require(tokenIdToOffer[_tokenId].active == false, "You cannot sell the same bear twice!");
        require(_kittycontract.isApprovedForAll(msg.sender, address(this)), "The Token Contract does not have permission to transfer your tokens!");

        Offer memory _offer = Offer({
            seller: msg.sender,
            price: _price,
            index: offers.length,
            tokenId: _tokenId,
            active: true
        });

        tokenIdToOffer[_tokenId] = _offer;
        offers.push(_offer);

        emit MarketTransaction("Create Offer", msg.sender, _tokenId);
    }

    function removeOffer(uint256 _tokenId) public {
        Offer memory offer = tokenIdToOffer[_tokenId];
        require(
            offer.seller == msg.sender, "Only seller can remove Kitty from sale!"
        );
      

        delete offer;
        offers[offer.index].active = false;

        emit MarketTransaction("Remove Offer", msg.sender, _tokenId);
    }

    function buyKitty(uint256 _tokenId) public payable {
        Offer memory offer = tokenIdToOffer[_tokenId];
        require(msg.value == offer.price, "Incorrect price!");
        require(tokenIdToOffer[_tokenId].active == true, "Kitty is not on sale currently!");

        delete offer;
        offers[offer.index].active == false;

        if (offer.price > 0){
            offer.seller.transfer(offer.price);
        }

        _kittycontract.transferFrom(offer.seller, msg.sender, _tokenId);

        emit MarketTransaction("Kitty NFT Sale", msg.sender, _tokenId);
    }


    
    
}

IMarketplace.sol

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


import "./Kittycontract.sol";
import "./Ownable.sol";

/*
 * Market place to trade kitties (should **in theory** be used for any ERC721 token)
 * It needs an existing Kitty contract to interact with
 * Note: it does not inherit from the kitty contracts
 * Note: The contract needs to be an operator for everyone who is selling through this contract.
 */
interface IMarketPlace {

    event MarketTransaction(string TxType, address owner, uint256 tokenId);

    /**
    * Set the current KittyContract address and initialize the instance of Kittycontract.
    * Requirement: Only the contract owner can call.
     */
    function setKittyContract(address _kittyContractAddress) external;

    /**
    * Get the details about a offer for _tokenId. Throws an error if there is no active offer for _tokenId.
     */
    function getOffer(uint256 _tokenId) external view returns ( address seller, uint256 price, uint256 index, uint256 tokenId, bool active);

    /**
    * Get all tokenId's that are currently for sale. Returns an empty array if none exist.
     */
    function getAllTokenOnSale() external returns(uint256[] memory listOfOffers);

    /**
    * Creates a new offer for _tokenId for the price _price.
    * Emits the MarketTransaction event with txType "Create offer"
    * Requirement: Only the owner of _tokenId can create an offer.
    * Requirement: There can only be one active offer for a token at a time.
    * Requirement: Marketplace contract (this) needs to be an approved operator when the offer is created.
     */
    function setOffer(uint256 _price, uint256 _tokenId) external;

    /**
    * Removes an existing offer.
    * Emits the MarketTransaction event with txType "Remove offer"
    * Requirement: Only the seller of _tokenId can remove an offer.
     */
    function removeOffer(uint256 _tokenId) external;

    /**
    * Executes the purchase of _tokenId.
    * Sends the funds to the seller and transfers the token using transferFrom in Kittycontract.
    * Emits the MarketTransaction event with txType "Buy".
    * Requirement: The msg.value needs to equal the price of _tokenId
    * Requirement: There must be an active offer for _tokenId
     */
    function buyKitty(uint256 _tokenId) external payable;
}

Hey @Mickey_McClimon, hope you are well.

You have a misspell when importing your market place interface. :eyes:

Carlos Z

2 Likes

Assignment:
KittyMarketPlace.sol

// SPDX-License-Identifier: UNLICENSED
pragma solidity >0.4.22 <=0.9.0;
import './IKittyMarketPlace.sol';
import './Ownable.sol';
import './kittyContract.sol';

contract KittyMarketPlace is IKittyMarketPlace,Ownable{
    KittyContract private _kittyContract;
    struct Offer{
        address payable seller;
        uint price;
        uint tokenId;
        uint index;
        bool active;
    }
    Offer[] offers;
    mapping(uint => Offer)tokenIdtoOffer;

    constructor(address _kittyAddress){
        setKittyContract(_kittyAddress);
    }

    function setKittyContract(address _kittyContractAddress) public override onlyOwner{
       _kittyContract = KittyContract(_kittyContractAddress);
    }

    function getOffer(uint256 _tokenId) external view override returns ( address seller, 
                                                                uint256 price, 
                                                                uint256 index, 
                                                                uint256 tokenId, 
                                                                bool active){
        require(isActive(_tokenId), 'No active offer for this tokenId');
        Offer memory offer = tokenIdtoOffer[_tokenId];
        seller = offer.seller;
        price = offer.price;
        index = offer.index;
        tokenId = _tokenId;
        active = offer.active;
    }
    
    function getAllTokenOnSale() external view override returns(uint256[] memory listOfOffers){
        uint offersLength = offers.length;
        require(offersLength != 0, 'No offers available');
        for(uint i = 0 ; i < offersLength ; i++){
            listOfOffers[i] = offers[i].tokenId;
        }
    }
    
    function setOffer(uint256 _price, uint256 _tokenId) external override{
       require(_kittyContract.owns(msg.sender, _tokenId),'Only owners can create an offer');
       require(!isActive(_tokenId),'One offer already exists');
       require(_kittyContract.getApproved(_tokenId) == address(this),'Market place contract is not an operator');
       _setOffer(_price,_tokenId);
    }
    
    function _setOffer(uint256 _price, uint256 _tokenId) internal {
       offers.push(Offer(payable(msg.sender),_price,_tokenId,offers.length,true));
       tokenIdtoOffer[_tokenId] = offers[offers.length-1];
       
       emit MarketTransaction("Create offer",msg.sender,_tokenId);
    }

    function removeOffer(uint256 _tokenId) external override{
       require(tokenIdtoOffer[_tokenId].seller == payable(msg.sender),'Only sellers can remove an offer');
       _removeOffer(_tokenId);
    }

    function _removeOffer(uint256 _tokenId) internal{
       uint rowToRemove = tokenIdtoOffer[_tokenId].index;
       offers[rowToRemove] = offers[offers.length-1];
       offers.pop();
       delete tokenIdtoOffer[_tokenId];

       emit MarketTransaction("Remove offer",msg.sender,_tokenId);
    }
    
    function buyKitty(uint256 _tokenId) external override payable{
        require(isActive(_tokenId),'This kitty is not available for sell');
        require(msg.value == tokenIdtoOffer[_tokenId].price,'Please send correct amount');

        _kittyContract.transferFrom(tokenIdtoOffer[_tokenId].seller, msg.sender, _tokenId);

        (bool result,) = tokenIdtoOffer[_tokenId].seller.call{value: msg.value}("");
        require(result,'Unsuccessful transfer of funds');

        emit MarketTransaction("Buy",msg.sender,_tokenId);
    }

    function isActive(uint _tokenId) internal view returns(bool){
        return tokenIdtoOffer[_tokenId].active;
    }

}
1 Like

It wasnet ment to be a mispelling, it was the name of my other contract i inherited but i had to switch the spelling due to the same name for the files haha i was getting so confused.

it doesnt allow me to compute any array lenght what do you think is the problem?

tests/mapping.sol:69:29: TypeError: Member "lenght" not found or not visible after argument-dependent lookup in uint256[] storage ref.
uint totalOffers = array.lenght;
^----------^

hey @denny ! Please send a picturo or copy paste the code here. What i notice is that you just wrote lenght and should be length the difference is the end of the word.

1 Like

thanks such a stupid typo hahahah

the most common typo error of all !! (it happens to me a lot too haha, until you get use to stop your brain a moment and type the word properly :rofl:)

Carlos Z

1 Like

Hey guys, here is my marketplace contract. I haven’t tested anything yet, so there might be some mistakes left. Will wait till my front-end is ready… Was too lazy to try everything out in remix :sweat_smile:

MarketPlace contract assignment:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.6;

import "./interface/ICatMarketplace.sol";
import "./Catcontract.sol";


contract CatMarketplace is ICatMarketPlace, Ownable {
    Catcontract private _catContract;


    /*Storage:
     **********/

    struct Offer {
        address payable seller;
        uint256 price;
        uint256 index;
        uint256 tokenId;
        bool active;
    }

    Offer[] offers;

    mapping(uint256 => Offer) tokenIdToOffer;
    mapping(uint256 => uint256) tokenIdToOfferId;


    /*Constructor:
     **************/

    constructor(address _catContractAddress) {
        _catContract = Catcontract(_catContractAddress);
    }


    /*Functions:
     ************/

    //Set the current Catcontract address and initialize the instance of Catcontract:
    function setCatContract(address _catContractAddress) external onlyOwner {
        _catContract = Catcontract(_catContractAddress);
    }

    //Get the details about a offer for _tokenId.
    function getOffer(uint256 _tokenId)
        external
        view
        override
        returns (
            address seller,
            uint256 price,
            uint256 index,
            uint256 tokenId,
            bool active
        )
    {
        require(_isOffer(_tokenId), "This cat isn't for sale!");
        
        Offer memory offer = tokenIdToOffer[_tokenId];

        return (offer.seller, offer.price, offer.index, offer.tokenId, offer.active);
    }

    //Get all tokenId's that are currently for sale. Returns an empty array if none exist.
    function getAllTokenOnSale()
        external
        view
        override
        returns (uint256[] memory listOfOffers)
    {
        uint256 totalOffers = offers.length;

        if (totalOffers == 0) {
            return new uint256[](0);
        } else {
            listOfOffers = new uint256[](totalOffers);

            for (uint256 offerId = 0; offerId < totalOffers; offerId++) {
                if (offers[offerId].active != false)
                    listOfOffers[offerId] = offers[offerId].tokenId;
            }
        }

        return listOfOffers;
    }

    // Creates a new offer for _tokenId for the price _price.
    function setOffer(uint256 _price, uint256 _tokenId) external override {
        require(
            msg.sender == _catContract.ownerOf(_tokenId),
            "You do not own this cat!"
        );
        require(
            tokenIdToOffer[_tokenId].active == false,
            "This cat is already on sale!"
        );

        _catContract.approve(address(this), _tokenId);

        Offer memory _offer = Offer({
            seller: payable(msg.sender),
            price: _price,
            index: offers.length,
            tokenId: _tokenId,
            active: true
        });

        offers.push(_offer);

        tokenIdToOffer[_tokenId] = _offer;
        tokenIdToOfferId[_tokenId] = _offer.index;

        emit MarketTransaction("Create offer", msg.sender, _tokenId);
    }

    // Removes an existing offer.
    function removeOffer(uint256 _tokenId) external override {
        require(
            msg.sender == tokenIdToOffer[_tokenId].seller,
            "This cat isn't yours!"
        );
        require(_isOffer(_tokenId), "This cat isn't on sale!");

        // Delete offer's info:
        delete offers[tokenIdToOfferId[_tokenId]];

        // Remove offer from mapping:
        delete tokenIdToOffer[_tokenId];

        // Delete token approval:
        _catContract.deleteApproval(_tokenId);

        emit MarketTransaction("Cancel offer", msg.sender, _tokenId);
    }

    //Executes the purchase of _tokenId.
    function buyCat(uint256 _tokenId) external payable override {
        require(_isOffer(_tokenId), "This cat isn't for sale!");
        require(
            msg.sender != tokenIdToOffer[_tokenId].seller,
            "This is already your cat!"
        );
        require(msg.value == tokenIdToOffer[_tokenId].price, "Wrong price!");

        // Delete offer info
        delete offers[tokenIdToOfferId[_tokenId]];
        // Remove offer in mapping
        delete tokenIdToOffer[_tokenId];

        tokenIdToOffer[_tokenId].seller.transfer(msg.value);
        _catContract.safeTransferFrom(tokenIdToOffer[_tokenId].seller, msg.sender, _tokenId);

        emit MarketTransaction("Buy", msg.sender, _tokenId);
    }

    function _isOffer(uint256 _tokenId) private view returns (bool) {
        return tokenIdToOffer[_tokenId].active == true;
    }
}
1 Like

I took some help from Filip with the getAllTokensOnSale() function, but the rest was fairly straightforward.

For the setOffer() function, instead of requiring there not be any active offers on the tokenId, if moved through an if/else, where if there is an active order, it updates the price.
But if there is no active order, a new offer is made, as shown below:

    function setOffer(uint256 _price, uint256 _tokenId) external {
        require(kittyContract.ownerOf(_tokenId) == msg.sender);
        require(kittyContract.getApproved(_tokenId) == address(this) || kittyContract.isApprovedForAll(kittyContract.ownerOf(_tokenId), address(this)) == true, "The Marketplace is not approved to sell this kitty");
        _setOffer(_price, _tokenId);
        emit MarketTransaction("Create offer", msg.sender, _tokenId);
    }

    function _setOffer(uint256 _price, uint256 _tokenId) internal {
        if(tokenIdToOffer[_tokenId].active == false) {
            Offer memory _offer = Offer({
                seller: msg.sender,
                price: _price,
                index: offers.length,
                tokenId: _tokenId,
                active: true
            });

            tokenIdToOffer[_tokenId] = _offer;
            offers.push(_offer);
        } else {
            tokenIdToOffer[_tokenId].price = _price;
            tokenIdToOffer[_tokenId].active = true;
        }
    }

I also implemented a redeemableBalance mapping and corresponding redeem() function, where the the seller gets their funds into, and can withdraw (or redeem) at a later date.
This is in the interest of security and saving on gas fees.

Here is how this was implemented.

    function buyKitty(uint256 _tokenId) external payable {
        Offer memory offer = tokenIdToOffer[_tokenId];
        require(offer.active == true, "This kitty is not for sale");
        require(msg.value >= tokenIdToOffer[_tokenId].price, "You sent less than the asking price");
        require(msg.sender != offer.seller, "You can't buy your own kitty");

        delete tokenIdToOffer[_tokenId];
        offers[offer.index].active = false;

        redeemableBalance[offer.seller] += msg.value;

        kittyContract.transferFrom(offer.seller, msg.sender, _tokenId);
        
        emit MarketTransaction("Buy", msg.sender, _tokenId);
    }

    function redeem(uint _amount) external {
        require(_amount <= redeemableBalance[msg.sender], "You cannot Withdraw More than your redeemable balance are owed");
        payable(msg.sender).transfer(_amount);
    }

There’s probably a good dose of optimizations to be done but here’s the code as it stands now:

pragma solidity ^0.8.0;

import './IKittyMarketplace.sol';
import './Ownable.sol';

contract KittyMarketplace is Ownable {

    Kittycontract kittyContract;
    event MarketTransaction(string TxType, address owner, uint256 tokenId);
    
    struct Offer {
        address seller;
        uint256 price;
        uint256 index;
        uint256 tokenId;
        bool active;
    }

    Offer[] public offers;
    mapping (uint => Offer) tokenIdToOffer;
    mapping (address => uint) redeemableBalance;

    constructor(address _kittyContractAddress) {
        setKittyContract(_kittyContractAddress);
    }

    function setKittyContract(address _kittyContractAddress) onlyOwner public {
        kittyContract = Kittycontract(_kittyContractAddress);
    }

    function getOffer(uint256 _tokenId) external returns (address seller, uint256 price, uint256 index, uint256 tokenId, bool active) {
        return _getOffer(_tokenId);
        require(active == true, "There is No Active Offer For this Kitty");
    }

    function _getOffer(uint256 _tokenId) internal returns (address seller, uint256 price, uint256 index, uint256 tokenId, bool active) {
        seller = tokenIdToOffer[_tokenId].seller;
        price = tokenIdToOffer[_tokenId].price;
        index = tokenIdToOffer[_tokenId].index;
        tokenId = tokenIdToOffer[_tokenId].tokenId;
        active = tokenIdToOffer[_tokenId].active;
    }

    function getAllTokenOnSale() external view returns(uint256[] memory listOfOffers) {
        uint totalOffers = offers.length;
        if(totalOffers == 0) {
            return new uint[](0);
        }

        uint[] memory result = new uint[](totalOffers);

        uint offerId;
        for(offerId = 0; offerId < totalOffers; offerId++) {
            if(offers[offerId].active == true) {
                result[offerId] = offers[offerId].tokenId;
            }
        }
        return result;
    }

    function setOffer(uint256 _price, uint256 _tokenId) external {
        require(kittyContract.ownerOf(_tokenId) == msg.sender);
        require(kittyContract.getApproved(_tokenId) == address(this) || kittyContract.isApprovedForAll(kittyContract.ownerOf(_tokenId), address(this)) == true, "The Marketplace is not approved to sell this kitty");
        _setOffer(_price, _tokenId);
        emit MarketTransaction("Create offer", msg.sender, _tokenId);
    }

    function _setOffer(uint256 _price, uint256 _tokenId) internal {
        if(tokenIdToOffer[_tokenId].active == false) {
            Offer memory _offer = Offer({
                seller: msg.sender,
                price: _price,
                index: offers.length,
                tokenId: _tokenId,
                active: true
            });

            tokenIdToOffer[_tokenId] = _offer;
            offers.push(_offer);
        } else {
            tokenIdToOffer[_tokenId].price = _price;
            tokenIdToOffer[_tokenId].active = true;
        }
    }

    function removeOffer(uint256 _tokenId) external {
        require(tokenIdToOffer[_tokenId].active == true, "There is no offer to remove for this kitty");
        require(tokenIdToOffer[_tokenId].seller == msg.sender, "You are not the owner of that kitty");
        _removeOffer(_tokenId);
        emit MarketTransaction("Remove offer", msg.sender, _tokenId);
    }

    function _removeOffer(uint256 _tokenId) internal {
        delete tokenIdToOffer[_tokenId];
        offers[tokenIdToOffer[_tokenId].index].active = false;
    }

    function buyKitty(uint256 _tokenId) external payable {
        Offer memory offer = tokenIdToOffer[_tokenId];
        require(offer.active == true, "This kitty is not for sale");
        require(msg.value >= tokenIdToOffer[_tokenId].price, "You sent less than the asking price");
        require(msg.sender != offer.seller, "You can't buy your own kitty");

        delete tokenIdToOffer[_tokenId];
        offers[offer.index].active = false;

        redeemableBalance[offer.seller] += msg.value;

        kittyContract.transferFrom(offer.seller, msg.sender, _tokenId);
        
        emit MarketTransaction("Buy", msg.sender, _tokenId);
    }

    function redeem(uint _amount) external {
        require(_amount <= redeemableBalance[msg.sender], "You cannot Withdraw More than your redeemable balance are owed");
        payable(msg.sender).transfer(_amount);
    }
}
1 Like

This assignment is a great learning experience, cementing what you have learned from the course and past courses when it comes to building a smart contract. Thank you Moralis Academy.

pragma solidity ^0.8;

import "./MyKitty.sol";
import "./IKittyMarketplace.sol";

contract KittyMarketplace is IKittyMarketPlace {

    MyKitty private _myKittyContract;
    address owner;

    struct Offer {
        address payable seller;
        uint price;
        uint index;
        uint tokenId;
        bool active;
    }

    Offer[] offers;

    mapping(uint => Offer) tokenIdToOffer;
    
    modifier onlyOwner {
        require(msg.sender == owner);
        _;
    }

    function setKittyContract(address _kittyContractAddress) external onlyOwner {
        _myKittyContract = MyKitty(_kittyContractAddress);
    }

    function getOffer(uint256 _tokenId) external view returns (address seller, uint256 price, uint256 index, uint256 tokenId, bool active) {
        require(tokenIdToOffer[_tokenId].active, "No active offer for the given token ID");

        seller = tokenIdToOffer[_tokenId].seller;
        price = tokenIdToOffer[_tokenId].price;
        index = tokenIdToOffer[_tokenId].index;
        tokenId = tokenIdToOffer[_tokenId].tokenId;
        active = tokenIdToOffer[_tokenId].active;
    }

    function getAllTokenOnSale() external view  returns(uint256[] memory listOfOffers) {
        uint tempIndex = 0;
        for(uint i = 0; i < offers.length; i++) {
            if (offers[i].active) {
                listOfOffers[tempIndex] = offers[i].tokenId;
                tempIndex++;
            }
        }
    }

    function setOffer(uint256 _price, uint256 _tokenId) external {
        require(_myKittyContract.tokenOwner(_tokenId) == msg.sender, "Only the owner can create an offer for the token");
        require(!tokenIdToOffer[_tokenId].active, "Only one offer is allowed at a time");
        require(_myKittyContract.tokenToApproved(_tokenId) == address(this) || _myKittyContract.isApprovedForAll(msg.sender, address(this)), "Marketplace should be an approved operator of the token");

        _setOffer(msg.sender, _price, _tokenId);

        emit MarketTransaction("Create Offer", msg.sender, _tokenId);
    }

    function _setOffer(address _seller, uint256 _price, uint256 _tokenId) private {
        Offer memory newOffer = Offer(payable(_seller), _price, offers.length, _tokenId, true);
        offers.push(newOffer);
        tokenIdToOffer[_tokenId] = newOffer;
    }

    function removeOffer(uint256 _tokenId) external {
        require(tokenIdToOffer[_tokenId].seller == msg.sender, "Only the seller can remove the offer");

        _removeOffer(_tokenId);

        emit MarketTransaction("Remove offer", msg.sender, _tokenId);
    }

    function _removeOffer(uint256 _tokenId) private {
        offers[tokenIdToOffer[_tokenId].index].active = false;
        tokenIdToOffer[_tokenId].active = false;        
    }

    function buyKitty(uint256 _tokenId) external payable {
        require(tokenIdToOffer[_tokenId].active, "There should be an active offer");
        require(tokenIdToOffer[_tokenId].price == msg.value, "Funds is not sufficient");

        _buyKitty(tokenIdToOffer[_tokenId].seller, msg.sender, _tokenId);
        _removeOffer(_tokenId);

        (bool sent, ) = tokenIdToOffer[_tokenId].seller.call{value: msg.value}("");
        require(sent, "Failed to send payment to seller.");

        emit MarketTransaction("Buy", msg.sender, _tokenId);
    }

    function _buyKitty(address _seller, address _buyer, uint256 _tokenId) private {
        _myKittyContract.safeTransferFrom(_seller, _buyer, _tokenId);
    }
}
2 Likes

If we structure the marketplace contract to inherit from the base contract, there’s no reason to create a new instance of the base contract, and therefore we can omit the setKittyContract() function, right?

pragma solidity 0.8.0;

import "./KittyToken.sol";

contract KittyMarketplace is KittyToken {

  event MarketTransaction(string TxType, address owner, uint256 tokenId);

  struct Offer {
    address payable seller;
    uint256 price;
    uint256 tokenId;
    bool active;
  }

  Offer[] offers;

  mapping(uint256 => Offer) public tokenIdToOffer;

  function getOffer(uint256 _tokenId) public view returns (
    address seller,
    uint256 price,
    uint256 tokenId,
    bool active) {
      require(tokenIdToOffer[_tokenId].active, "There is no active offer for this kitty");
      Offer memory offer = tokenIdToOffer[_tokenId];
      return (offer.seller, offer.price, offer.tokenId, offer.active);
  }

  function getAllTokenOnSale() public view returns(uint256[] memory) {
    uint256 totalOffers = offers.length;
    if(totalOffers == 0) {
      return new uint256[](0);
    }
    uint256[] memory result = new uint256[](totalOffers);

    for(uint256 i = 0; i < offers.length; i++) {
      if(offers[i].active) {
        result[i] = offers[i].tokenId;
      }
    }
    return result;
  }

  function setOffer(uint256 _price, uint256 _tokenId) public {
    require(_owns(msg.sender, _tokenId), "You must be the owner to create an offer");
    require(tokenIdToOffer[_tokenId].active == false, "There is already an active offer for this kitty");
    approve(address(this), _tokenId);

    Offer memory _offer = Offer({
      seller: payable(msg.sender),
      price: _price,
      tokenId: _tokenId,
      active: true
    });
    offers.push(_offer);
    tokenIdToOffer[_tokenId] = _offer;

    emit MarketTransaction("Create offer", msg.sender, _tokenId);
  }

  function removeOffer(uint256 _tokenId) public {
    require(_owns(msg.sender, _tokenId), "You must be the owner to remove an offer");

    Offer memory offerToRemove = tokenIdToOffer[_tokenId];
    offerToRemove.active = false;
    delete tokenIdToOffer[_tokenId];
    delete kittyIndexToApproved[_tokenId];

    emit MarketTransaction("Remove offer", msg.sender, _tokenId);
  }

  function buyKitty(uint256 _tokenId) public payable {
    Offer memory kittyForSale = tokenIdToOffer[_tokenId];
    require(kittyForSale.active, "This kitty is not for sale");
    require(msg.value == kittyForSale.price, "Transfer amount is incorrect");

    kittyForSale.active = false;
    delete tokenIdToOffer[_tokenId];

    _approve(msg.sender, _tokenId);
    transferFrom(kittyForSale.seller, msg.sender, _tokenId);
    kittyForSale.seller.transfer(msg.value);

    emit MarketTransaction("Buy", msg.sender, _tokenId);
  }

}

Hey @Attiss ! Thats a way but the best practice is to keep it separate, so they dont have to depend on each other.

Thanks @kenn.eth - could you please explain a little more about this concept? In this case, the marketplace contract definitely has to depend on the mappings and variables from the base contract, but the base contract doesn’t really depend on the marketplace contract. It seemed so much cleaner to use inheritance and not have multiple ABIs, addresses, migrations, etc. What advantages do we gain by keeping these contracts separate?

1 Like

Hi, this is my code:

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.4.22 <=0.9.0;

import "./IERC721.sol";
import "./IKittiesMarketplace.sol";
import "./Ownable.sol";

contract KittiesMarketplace is Ownable, IKittiesMarketPlace {
    IERC721 private kittiesContract;

    struct Offer {
        address seller;
        uint256 price;
        uint256 index;
        uint256 tokenId;
        bool active;
    }

    Offer[] offers;

    mapping(uint256 => Offer) tokenIdToOffer;

    constructor(address kittiyContractAddress_) {
        setKittyContract(kittiyContractAddress_);
    }

    function setKittyContract(address _kittyContractAddress) public onlyOwner {
        kittiesContract = IERC721(_kittyContractAddress);
    }

    function getOffer(uint256 tokenId_)
        public
        view
        returns (
            address _seller,
            uint256 _price,
            uint256 _index,
            uint256 _tokenId,
            bool _active
        )
    {
        require(
            _isOfferActive(tokenId_),
            "Marketplace: No offer is active for this token"
        );
        Offer memory _offer = tokenIdToOffer[tokenId_];

        _seller = _offer.seller;
        _price = _offer.price;
        _index = _offer.index;
        _tokenId = _offer.tokenId;
        _active = _offer.active;
    }

    function getAllTokenOnSale()
        external
        view
        returns (uint256[] memory _listOfOffers)
    {
        uint256[] memory _allOffers = new uint256[](totalOffer());
        for (uint256 i = 0; i < totalOffer(); i++) {
            _allOffers[i] = offers[i].tokenId;
        }
        _listOfOffers = _allOffers;
    }

    function totalOffer() public view returns (uint256) {
        return offers.length;
    }

    function setOffer(uint256 price_, uint256 tokenId_) external {
        //  * Requirement: Only the owner of _tokenId can create an offer.
        require(
            _owns(msg.sender, tokenId_),
            "Marketplace: Only owner of the token can create an offer"
        );
        //  * Requirement: There can only be one active offer for a token at a time.
        require(
            !_isOfferActive(tokenId_),
            "Marketplace: A token can only has one active offer"
        );
        //  * Requirement: Marketplace contract (this) needs to be an approved operator when the offer is created.
        require(
            kittiesContract.isApprovedForAll(msg.sender, address(this)) ||
                kittiesContract.isApprovedForThis(address(this), tokenId_),
            "Marketplace: You should approve the token before you can set offer"
        );
        _setOffer(msg.sender, price_, tokenId_);
    }

    function _setOffer(
        address seller_,
        uint256 price_,
        uint256 tokenId_
    ) private {
        Offer memory _newOffer = Offer({
            seller: seller_,
            price: price_,
            index: totalOffer(),
            tokenId: tokenId_,
            active: true
        });

        offers.push(_newOffer);
        tokenIdToOffer[tokenId_] = _newOffer;

        //  * Emits the MarketTransaction event with txType "Create offer"
        emit MarketTransaction("Create offer", seller_, tokenId_);
    }

    function removeOffer(uint256 tokenId_) external {
        // Requirement: Only the seller of _tokenId can remove an offer.
        require(
            _owns(msg.sender, tokenId_),
            "Marketplace: Only the seller can remove it"
        );
        _removeOffer(msg.sender, tokenId_);
    }

    function _removeOffer(address owner_, uint256 tokenId_) private {
        // Removes an existing offer from mapping
        Offer storage _offer = tokenIdToOffer[tokenId_];
        _offer.active = false;

        // Removes the existing offer from array
        uint256 _offerIndex = _offer.index;
        uint256 _lastOfferIndex = totalOffer() - 1;

        offers[_offerIndex] = offers[_lastOfferIndex];
        offers.pop();

        // Emits the MarketTransaction event with txType "Remove offer"
        emit MarketTransaction("Remove offer", owner_, tokenId_);
    }

    function buyKitty(uint256 tokenId_) external payable {
        Offer memory _offer = tokenIdToOffer[tokenId_];
        address _seller = _offer.seller;
        // Requirement: There must be an active offer for _tokenId
        require(
            _isOfferActive(tokenId_),
            "Marketplace: Token is not available to purchase"
        );
        // Requirement: The msg.value needs to equal the price of _tokenId
        require(
            msg.value == _offer.price,
            "Marketplace: The amount you enter is too small"
        );

        _buyKitty(_seller, tokenId_);
    }

    function _buyKitty(address seller_, uint256 tokenId_) private {
        /**
         * Executes the purchase of _tokenId.
         * Sends the funds to the seller and transfers the token using transferFrom in Kittycontract.
         */
        (bool _sent, ) = seller_.call{value: msg.value}("");
        require(_sent, "Marketplace: Failed to send Ether");

        kittiesContract.safeTransferFrom(seller_, msg.sender, tokenId_);
        _removeOffer(seller_, tokenId_);

        // Emits the MarketTransaction event with txType "Buy".
        emit MarketTransaction("Buy", seller_, tokenId_);
    }

    function _isOfferActive(uint256 tokenId_) internal view returns (bool) {
        Offer memory _offer = tokenIdToOffer[tokenId_];
        return _offer.active;
    }

    function _owns(address claimant_, uint256 tokenId_)
        internal
        view
        returns (bool)
    {
        return kittiesContract.ownerOf(tokenId_) == claimant_;
    }
}

2 Likes

I made a few modifications so the contract makes more sense:

//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./IMoralisKitties";
import "../node_modules/@openzeppelin/contracts/access/Ownable";
import "./IKittyMarketPlace.sol";

contract Marketplace is Ownable, IKittyMarketPlace{
    uint private indexCount;
    bool private paused;

    constructor() Ownable(){}
   
    IMoralisKitties private MoralisKitties;
 
    modifier notPaused(){//it should not be possible to use the smart contract until the kitties contract is instanciated
        require(paused==false, "Marketplace : not ready yet");
        _;
    }
    struct Offer{
        address payable seller;
        uint price;
        uint index;
        uint tokenId;
        bool active;
    }

    Offer [] offers;
    mapping(uint => Offer) tokenIdOffer;


    event MarketTransaction(string TxType, address owner, uint256 tokenId);

    function setKittyContract(address _kittyContractAddress) public onlyOwner{
        MoralisKitties = IMoralisKitties(_kittyContractAddress);
        paused = false;
    }


    function getOffer(uint256 _tokenId) external view returns ( address seller, uint256 price, uint256 index, uint256 tokenId){
        require(tokenIdOffer[_tokenId].active,"Marketplace: that token is not on sale");
        seller = tokenIdOffer[_tokenId].seller;
        price = tokenIdOffer[_tokenId].price;
        index = tokenIdOffer[_tokenId].index;
        tokenId = tokenIdOffer[_tokenId].tokenId;
    }

    
    function getAllTokenOnSale() public view  returns(uint256[] memory){
        uint256 [] listOfOffers;
        for(int i=0; i<offers.length; i++){
            if(offers[i].active){
                listOfOffers.push(offers[i].tokenId);

            }
        }
        return listOfOffers;
    }   

    function setOffer(uint256 _price, uint256 _tokenId) public notPaused{
        require(MoralisKitties.ownerOf(_tokenId)=msg.sender, "Marketplace: Seller doesnt own this token");
        require(!tokenIdOffer[_tokenId].active, "Marketplace : There is offer for that token already");
        require(MoralisKitties.getApproved(_tokenId)==address(this));
        offers.push(Offer(payable(msg.sender), _price , indexCount,_tokenId,true));
        tokenIdOffer[_tokenId]=Offer(payable(msg.sender), _price , indexCount,_tokenId,true);
        indexCount++;
        emit MarketTransaction("Create offer",msg.sender,_tokenId);



    }

    function removeOffer(uint256 _tokenId) public{
        require(MoralisKitties.owner(_tokenId)==msg.sender || msg.sender ==address(this));//the contract should be able to delete offers to
        delete offers[tokenIdOffer[_tokenId].index];
        delete tokenIdOffer[_tokenId];
        emit MarketTransaction("Remove offer", msg.sender, _tokenId);

    }

    function buyKitty(uint256 _tokenId) public payable{
        require(tokenIdOffer[_tokenId].active);
        require (msg.value==tokenIdOffer[_tokenId].price);
        (bool success,)=tokenIdOffer[_tokenId].sender.call{value : msg.value}("");
        MoralisKitties.transferFrom(tokenIdOffer[_tokenId].sender, msg.sender, _tokenId);
        emit MarketTransaction("Buy", msg.sender, _tokenId);
        removeOffer(_tokenId);



    }

    
}```
2 Likes

KittyMarketplace.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.5.12;

import "./Kittycontract.sol";
import "./Ownable.sol";
import "./IKittyMarketplace.sol";

contract KittyMarketplace is Ownable, IKittyMarketPlace {
  Kittycontract private _kittyContract;
  bool private kittyContractStarted = false;
  uint256 private indexCounter;

  modifier isKittyContractStarted() {
    require(kittyContractStarted == true, "KittyContract not started: marketplace waiting");
    _;
  }

  struct Offer {
    address payable seller;
    uint256 price;
    uint256 index;
    uint256 tokenId;
    bool active;
  }
  Offer[] private allOffers;

  mapping(uint256 => Offer) tokenIdToOffer;

  event MarketTransaction(string TxType, address owner, uint256 tokenId);

  constructor(address _kittyContractAddress) public {
    setKittyContract(_kittyContractAddress);
  }

  function setKittyContract(address _kittyContractAddress) public onlyOwner {
    _kittyContract = Kittycontract(_kittyContractAddress);
    kittyContractStarted = true;
  }

  /**
  * Get the details about a offer for _tokenId. Throws an error if there is no active offer for _tokenId.
  */
  function getOffer(uint256 _tokenId) external view returns ( address seller, uint256 price, uint256 index, uint256 tokenId, bool active) {
    require(tokenIdToOffer[_tokenId].active, "No Offer for tokenId active");

    Offer memory offer = tokenIdToOffer[_tokenId];
    seller  = offer.seller;
    price   = offer.price;
    index   = offer.index;
    tokenId = offer.tokenId;
    active  = offer.active;
  }

  /**
  * Get all tokenId's that are currently for sale. Returns an empty arror if none exist.
  */
  function getAllTokenOnSale() external view returns(uint256[] memory) {
    uint256[] memory _listOfOffers = new uint256[](allOffers.length);
    uint256 n = 0;
    for(uint256 i=0; i<allOffers.length; i++) {
      if (allOffers[i].active == true) { // check for price > 0 ??
        _listOfOffers[ n++ ] = allOffers[i].tokenId;
      }
    }
    return _listOfOffers;
  }

  /**
  * Creates a new offer for _tokenId for the price _price.
  * Emits the MarketTransaction event with txType "Create offer"
  * Requirement: Only the owner of _tokenId can create an offer.
  * Requirement: There can only be one active offer for a token at a time.
  * Requirement: Marketplace contract (this) needs to be an approved operator when the offer is created.
  */
  function setOffer(uint256 _price, uint256 _tokenId) external isKittyContractStarted {
    require(_kittyContract.ownerOf(_tokenId) == msg.sender, "Seller does not own this token Id");
    require(!tokenIdToOffer[_tokenId].active == true, "Token Id has already an offer");
    require(_kittyContract.getApproved(_tokenId) == address(this), "Marketplace contract is not yet an approved operator");

    Offer memory offer = Offer({
      seller: msg.sender,
      price: _price,
      index: indexCounter++,
      tokenId: _tokenId,
      active: true
    });
    allOffers.push(offer);
    tokenIdToOffer[_tokenId] = offer;

    emit MarketTransaction("Create offer", msg.sender, _tokenId);
  }

  /**
  * Removes an existing offer.
  * Emits the MarketTransaction event with txType "Remove offer"
  * Requirement: Only the seller of _tokenId can remove an offer.
    */
  function removeOffer(uint256 _tokenId) external {
    //require(_owns(msg.sender, _tokenId), "Only seller can remove token Id");

    Offer memory offer = tokenIdToOffer[_tokenId];
    require(offer.active == true, "There is no active order to remove for token Id");
    require(offer.seller == msg.sender, "You should own the kitty to be able to remove this offer");

    /* we delete the offer info */
    delete allOffers[tokenIdToOffer[_tokenId].index];

    /* Remove the offer in the mapping*/
    delete tokenIdToOffer[_tokenId];

    emit MarketTransaction("Remove offer", msg.sender, _tokenId);
  }

  /**
  * Executes the purchase of _tokenId.
  * Sends the funds to the seller and transfers the token using transferFrom in Kittycontract.
  * Emits the MarketTransaction event with txType "Buy".
  * Requirement: The msg.value needs to equal the price of _tokenId
  * Requirement: There must be an active offer for _tokenId
    */
  function buyKitty(uint256 _tokenId) external payable {
    Offer memory offer = tokenIdToOffer[_tokenId];
    require(offer.active, "The offer is not active for token Id");
    require(msg.value == offer.price, "The price is not correct");

    /* we delete the offer info */
    delete allOffers[tokenIdToOffer[_tokenId].index];

    /* Remove the offer in the mapping*/
    delete tokenIdToOffer[_tokenId];

    /* TMP REMOVE THIS*/
    //_approve(_tokenId, msg.sender);

    _kittyContract.transferFrom(offer.seller, msg.sender, _tokenId);

    offer.seller.transfer(msg.value);
    emit MarketTransaction("Buy", msg.sender, _tokenId);
  }
}

To deploy this one, I updated the 2_token_migration.js as follows, with the address that of the Kittycontract.

//const Token = artifacts.require("Kittycontract");
const Marketplace = artifacts.require("KittyMarketplace");

module.exports = function (deployer) {
  //deployer.deploy(Token);
  deployer.deploy(Marketplace, "0xcec1abf48fe3F1549e93Fb609002BF78579e37Cb");
};
2 Likes

Hi, this is my contract Marketplace (my contract is more different because i would create a little game nft for tryning my skills)

N.B. my code my code is very messy.

Code MarketplaceStiki.sol (i will check security and bugs, and miss “events” )

// SPDX-License-Identifier: Leluk911
pragma solidity 0.8.7;

import "../node_modules/@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "../contracts/interface/INewStikiNft.sol";
import "../contracts/stikiToken.sol";
import "../node_modules/@openzeppelin/contracts/access/Ownable.sol";
import "../node_modules/@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract MarketplaceStiki is Ownable, ReentrancyGuard {
    IERC721 private Ierc721;
    IStikiNFT private Isnft;

    constructor(address _NftContract) {
        require(_NftContract != address(0), "Set correct Address");
        Ierc721 = IERC721(_NftContract);
        Isnft = IStikiNFT(_NftContract);
    }

    // buyer
    struct OfferBuy {
        address payable Buyer;
        uint256 price;
        uint256 index;
        uint256 tokenId;
        bool active;
    }
    OfferBuy[] public offersBuy;

    // tokenID => offerSeller
    mapping(uint256 => OfferBuy[]) TokenIdToOfferBuyer;

    //Seller
    struct OfferSeller {
        address payable Seller;
        uint256 price;
        uint256 tokenId;
    }
    OfferSeller public offersSell;

    // tokenID => offerBuyer
    mapping(uint256 => OfferSeller) TokenToOfferSeller;

    function setNewStikiContract(address _contract)
        external
        onlyOwner
        nonReentrant
    {
        require(_contract != address(0), "Set correct Address");
        Ierc721 = IERC721(_contract);
        Isnft = IStikiNFT(_contract);
    }

    // Function Seller
    //ricevuta di deposito
    // bool =>(address=>tokenId)
    mapping(address => mapping(uint256 => bool)) receiptDeposit;

    function _putSell(
        address payable _to,
        uint256 _tokenId,
        uint256 _price
    ) internal {
        require(_to == Ierc721.ownerOf(_tokenId), "Not Owner Stiki NFT");
        require(receiptDeposit[_to][_tokenId] == false, "Nft just in list");
        Ierc721.transferFrom(_to, address(this), _tokenId);
        receiptDeposit[_to][_tokenId] = true;
        //OfferSeller memory off = OfferSeller(_to, _price, _tokenId);
        TokenToOfferSeller[_tokenId] = OfferSeller(_to, _price, _tokenId);
        //TokenToOfferSeller[_tokenId] = offersSell;
    }

    function putSell(uint256 _tokenId, uint256 _price) external nonReentrant {
        _putSell(payable(msg.sender), _tokenId, _price);
    }

    function _removePutSell(address _to, uint256 _tokenId) internal {
        require(
            TokenToOfferSeller[_tokenId].Seller == _to,
            "This isnt your put"
        );
        delete TokenToOfferSeller[_tokenId];
        receiptDeposit[_to][_tokenId] = false;
        Ierc721.approve(address(this), _tokenId);
        Ierc721.transferFrom(address(this), _to, _tokenId);
    }

    function removePutSell(uint256 _tokenId) external {
        _removePutSell(msg.sender, _tokenId);
    }

    function viewPuttSell(uint256 _tokenId)
        public
        view
        returns (OfferSeller memory)
    {
        return TokenToOfferSeller[_tokenId];
    }

    // Proventi della vedita
    mapping(address => uint256) balanceSelling;

    function fillPutSell(uint256 _tokenId) external payable nonReentrant {
        //chek
        require(
            receiptDeposit[TokenToOfferSeller[_tokenId].Seller][_tokenId] ==
                true,
            "nft not in selling"
        );
        require(
            msg.value == TokenToOfferSeller[_tokenId].price,
            "Set correct value"
        );
        //transaction
        //(bool success, ) = TokenToOfferSeller[_tokenId].Seller.call{
        //    value: msg.value
        //}("");
        //require(success, "transaction payment faill");
        //modifie
        balanceSelling[TokenToOfferSeller[_tokenId].Seller] += msg.value;
        receiptDeposit[TokenToOfferSeller[_tokenId].Seller][_tokenId] = false;
        delete TokenToOfferSeller[_tokenId];
        // result
        Ierc721.transferFrom(address(this), msg.sender, _tokenId);

        require(Ierc721.ownerOf(_tokenId) == msg.sender, "transfer NFt faill");
    }

    function viewBalance(address _owner) public view returns (uint256) {
        return balanceSelling[_owner];
    }

    function widrowProfitSell() external nonReentrant {
        require(
            address(this).balance >= viewBalance(msg.sender),
            "balance low"
        );
        uint256 profit = viewBalance(msg.sender);
        balanceSelling[msg.sender] = 0;
        (bool success, ) = msg.sender.call{value: profit}("");
        require(success, "Widrow fail");
    }

    /**
     struct OfferBuy {
        address payable Buyer;
        uint256 price;
        uint256 index;
        uint256 tokenId;
        bool active;
        
    }
    OfferBuy[] public offersBuy;

    // tokenID => offerSeller
    mapping(uint256 => OfferBuy) TokenIdToOfferBuyer; */
    mapping(address => uint256) depositEthBuyer;

    function putBuyOffer(uint256 _price, uint256 _tokenId) external payable {
        require(Ierc721.ownerOf(_tokenId) != msg.sender, "already owner");
        require(
            msg.value == _price,
            "Send correct Eth in contract for your offer"
        );
        depositEthBuyer[msg.sender] += msg.value;
        address payable _to = payable(msg.sender);
        _putBuyOffer(_to, _tokenId, msg.value);
    }

    function _putBuyOffer(
        address payable _to,
        uint256 _tokenId,
        uint256 _price
    ) internal {
        uint256 index = TokenIdToOfferBuyer[_tokenId].length;
        OfferBuy memory Offer = OfferBuy(_to, _price, index, _tokenId, true);
        TokenIdToOfferBuyer[_tokenId].push(Offer);
    }

    function deactiveBuyOffer(uint256 _tokenId, uint256 _index) external {
        require(
            TokenIdToOfferBuyer[_tokenId][_index].Buyer == msg.sender,
            "this not is your offer"
        );
        require(
            TokenIdToOfferBuyer[_tokenId][_index].active == true,
            "this offer is already deactive"
        );
        TokenIdToOfferBuyer[_tokenId][_index].active = false;
    }

    function reactiveBuyOffer(uint256 _tokenId, uint256 _index) external {
        require(
            TokenIdToOfferBuyer[_tokenId][_index].Buyer == msg.sender,
            "this not is your offer"
        );
        require(
            TokenIdToOfferBuyer[_tokenId][_index].active == false,
            "this offer is already active"
        );
        TokenIdToOfferBuyer[_tokenId][_index].active = true;
    }

    function _deletOfferBuyer(
        address _to,
        uint256 _tokenId,
        uint256 _index
    ) internal {
        require(
            TokenIdToOfferBuyer[_tokenId][_index].Buyer == msg.sender,
            "this not is your offer"
        );
        uint256 repay = TokenIdToOfferBuyer[_tokenId][_index].price;
        depositEthBuyer[msg.sender] -= repay;

        TokenIdToOfferBuyer[_tokenId][_index] = TokenIdToOfferBuyer[_tokenId][
            TokenIdToOfferBuyer[_tokenId].length - 1
        ];
        TokenIdToOfferBuyer[_tokenId].pop();

        (bool success, ) = _to.call{value: repay}("");
        require(success, "Repay Eth faill");
    }

    function deletOfferBuyer(uint256 _tokenId, uint256 _index)
        external
        nonReentrant
    {
        _deletOfferBuyer(msg.sender, _tokenId, _index);
    }

    // buyer => nft == riceipt for deposit
    mapping(address => mapping(uint256 => bool)) recptNftBuy;

    function _fillBuyerOffer(
        address _to,
        uint256 _tokenId,
        uint256 _index
    ) internal {
        require(
            Ierc721.ownerOf(_tokenId) == _to,
            "you haven't owner's dirict on this nft "
        );
        require(
            TokenIdToOfferBuyer[_tokenId][_index].active == true,
            "this offer is deactive"
        );
        Ierc721.transferFrom(_to, address(this), _tokenId);
        uint256 price = TokenIdToOfferBuyer[_tokenId][_index].price;
        address buyer = TokenIdToOfferBuyer[_tokenId][_index].Buyer;

        //delet Offer
        TokenIdToOfferBuyer[_tokenId][_index] = TokenIdToOfferBuyer[_tokenId][
            TokenIdToOfferBuyer[_tokenId].length - 1
        ];
        TokenIdToOfferBuyer[_tokenId].pop();

        depositEthBuyer[buyer] -= price;
        balanceSelling[_to] += price;

        recptNftBuy[buyer][_tokenId] = true;
    }

    function fillBuyerOffer(uint256 _tokenId, uint256 _index) external {
        _fillBuyerOffer(msg.sender, _tokenId, _index);
    }

    function _widrowlNftBuy(address _to, uint256 _tokenId) internal {
        require(recptNftBuy[_to][_tokenId] == true, "not avalible for widrowl");
        recptNftBuy[_to][_tokenId] == false;
        Ierc721.transferFrom(address(this), _to, _tokenId);
    }

    function widrowlNftBuy(uint256 _tokenId) external nonReentrant {
        _widrowlNftBuy(msg.sender, _tokenId);
    }

    function viewBuyOfferForToken(uint256 _tokenId)
        public
        view
        returns (OfferBuy[] memory)
    {
        return TokenIdToOfferBuyer[_tokenId];
    }
}

This is test :

const mockUsdc = artifacts.require("mockUsdc");
const stikiToken = artifacts.require("stikiToken");
const NewStikiNFT = artifacts.require("NewStikiNFT");
const MintContract = artifacts.require("MintContract");
const MarketplaceStiki = artifacts.require("MarketplaceStiki");

const Turnament = artifacts.require("Turnament");
const truffleAssert = require("truffle-assertions");


contract("Stiki Marketplace", accounts => {

    const account = accounts[0];
    const account2 = accounts[1];
    //console.log(account);

    it("Mint 1 nft", async () => {
        const stk = await stikiToken.deployed();
        const contMinter = await MintContract.deployed();
        const nft = await NewStikiNFT.deployed()
        const usdc = await mockUsdc.deployed();
        await contMinter.setAddrStikiNft(nft.address)

        await usdc.approve(contMinter.address, 1000);
        await contMinter.BuyStikiToken(account, 1000);

        await stk.approve(contMinter.address, 100);

        await contMinter.MintStiki("impostore", 11, 1, 1, 1, 1);

        const nft1 = await nft.viewStat(0);

    })
    it("deposit 3 nft in put Sell", async () => {
        const stk = await stikiToken.deployed();
        const contMinter = await MintContract.deployed();
        const nft = await NewStikiNFT.deployed()
        const usdc = await mockUsdc.deployed();
        const market = await MarketplaceStiki.deployed()
        await contMinter.setAddrStikiNft(nft.address)

        await usdc.approve(contMinter.address, 1000);
        await contMinter.BuyStikiToken(account, 1000);

        await stk.approve(contMinter.address, 300);
        await contMinter.MintStiki("venduto", 11, 1, 1, 1, 1);
        await contMinter.MintStiki("non venduto", 11, 1, 1, 1, 1);
        await contMinter.MintStiki("ritirato", 11, 1, 1, 1, 1);


        //MarketplaceStiki
        // deposito
        await nft.approve(market.address, 0);
        await nft.approve(market.address, 1);
        await nft.approve(market.address, 2);
        await market.putSell(0, (1000));
        await market.putSell(1, (2000));
        await market.putSell(2, (3000));


        let nft0 = await market.viewPuttSell(0);
        let nft1 = await market.viewPuttSell(1);
        let nft2 = await market.viewPuttSell(2);

        //console.log(nft0)
        //console.log(nft1)
        //console.log(nft2)


    })
    it("Fill offer sel NFT", async () => {
        const contMinter = await MintContract.deployed();
        const nft = await NewStikiNFT.deployed()
        const market = await MarketplaceStiki.deployed()
        await contMinter.setAddrStikiNft(nft.address)

        let nft0 = await market.viewPuttSell(0);
        let nft1 = await market.viewPuttSell(1);
        let nft2 = await market.viewPuttSell(2);

        //console.log(nft0)
        //console.log(nft1)
        //console.log(nft2)
        // fill offer sell
        await market.fillPutSell(0, { from: account2, value: 1000 })
        nft0 = await market.viewPuttSell(0);

    })
    it("widrowl ETH", async () => {
        const market = await MarketplaceStiki.deployed()

        let bal = await market.viewBalance(account)
        //console.log(bal.toString())
        await market.widrowProfitSell();
    })
    it("Ower nft buy", async () => {
        const nft = await NewStikiNFT.deployed()

        let ownerNft = await nft.ownerOf(0)
        //console.log(account2)
        //console.log(ownerNft)
    })
    it("Set 3 offerBuyr", async () => {
        const market = await MarketplaceStiki.deployed()
        const nft = await NewStikiNFT.deployed()
        await nft.ownerOf(3);
        await market.putBuyOffer(10000, 3, { from: account2, value: 10000 });
        await market.putBuyOffer(20000, 3, { from: account2, value: 20000 });
        await market.putBuyOffer(30000, 3, { from: account2, value: 30000 });

        let listOffer = await market.viewBuyOfferForToken(3);
        console.log(listOffer);
    })
    it("Sell with fill Buyer offer", async () => {
        const market = await MarketplaceStiki.deployed()
        const nft = await NewStikiNFT.deployed()

        await nft.approve(market.address, 3);
        await market.fillBuyerOffer(3, 2)

        listOffer = await market.viewBuyOfferForToken(3);
        //console.log(listOffer);
    })
    it("Parts widrowl Eth and Nft", async () => {
        const market = await MarketplaceStiki.deployed()
        const nft = await NewStikiNFT.deployed()

        await market.widrowlNftBuy(3, { from: account2 });
        let newOwner = await nft.ownerOf(3);
        console.log(newOwner, account2)


        await market.widrowProfitSell()


    })
    it("deactive one offer Buy", async () => {
        const market = await MarketplaceStiki.deployed()
            ;
        await market.deactiveBuyOffer(3, 1, { from: account2 })

        listOffer = await market.viewBuyOfferForToken(3);
        console.log(listOffer);
    })
    it("deactive one offer Buy already deactive", async () => {
        const market = await MarketplaceStiki.deployed();

        await truffleAssert.reverts(
            market.deactiveBuyOffer(3, 1, { from: account2 })
        )

        listOffer = await market.viewBuyOfferForToken(3);
        console.log(listOffer);
    })
    it("Sell with fill Buyer offer deactive", async () => {
        const market = await MarketplaceStiki.deployed()
        const stk = await stikiToken.deployed();
        const contMinter = await MintContract.deployed();
        const nft = await NewStikiNFT.deployed()
        const usdc = await mockUsdc.deployed();
        await contMinter.setAddrStikiNft(nft.address)

        await usdc.approve(contMinter.address, 1000);
        await contMinter.BuyStikiToken(account, 1000);

        await stk.approve(contMinter.address, 100);

        await contMinter.MintStiki("impostore", 11, 1, 1, 1, 1);
        await market.putBuyOffer(10000, 4, { from: account2, value: 10000 });


        await nft.approve(market.address, 4);

        await market.deactiveBuyOffer(4, 0, { from: account2 })
        await truffleAssert.reverts(
            market.fillBuyerOffer(4, 0)
        )
    })
    it("delete offer Buy", async () => {
        const market = await MarketplaceStiki.deployed();


        await market.deletOfferBuyer(3, 1, { from: account2 });


        listOffer = await market.viewBuyOfferForToken(3);
        console.log(listOffer);
    })



    //await truffleAssert.reverts


})

2 Likes

CatMarketplace.sol:

I seem to be getting a “Undeclared identifier. “setCatContract” is not (or not yet) visible at this point” problem in VS Code. Any help with solving that would be greatly appreciated!

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

import "./CatContract.sol";
import "./Ownable.sol";
import "./IKittyMarketPlace.sol";

contract CatMarketplace is Ownable, IKittyMarketPlace {
    
    CatContract private _catContract;

    struct Offer {
        uint256 price;
        address payable seller;
        uint256 tokenId;
        uint256 index;
        bool active;
    }

    Offer[] offer;

    mapping(uint256 => Offer) public tokenIdToOffer;

    event MarketTransaction(string TxType, address owner, uint256 tokenId);
    
    constructor (address tokenContractAddress) {
        setCatContract = payable(tokenContractAddress);
    }
    /**
    * Set the current KittyContract address and initialize the instance of Kittycontract.
    * Requirement: Only the contract owner can call.
     */
    function setCatContract(address catContractAddress) external onlyOwner {
        _catContract = payable(catContractAddress);
    }
    /**
    * Get the details about a offer for _tokenId. Throws an error if there is no active offer for _tokenId.
     */
    function getOffer(uint256 _tokenId) external view returns (address seller, uint256 price, uint256 index, uint256 tokenId, bool active) {
        require(tokenIdToOffer[_tokenId].active == true, "Offer for the tokenId does not exist");
        Offer memory o = tokenIdToOffer([seller, price, index, tokenId, active]);
        return (o.price, o.seller, o.tokenId, o.index, o.active);
    }
    /**
    * Get all tokenId's that are currently for sale. Returns an empty arror if none exist.
     */
    function getAllTokenOnSale() external view  returns(uint256[] memory listOfOffers) {
        return (offer);
    }

    /**
    * Creates a new offer for _tokenId for the price _price.
    * Emits the MarketTransaction event with txType "Create offer"
    * Requirement: Only the owner of _tokenId can create an offer.
    * Requirement: There can only be one active offer for a token at a time.
    * Requirement: Marketplace contract (this) needs to be an approved operator when the offer is created.
     */
    function setOffer(uint256 _price, uint256 _tokenId) external {
        require(CatContract.ownerOf(_tokenId) == msg.sender, "Only the owner of _tokenId can create an offer.");
        require(!tokenIdToOffer[_tokenId].active, "There can only be one active offer for a token at a time.");
        require(CatContract.getApproved(_tokenId) == address(this) || CatContract.isApprovedForAll(msg.sender, address(this)), "Marketplace contract needs to be an approved operator when the offer is created.");

        Offer memory _offer = Offer ({
            price : _price,
            seller : payable(msg.sender),
            tokenId : _tokenId,
            index : offer.length - 1,
            active : true
        });
        offer.push(_offer);

        emit MarketTransaction("Create offer", owner, _tokenId);
    }

    /**
    * Removes an existing offer.
    * Emits the MarketTransaction event with txType "Remove offer"
    * Requirement: Only the seller of _tokenId can remove an offer.
     */
    function removeOffer(uint256 _tokenId) external {
        require(CatContract.ownerOf(_tokenId) == msg.sender, "Only the seller of _tokenId can remove an offer.");
            
        Offer memory _offer = Offer ({
            price: 0,
            seller: (0x000000000000000000000000000000000000000),
            tokenId: 0,
            index: 0,
            active: false 
        });

        emit MarketTransaction("Remove offer", msg.sender, _tokenId);
    }

    /**
    * Executes the purchase of _tokenId.
    * Sends the funds to the seller and transfers the token using transferFrom in Kittycontract.
    * Emits the MarketTransaction event with txType "Buy".
    * Requirement: The msg.value needs to equal the price of _tokenId
    * Requirement: There must be an active offer for _tokenId
     */
    function buyKitty(uint256 _tokenId) external payable {
        require(msg.value == Offer.price[_tokenId], "The msg.value needs to equal the price of _tokenId");
        require(tokenIdToOffer[_tokenId].active == true, "There must be an active offer for _tokenId.");
        
        CatContract.transferFrom(offer.seller, msg.sender, _tokenId);
        
        emit MarketTransaction("Buy", msg.sender, _tokenId);
    }

}
1 Like