BlockChain/이더리움

Toy Project - DAPP(WATTO)(1)

정신이 많이없는 개발자 2022. 2. 7. 00:57
728x90
반응형
안녕하세요!
이번 프로젝는 웹 애플리케이션에서 klaytn 바오밥 네트워크와, eth 롭스텐 네트워크를 이용해서 
Erc-20 / KIP-7 토큰을 이용해서 NFT를 구매(고정가,옥션가),
배팅 서비스를 제공하는 컨텐츠 배팅 프로젝트에 대해서 설명해보려고 합니다.

- 주된 설명의 내용은 contract가 될것같습니다.
- 전체코드는 github를 참고해주세요.
- contract부분을 보시게 되면 KIP와 eth를 구분해놨습니다.
- contract 함수의 설명 && node.js 서버에서 배포된 contract를 web3.js에서 연결하는 방법 설명 
구현한 전체 프로젝트
 contract이용해서 민팅하고 구매하는 화면



전체 코드는 github를 참고해주세요!
https://github.com/ms3221/BEB_01_final3

👍🏻 eth -NFT CONTRACT(전체코드)

  - remix IDE OR VSCode에서 작성하시면됩니다. 그러나 openzeppelin부분은 각 IDE에 맞게 수정하셔야지 import가 됩니다. 

  - 밑에있는 코드는 VSCode에 맞게 import된 코드입니다.

  - Klaytn도 오픈제플린을 사용해서 알맞게 배포할 수 있습니다. 그러나 Klaytn에서 만든 KIP를 최대한  사용해서 배포해 보았습니다.

    // 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;  //IERC20 함수를 사용한다. 위함이다 setToken을 먼저 해줄 수 있도록 
        uint256 public nftPrice; // nft 가격을 정하기위해서 만들어논 상태변수 

        event NewNft(address owner,uint256 tokenId,string tokenUri);
        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);

        mapping (string => uint256) public getTokenId;   // tokenUri를 key값으로하는 tokenId를 얻기위한 맵핑 tokenId가 재대로반환이 되지않아서 만들어 놓았다.
        mapping (uint256 => uint256) public tokenPrice;   // 토큰아이디를 통해서 해당 nft의 가격을 알 수 있게 만들어 놓은 맵핑 
        mapping(uint => Auction) auction;
        mapping(uint => mapping(address => uint)) public bids;
      
      // auction을 위한 구조체 
      struct Auction {
            bool started;
            address owner;
            uint nftId;
            bool status;
            uint endAt;
            address highestBidder;
            uint highestBid;
            bool ended;
            
        }

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


    //1. setToken erc20컨트랙주소를 가져와서 IERC20에 접근하여 IERC20의 함수를 사용가능 
    //    IERC20함수의 member가 무엇이 있는지 정확하게 파악하고 사용할 것. 
    function setToken (address tokenAddress) public onlyAuthorized returns (bool) {
            require(tokenAddress != address(0x0));
            token = IERC20(tokenAddress);
            return true;
        }

    
    //2. mintNFT erc721에 있는 _mint함수를 이용해서 유일한 토큰을 생성하는 함수 
    // 밑에 nft발행방식은 누군가에게 발헹해주는 방식이 아닌 서버계정 자체에 nft를 발행해주는 함수.
        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;
        }

    // 3. approveSale함수를 사용해서 소유자가 msg.seder에게 대납소유권을 허락해주는 방식. 그래야지 safeTransferFrom을 사용할 수 가 있다.
        function approveSale(address receipent) onlyAuthorized public {
            _setApprovalForAll(receipent, msg.sender, true);
        }

        

        // 4.setForSale함수를 이용해서 소유자가 tokenId에 가격을 지정할 수 있다. 
        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;
            }
        
        }

    // 5. purchaseToken함수를 사용해서 seller 와 buyer간에 교환이 이루어 질 수 있다.

        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);  // 구매자가 판매자게에 erc20토큰을 보내는 함수.
            safeTransferFrom(tokenSeller, buyer, _tokenId);  // 판매자가 구매자에게 tokenId를 넘기는 함수.
            
        }


    
    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;
            //현재 가장 높은 입찰자의 입찰 금액에 
            //가장 높은 입찰자가 누군지도 알아야되서 
            //bids[highestBidder] 선언
            //300이더

        }
        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);
       //NftWt가 Erc20을 호출, msg.sender = NftWt
       //
        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);
    }
}

               


👍🏻 Klaytn -NFT CONTRACT(전체코드)

  - remix IDE OR VSCode에서 작성하시면됩니다. 그러나 openzeppelin부분은 각 IDE에 맞게 수정하셔야지 import가 됩니다. 

  - 밑에있는 코드는 VSCode에 맞게 import된 코드입니다.

  - Klaytn도 오픈제플린을 사용해서 알맞게 배포할 수 있습니다. 그러나 Klaytn에서 만든 KIP를 최대한  사용해서 배포해 보았습니다.

 - 대납기능 사용. 대납기능에 관한 트랜잭션은 caver.js부분을 자세히 보시면 좋습니다.

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

import "../klaytn-contracts-master/contracts/token/KIP7/KIP7.sol";
import "../klaytn-contracts-master/contracts/token/KIP17/KIP17.sol";
import "../klaytn-contracts-master/contracts/drafts/Counters.sol";
import "../klaytn-contracts-master/contracts/token/KIP17/KIP17Full.sol";


    contract WATTONFT is KIP17Full{  
        using Counters for Counters.Counter;
        Counters.Counter private _tokenIds;

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

    event NewNft(address owner, uint256 tokenId, string tokenUri);
    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);
    
    mapping (string => uint256) public getTokenId;   // tokenUri를 key값으로하는 tokenId를 얻기위한 맵핑 tokenId가 재대로반환이 되지않아서 만들어 놓았다.
    mapping (uint256 => uint256) public tokenPrice;   // 토큰아이디를 통해서 해당 nft의 가격을 알 수 있게 만들어 놓은 맵핑 
    mapping(uint => Auction) auction;
    mapping(uint => mapping(address => uint)) public bids;
    
    //tokenId에 대한 auction을 진행하기위한 구조체
     struct Auction {
            bool started;
            address owner;
            uint nftId;
            bool status;
            uint endAt;
            address highestBidder;
            uint highestBid;
            bool ended;
            
        }
    
    constructor() public KIP17Full ("WATTONFTs", "WTNFT") {}   

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

    
    function mintNFT(string memory tokenURI) public 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) public {
            setApprovalForAll(receipent, true);
        }

      function setForSale(uint256 _tokenId, uint256 _price) public {
            //토큰의 소유자 계정만 판매하도록 만드는 함수
            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 {
            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);  // 구매자가 판매자게에 erc20토큰을 보내는 함수.
            safeTransferFrom(tokenSeller, buyer, _tokenId);  // 판매자가 구매자에게 tokenId를 넘기는 함수.
            
        }
    
    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;
            // 
            //현재 가장 높은 입찰자의 입찰 금액에 
            //가장 높은 입찰자가 누군지도 알아야되서 
            //bids[highestBidder] 선언
            //300이더
        }
        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);
       //NftWt가 Erc20을 호출, msg.sender = NftWt
       //
        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);
    }
}

  •  counter => tokenId를 사용하기 위해서 contract에서 ERC721URIStoraged를 상속받고 있다. 또 컨트랙트를 보면
    Counter를 상속받는 것을  확인할 수 있다. 밑에사진은 Counter.sol contract의 위치를 제공하는 사진.

 오픈제플린 -> utils -> Counter.sol contract를 들어가서 확인해볼 수 있다. 

 결국 들어가서 확인해 보면 알 수 있겠지만, tokenId값을 자동적으로 +1 해주고 있는 모습을 확인해 볼 수 있다. 다시 말하자면 mint함수를 구현시 들어가는 tokenId값을 자동으로 +1 해준다고 생각하면 될 것같다.

오픈제플린 -&amp;amp;amp;gt; util -&amp;amp;amp;gt; Counters.sol

 

  😾 IERC20 public token =>

Erc721 컨트랙트에서 사고판매시 다른 싸이트 들과 다르게 Erc20 토큰으로 구매, 판매를 해보기 위해서 이 변수를 사용합니다.

  😾 mapping(uint 256 => uint256) public tokenPrice =>

토큰아이디를 통해서 해당 nft의 가격을 알 수 있게 만들어 놓은 맵핑 입니다. 

 


 지금까지 전체 Contract 코드를 제공하고 간다한 변수명에대해서 설명만 했다. 다음 글에서 기능에 해당되는 Contract 함수를 web3.js || caver.js와 같이 어떻게 사용했는지 설명해보려고 한다. 궁금한점이 있거나 논의해볼 사항이 있다면 댓글로 남겨주세요!
반응형