๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
D.evelop/BlockChian \ NFT

[NFT] 2์ฃผ์ฐจ(5) KIP-17 / NFT Market contract - ๋ธ”๋ก์ฒด์ธ ๋งˆ์ผ“ ์•ฑ ๋งŒ๋“ค๊ธฐ with ๊ทธ๋ผ์šด๋“œX

by Danne 2022. 12. 25.

๐Ÿ‘‰  KIP-17

 

๐Ÿ‘‰  KIP-17์˜ ํŠน์ง•

  •  NFT์šฉ ํ‘œ์ค€ API ์ œ๊ณต (NFT์ถ”์ , ์ „์†ก ๋“ฑ)
  • ๋ชจ๋“  ํ† ํฐ์˜ transfer/ mint/ burn ์ž‘์—…์€ ์ด๋ฒคํŠธ ๋กœ๊ทธ๋ณ„ ์ถ”์ ์„ ๊ฑฐ์ณ์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    ์ฆ‰, ์†ก๊ธˆ ์ž‘์—…์€ ๋ฌด์กฐ๊ฑด transfer/ mint/ burn ๊ด€๋ จ๋œ ๋ชจ๋“  ์ž‘์—…์—์„œ ๋ฐœ์ƒ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ERC-721์˜ ์ง€๊ฐ‘ ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ERC-721๊ณผ ํ˜ธํ™˜๋˜๋„๋ก ์ง€์›ํ•จ.

์ถœ์ฒ˜ : KIP-17 ๊ณต์‹ ๋ฌธ์„œ

 

 


 

โœ…  KIP17 ์ปจํŠธ๋ž™ํŠธ๋ฅผ ์‚ฌ์šฉํ•ด ๋ฐฐํฌ

KIP17Token.sol ์— ์ •์˜๋œ ์ธํ„ฐํŽ˜์ด์Šค (๊ณต์‹๋ฌธ์„œ)

 

 

โœ…   KIP-17์„ ์‚ฌ์šฉํ•ด Bapp NFT Market contract ๋งŒ๋“ค๊ธฐ

  1. ๋ฐœํ–‰, ์กฐํšŒ
  2. ํŒ๋งค: Market์—๊ฒŒ ์ „์†ก
  3. ๊ตฌ๋งค: Market์—์„œ buy์‹คํ–‰

 

KIPcontract์—์„œ ๋‹ค์Œ ์ฝ”๋“œ ์ œ๊ฑฐ (์‹ค์Šต์šฉ์œผ๋กœ ๊ฐ„๋‹จํ•œ ๊ธฐ๋Šฅ๋ง ์‚ฌ์šฉํ• ๊ฑฐ๋ผ)

KIP17Burnable,
KIP17Pausable
๊ด€๋ จ ์ฝ”๋“œ ์‚ญ์ œ

KIP17Mintable,mintWithTokenURI์˜ onlyMinter ์‚ญ์ œ

 

pragma solidity >=0.4.24 <=0.5.6;

contract NFTSimple {
    string public name = 'KlayLion';
    string public symbol = "DD";
    
    mapping (uint256 => address) public tokenOwner;
    mapping (uint256 => string) public tokenURIs;

    // ์†Œ์œ ํ•œ ํ† ํฐ ๋ฆฌ์ŠคํŠธ
    mapping(address => uint256[]) private _ownedTokens;

    // onKIP17Received bytes value
    bytes4 private constant _KIP17_RECEIVED = 0x6745782b;

    // mint(tokenId, uri, owner)
    // transferForm(form, to, tokenId) -> owner๊ฐ€ ๋ฐ”๋€Œ๋Š” ๊ฒƒ(from -> to)

    //๋ฐœํ–‰
    function mintWithTokenURI(address to, uint256 tokenId, string memory tokenURI) public returns (bool) {
        // to์—๊ฒŒ tokenId(์ผ๋ จ๋ฒˆํ˜ธ)๋ฅผ ๋ฐœํ–‰ํ•˜๊ฒ ๋‹ค.
        // ์ ํž ๊ธ€์ž๋Š” tokenURI
        tokenOwner[tokenId] = to;
        tokenURIs[tokenId] = tokenURI;

        // add token to the list
        _ownedTokens[to].push(tokenId);
        return true;
    }

    // ์ „์†ก 
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public {
        require(from == msg.sender, "from != msg.sender"); // ๋ณด๋‚ธ ์‚ฌ๋žŒ์ด fromํ•˜๊ณ  ๊ฐ™์„ ๋•Œ
        require(from == tokenOwner[tokenId], "you are not the owner of the token"); // ๋ณด๋‚ธ ์‚ฌ๋žŒ์ด ํ† ํฐ์˜ ์†Œ์œ ์ฃผ์ผ ๋•Œ
        // ํ•ด๋‹น ํ† ํฐ๋งŒ ์ง€์šฐ๊ณ 
        _removeTokenFromList(from, tokenId);
        // ์ „์†ก ๋ฐ›๋Š” ๊ณณ์— ํ•ด๋‹นํ† ํฐ ์ถ”๊ฐ€
        _ownedTokens[to].push(tokenId);
        // ํ•ด๋‹น ํ† ํฐ์„ ์ „์†ก
        tokenOwner[tokenId] = to; // ์ด๊ฒƒ๋งŒ ์žˆ์œผ๋ฉด ์•„๋ฌด๋‚˜ ํ† ํฐ์„ ์ „์†กํ•  ์ˆ˜ ์žˆ๋‹ค.

        // if ์ „์†ก ๋ฐ›์„ ์Šค๋งˆํŠธ ์ปจํ”„ํ…ํŠธ์— ์‹คํ–‰ํ•  ์ฝ”๋“œ๊ฐ€ ์žˆ์œผ๋ฉด! ๋‹ค์Œ ์ฝ”๋“œ ์‹คํ–‰
        require(
            _checkOnKIP17Received(from, to, tokenId, _data),  "KIP17: transfer to non KIP17Receiver implementer"
        );

    }

    // internal ์„ ์‚ฌ์šฉ ํ•˜์—ฌ ๋‚ด๋ถ€์—์„œ๋งŒ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•จ
    function _checkOnKIP17Received(address from, address to, uint256 tokenId, bytes memory _data) internal returns (bool) {
        bool success;
        bytes memory returndata;
        
        // ์Šค๋งˆํŠธ ์ปจํŠธ๋ ‰ํŠธ๊ฐ€ ์•„๋‹ˆ๋ฉด
        if (!isContract(to)){
            return true;
        }

        // ์Šค๋งˆํŠธ ์ปจํŠธ๋ ‰ํŠธ๋ผ๋ฉด ์‹คํ–‰ํ•ด๋ผ
        (success, returndata) = to.call(
            abi.encodeWithSelector(
                _KIP17_RECEIVED, // ํ•ด๋‹น ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•ด์„œ ๋‹ค์Œ parameter๋“ค์„ ๋ฐ›์•„์™€๋ผ
                msg.sender,
                from,
                tokenId,
                _data
            )
        );

        // ๋ฆฌํ„ด๊ฐ’์ด _KIP17_RECEIVED์™€ ๊ฐ™์œผ๋ฉด?
        if(
            returndata.length !=0 &&
            abi.decode(returndata, (bytes4)) == _KIP17_RECEIVED
        ){
            return true;
        }
        return false; 
    }


    function isContract(address account) internal view returns (bool) {
        uint256 size;
        // extcodesize : ์ฝ”๋“œ๊ฐ€ ์กด์žฌ ํ•˜๋Š”์ง€?
        assembly { size :=extcodesize(account)}
        return size > 0;
    }


    // ๊ธฐ์กด ํ† ํฐ ์†Œ์œ ์ฃผ์˜ ํ† ํฐ ์‚ญ์ œ
    // ์Šค๋งˆํŠธ ์ปจํŠธ๋ž™ํŠธ ์•ˆ์—์„œ๋งŒ ํ˜ธ์ถœํ•˜๊ณ  ์‹ถ์œผ๋ฉด private
    function _removeTokenFromList(address from, uint256 tokenId) private { 
        // [10, 15, 19, 20] -> 19๋ฒˆ ์‚ญ์ œํ•˜๊ณ  ์‹ถ๋‹ค
        // [10, 15, 20, 19]
        // [10, 15, 20]
        uint256 lastTokenIndex = _ownedTokens[from].length - 1;

        for(uint256 i=0 ; i<_ownedTokens[from].length; i++){
            // Swap last token with deleting token.
            if(tokenId == _ownedTokens[from][i]){
                _ownedTokens[from][i] = _ownedTokens[from][lastTokenIndex];
                _ownedTokens[from][lastTokenIndex] = tokenId;
                break;
            }
        }
        _ownedTokens[from].length--;
        
    }

    // ์†Œ์œ ์ค‘์ธ ํ† ํฐ
    function ownerToken(address owner) public view returns (uint256[] memory){
        return _ownedTokens[owner];
    }
    

    function setTokenUri(uint256 id, string memory uri) public{
        tokenURIs[id] = uri;
    }
}

// ์Šค๋งˆํŠธ ์ปจํŠธ๋ž™ํŠธ๋„ ํ† ํฐ์„ ์†Œ์œ ํ•  ์ˆ˜ ์žˆ์Œ
contract NFTMarket {
    mapping(uint256 => address) public seller;

    //function buyNFT(uint256 tokenId, address NFTAddress, address to) public returns (bool){
        // NFTAddress์—์„œ NFTsimple์„ ๊ฐ€์ง„ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœ ํ•  ์ˆ˜ ์žˆ์Œ
        // NFTSimple(NFTAddress).safeTransferFrom(address(this), to, tokenId);
        // address(this) ๋‚˜ ์Šค์Šค๋กœ์˜ ์ฃผ์†Œ

    // KLAY๋ฅผ ์ „์†กํ•  ์ˆ˜ ์žˆ๊ฒŒํ•˜๋ ค๋ฉด, payble ์‚ฌ์šฉ
    function buyNFT(uint256 tokenId, address NFTAddress) public payable returns (bool){
        // ํ† ํฐ ํŒ ์‚ฌ๋žŒ(seller)์˜ ์ฃผ์†Œ์— ๋ˆ ๋ฐ›์„ ์‚ฌ๋žŒ(receiver)์˜ ์ฃผ์†Œ๋ฅผ ๋„ฃ์–ด์คŒ
        address payable receiver = address(uint160(seller[tokenId]));

        // Send 0.01 KLAY to receiver
        // 10 ** 18 PEB = 1KALY
        // 10 ** 16 PEB = 0.01KALY
        receiver.transfer(10 ** 16);
        

        // ๊ตฌ๋งค์ž์—๊ฒŒ ํ† ํฐ์ด ๊ฐ€๋„๋ก
        NFTSimple(NFTAddress).safeTransferFrom(address(this), msg.sender, tokenId, '0x00');
        return true;
    }

    // Market์ด token์„ ๋ฐ›์•˜์„ ๋•Œ (ํŒ๋งค๋Œ€์— ์˜ฌ๋ผ๊ฐ”์„ ๋•Œ), ํŒ๋งค์ž๊ฐ€ ๋ˆ„๊ตฐ์ง€ ๊ธฐ๋ก
    function onKIP17Recevied(address operator, address from, uint256 tokenId, bytes memory data) public returns (bytes4) {
        seller[tokenId] = from;
        // ํ† ํฐ์„ ๋ฐ›์•˜์„ ๋•Œ, 
        // "๊ตฌํ˜„ํ•˜๊ณ  ์‹ถ์€ ๊ธฐ๋Šฅ์ด ์žˆ์œผ๋ฉด ~~ํ•œ ๋ฌธ์ž๋ฅผ ๋ฆฌํ„ดํ•ด๋ผ."
        return bytes4(keccak256("onKIP17Received(address,address,uint256,bytes)"));
    }
}

 

 

 

๋ฐ˜์‘ํ˜•

๋Œ“๊ธ€