본문 바로가기
WATTO 프로젝트/컨트렉트

NFT 컨트렉트

by gun_poo 2022. 2. 13.

NFT는 ERC-721로 구성되어 있으며 NWT_Token으로 구매가 가능하다.

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

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

 
    contract WATTONFT is ERC721URIStorage, Ownable {  
        using Counters for Counters.Counter;
        Counters.Counter private _tokenIds;

        IERC20 public token;
        uint256 public nftPrice; 

        event NewNft(address owner, uint256 tokenId, string tokenUri);

        mapping (string => uint256) public getTokenId;   
        mapping (uint256 => uint256) public tokenPrice;

        constructor() ERC721("WATTONFTs", "WTNFT") {}   

  
    function setToken (address tokenAddress) public onlyAuthorized returns (bool) {
            require(tokenAddress != address(0x0));
            token = IERC20(tokenAddress);
            return true;
        }

        function mintNFT(string memory tokenURI) public onlyAuthorized returns (uint256) {
            
            _tokenIds.increment();
            uint256 newItemId = _tokenIds.current();
            _mint(msg.sender, newItemId);
            _setTokenURI(newItemId, tokenURI); 
            emit NewNft(msg.sender,newItemId,tokenURI);
           
            return newItemId;
        }

   
        function approveSale(address receipent) onlyAuthorized public {
            _setApprovalForAll(receipent, msg.sender, true);
        }

        
        function setForSale(uint256 _tokenId, uint256 _price) public onlyAuthorized {
            address tokenOwner = ownerOf(_tokenId);
            require(tokenOwner != address(0x0));
            if(tokenOwner == msg.sender){
            require(_price > 0,'price is zero or lower'); 
            tokenPrice[_tokenId] = _price;
            }else{
            require(_price > 0,'price is zero or lower');
            require(isApprovedForAll(tokenOwner, msg.sender),'token owner did not approve');  
            tokenPrice[_tokenId] = _price;
            }
        
        }


        function purchaseToken(uint256 _tokenId,address buyer) public onlyAuthorized {
            uint bal = token.balanceOf(buyer);
            uint256 price = tokenPrice[_tokenId];
            address tokenSeller = ownerOf(_tokenId);

            require(buyer != address(0x0));
            require(tokenSeller != address(0x0));
            require(bal >= price,"buyer is not enough money");
            require(tokenSeller != buyer,"owner not buy itself"); 
        

            token.transferFrom(buyer,tokenSeller,price);  
            safeTransferFrom(tokenSeller, buyer, _tokenId); 
            
        }


    event Start();
    event Bid(address indexed sender, uint amount);
    event Withdraw(address indexed bidder, uint amount);
    event End(address winner, uint amount);
    event Endedat(uint a);
    

    struct Auction {
            bool started;
            address owner;
            uint nftId;
            bool status;
            uint endAt;
            address highestBidder;
            uint highestBid;
            bool ended;
            
        }
    mapping(uint => Auction) auction;
    mapping(uint => mapping(address => uint)) public bids;
    
    function startAuction(uint nftId, address owner, uint _startingBid) public {
         auction[nftId].nftId = nftId;
         auction[nftId].owner = owner;
         auction[nftId].started = true;
         auction[nftId].highestBid = _startingBid;
         transferFrom(owner, address(this), nftId);
         auction[nftId].endAt = block.timestamp + 1 days;

         emit Endedat(auction[nftId].endAt);
      }

    function bid(uint nftId, address buyer, uint amount) public {
        require(auction[nftId].started, "not started");
        require(block.timestamp < auction[nftId].endAt, "ended");
        require(amount > auction[nftId].highestBid, "value < highest");

        if (auction[nftId].highestBidder != address(0)) {
            bids[nftId][auction[nftId].highestBidder] += auction[nftId].highestBid;
        }
        token.transferFrom(buyer, address(this), amount);
        auction[nftId].highestBidder = buyer;
        auction[nftId].highestBid = amount;

    }

    function withdraw(address addr, uint nftId) public {
        uint bal = bids[nftId][addr];
        bids[nftId][addr] = 0;
        token.transferFrom(address(this), addr , bal);
        emit Withdraw(addr, bal);
    }

    function end(uint nftId) public {
        require(auction[nftId].started, "not started");
        require(block.timestamp >= auction[nftId].endAt, "not ended");
        require(!auction[nftId].ended, "ended");

        auction[nftId].ended = true;
        if (auction[nftId].highestBidder != address(0)) {
            safeTransferFrom(address(this), auction[nftId].highestBidder, nftId);
            token.transferFrom(address(this), auction[nftId].owner, auction[nftId].highestBid);
        } else {
            safeTransferFrom(address(this), auction[nftId].owner, nftId);
        }
        emit End(auction[nftId].highestBidder, auction[nftId].highestBid);
    }
}

해석

using Counters for Counters.Counter;
  • Counters 는 라이브러리
  • using Conters for Counters.Counter :  current() 또는 increment()와 같은 카운터 라이브러리 내부의 모든 기능을 카운터 구조에 할당하라는 의미
  • 이러한 함수들을 호출할 때, 그 함수들의 첫 번째 매개변수는 카운터 구조인 유형 그 자체
  • // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (utils/Counters.sol)
    
    pragma solidity ^0.8.0;
    
    /**
     * @title Counters
     * @author Matt Condon (@shrugs)
     * @dev Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number
     * of elements in a mapping, issuing ERC721 ids, or counting request ids.
     *
     * Include with `using Counters for Counters.Counter;`
     */
    library Counters {
        struct Counter {
            // This variable should never be directly accessed by users of the library: interactions must be restricted to
            // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add
            // this feature: see https://github.com/ethereum/solidity/issues/4637
            uint256 _value; // default: 0
        }
    
        function current(Counter storage counter) internal view returns (uint256) {
            return counter._value;
        }
    
        function increment(Counter storage counter) internal {
            unchecked {
                counter._value += 1;
            }
        }
    
        function decrement(Counter storage counter) internal {
            uint256 value = counter._value;
            require(value > 0, "Counter: decrement overflow");
            unchecked {
                counter._value = value - 1;
            }
        }
    
        function reset(Counter storage counter) internal {
            counter._value = 0;
        }
    }
Counters.Counter private _tokenIds;
  • Counters.Counter 구조체의 _value = _tokenIds;

IERC20 public token;
  • IERC20 함수를 사용한다. 위함이다 setToken을 먼저 해줄 수 있도록
uint256 public nftPrice;
  • nft 가격을 정하기위해서 만들어논 상태변수

event NewNft(address owner, uint256 tokenId, string tokenUri);

mapping (string => uint256) public getTokenId;
  • tokenUri를 key값으로하는 tokenId를 얻기위한 맵핑 tokenId가 재대로반환이 되지않아서 만들어 놓았다.
mapping (uint256 => uint256) public tokenPrice;
  •  토큰아이디를 통해서 해당 nft의 가격을 알 수 있게 만들어 놓은 맵핑

function setToken

function setToken (address tokenAddress) public onlyAuthorized returns (bool) {
require(tokenAddress != address(0x0));
token = IERC20(tokenAddress);
return true;
}

function mintNFT

  • NFT 민팅
function mintNFT(string memory tokenURI) public onlyAuthorized returns (uint256) {
 
_tokenIds.increment();
  • Counter 라이브러리 increment : counter._tokenIds += 1;
uint256 newItemId = _tokenIds.current();
  • Counter 라이브러리 current : counter._tokenIds; => newItemId
_mint(msg.sender, newItemId);
  • 우리 플랫폼은 서버가 호출한다 msg.sender = serverAddress, newItemId = 현재 Id 값
_setTokenURI(newItemId, tokenURI);
  • ERC721URIStorage 에 _setTokenURI로 newItemId, tokenURI를 넘겨준다
  • abstract contract ERC721URIStorage is ERC721 {
    using Strings for uint256;
    
    
    mapping(uint256 => string) private _tokenURIs;
    
    function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual {
            require(_exists(tokenId), "ERC721URIStorage: URI set of nonexistent token");
            _tokenURIs[tokenId] = _tokenURI;
        }
    }
  • mapping(uint256 => string) private _tokenURIs => _tokenUIRs[uint256] = string =>
    _tokenURIs[tokenId] = _tokenURI;
emit NewNft(msg.sender,newItemId,tokenURI);
  • event NewNft에 기록
return newItemId;
}
  • mintNFT erc721에 있는 _mint함수를 이용해서 유일한 토큰을 생성하는 함수
  • 밑에 nft발행방식은 누군가에게 발헹해주는 방식이 아닌 서버계정 자체에 nft를 발행해주는 함수.

function approveSale

  • approveSale함수를 사용해서 소유자가 msg.seder에게 대납소유권을 허락해주는 방식. 그래야 safeTransferFrom을 사용할 수 가 있다.
function approveSale(address receipent) onlyAuthorized public {
_setApprovalForAll(receipent, msg.sender, true);
  • ERC-721 : _setApprovalForAll
    • function _setApprovalForAll(
      address owner,
      address operator,
      bool approved
      ) internal virtual {
      require(owner != operator, "ERC721: approve to caller");
      _operatorApprovals[owner][operator] = approved;
      emit ApprovalForAll(owner, operator, approved);
      }
}

function setForSale

  • setForSale함수를 이용해서 소유자가 tokenId에 가격을 지정
function setForSale(uint256 _tokenId, uint256 _price) public onlyAuthorized {
  • 토큰의 소유자 계정만 판매하도록 만드는 함수
address tokenOwner = ownerOf(_tokenId);
  • ERC-721 ownerOf
  • function ownerOf(uint256 tokenId) public view virtual override returns (address) {
            address owner = _owners[tokenId];
            require(owner != address(0), "ERC721: owner query for nonexistent token");
            return owner;
        }
require(tokenOwner != address(0x0));
if(tokenOwner == msg.sender){
  • tokenOwner 와 msg.sender가 일치하다면
require(_price > 0,'price is zero or lower');
tokenPrice[_tokenId] = _price;
  • _tokenId를 통해 가격을 알 수 있고 인자로 받은 _price 값 지정
}else{
require(_price > 0,'price is zero or lower');
require(isApprovedForAll(tokenOwner, msg.sender),'token owner did not approve');
  • approveSale에서 했던
    • _operatorApprovals[owner][operator] = approved;
      emit ApprovalForAll(owner, operator, approved); 
    • 대납 소유권 확인 한다
tokenPrice[_tokenId] = _price;
  • 확인이 되었으면 값 지정 판매
}
}

function purchaseToken

  • purchaseToken함수를 사용해서 seller 와 buyer간에 교환
function purchaseToken(uint256 _tokenId,address buyer) public onlyAuthorized {
uint bal = token.balanceOf(buyer);
  • 구매자의 토큰 밸런스 확인
uint256 price = tokenPrice[_tokenId];
  • setForSale에서 지정한 값을 price로 
address tokenSeller = ownerOf(_tokenId);
  • 토큰 소유자만 판매하게 하는 함수 ownerOf 주소값을 tokenSeller 지정
require(buyer != address(0x0));
require(tokenSeller != address(0x0));
require(bal >= price,"buyer is not enough money");
require(tokenSeller != buyer,"owner not buy itself");
  • 본인은 구매를 못함

token.transferFrom(buyer,tokenSeller,price);

  •  구매자가 판매자게에 erc20토큰을 보내는 함수
safeTransferFrom(tokenSeller, buyer, _tokenId);
  •  판매자가 구매자에게 tokenId를 넘기는 함수
}

 

'WATTO 프로젝트 > 컨트렉트' 카테고리의 다른 글

Ethereum 기반 컨트렉트 klaytn 환경으로 변경  (0) 2022.02.14
NFT 경매  (0) 2022.02.13
Token_Swap  (0) 2022.02.13
WT_Token  (0) 2022.02.12
NWT_Token  (0) 2022.02.12

댓글