BlockChain

(BlockChain) NFT 거래 컨트랙트

JJeongHyun 2023. 3. 14. 17:13
반응형

https://developerjjh.tistory.com/177

 

(BlockChain) NFT 토큰 컨트랙트

OpenSea 등 NFT 마켓에서 사용하는 컨트랙트 NFT 토큰 컨트랙트 구현 import "../node_modules/@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; import "../node_modules/@openzeppelin/contracts/access/Ownable.sol"; import

developerjjh.tistory.com

SaleToken 컨트랙트

  • 사용자 간 NFT 판매 및 구매에 관한 컨트랙트

토큰 정보에 대한 구조체

struct TokenInfo {
    uint tokenId;
    uint Rank;
    uint Type;
    uint price;
}
  • price : 가격, 0일 때 판매 중이 아닌 걸로 정의

 

NFT 가격 매핑

mapping(uint => uint) public tokenPrices;
  • tokenId => price

판매중인 NFT의 tokenId 목록

uint[] public SaleTokenList;

판매 등록 함수

function SalesToken(uint _tokenId, uint _price) public {
    address tokenOwner = Token.ownerOf(_tokenId);

    require(tokenOwner == msg.sender);
    require(_price > 0);
    require(Token.isApprovedForAll(msg.sender, address(this)));

    tokenPrices[_tokenId] = _price;
    SaleTokenList.push(_tokenId);
}
  • NFT의 소유자를 찾아 저장
  • NFT 소유자가 판매 등록을 했는지, 소유자만 판매 등록가능
  • 판매 가격이 0보다 큰지, 판매 중인지 확인
  • NFT에 대한 권한이 현재 컨트랙트에 있는지 확인
    • OpenSea를 기준으로 했을 때 setApprovedForAll 메서드가 이미 존재
    • 메타마스크에 연결, 로그인이 됐을 때 그 계정에 대한 권한을 위임받는다
    • owner가 판매 컨트랙트에게 모든 토큰을 위임했는지 확인
    • msg.sender : 판매하는 사람 (토큰 소유자)
    • 두 번째 매개변수 : 대리인 (OpenSea 계정)
  • 가격을 등록, 매핑한다
  • 판매 목록에 추가

토큰을 구매하는 함수

function PurchaseToken(uint _tokenId) public payable {
    address tokenOwner = Token.ownerOf(_tokenId);

    require(tokenOwner != msg.sender);
    require(tokenPrices[_tokenId] > 0);
    require(tokenPrices[_tokenId] <= msg.value);

    payable(tokenOwner).transfer(msg.value);
    Token.transferFrom(tokenOwner, msg.sender, _tokenId);
    tokenPrices[_tokenId] = 0;
    popSaleToken(_tokenId);
}
  • 토큰 ID에 대한 소유자를 저장
  • 구매하려고 하는 계정이 토큰의 소유자이면 실행 중지
  • 토큰 ID의 가격이 0보다 큰지, 즉 판매 중인지 확인
  • 구매자가 토큰을 구매하기 위해 충분한 Ether를 전송했는지 가격을 확인
  • 현재 컨트랙트가 NFT 소유자에게 구매자로부터 받은 Ether를 전송
  • NFT 소유자로부터 구매자에게 NFT 전송
  • 가격 0, 판매를 중지한다는 뜻으로 재정의
  • 판매 목록에서 제외, 제거

구매 취소 함수

function cancelSaleToken(uint _tokenId) public {
    address tokenOwner = Token.ownerOf(_tokenId);

    require(tokenOwner == msg.sender);
    require(tokenPrices[_tokenId] > 0);

    tokenPrices[_tokenId] = 0;
    popSaleToken(_tokenId);
}

 

전달받은 토큰을 SaleTokenList에서 삭제하는 함수

function popSaleToken(uint _tokenId) private returns (bool) {
    for (uint i = 0; i < SaleTokenList.length; i++) {
      if (SaleTokenList[i] == _tokenId) {
        SaleTokenList[i] = SaleTokenList[SaleTokenList.length - 1];
        SaleTokenList.pop();
        return true;
      }
    }
    return false;
}
  • 삭제할 원소를 찾아서 목록 제일 마지막 원소로 덮어주고 그 마지막 원소를 제거해줌으로써 원하는 원소를 덮는다

판매 중인 전체 NFT 목록 가져오는 함수

function getSaleTokenList() public view returns (TokenInfo[] memory) {
    require(SaleTokenList.length > 0);

    TokenInfo[] memory list = new TokenInfo[](SaleTokenList.length);

    for (uint i = 0; i < SaleTokenList.length; i++) {
      uint tokenId = SaleTokenList[i];
      uint Rank = Token.getTokenRank(tokenId);
      uint Type = Token.getTokenType(tokenId);
      uint price = tokenPrices[tokenId];

      list[i] = TokenInfo(tokenId, Rank, Type, price);
    }
    return list;
}
  • TokenInfo 형식으로 반환
    • [{tokenId : 1, Type : 1, Rank : 2, price :....},...]
  • 판매 중인 목록이 없는지 확인
  • 등록된, 판매중인 NFT 개수의 크기로 NFT 정보 배열을 생성
    • JS 문법 > let list : new Array(SaleTokenList.length)
  • NFT의 정보를 생성해서 list에 저장

NFT 소유자를 기준으로 가지고 있는 NFT 목록 가져오는 함수

function getOwnerTokens(
    address _tokenOwner
  ) public view returns (TokenInfo[] memory) {
    uint balance = Token.balanceOf(_tokenOwner);

    require(balance > 0);

    TokenInfo[] memory list = new TokenInfo[](balance);

    for (uint i = 0; i < balance; i++) {
      uint tokenId = Token.tokenOfOwnerByIndex(_tokenOwner, i);
      uint Rank = Token.getTokenRank(tokenId);
      uint Type = Token.getTokenType(tokenId);
      uint price = tokenPrices[tokenId];

      list[i] = TokenInfo(tokenId, Rank, Type, price);
    }
    return list;
  }
  • 등록한 NFT 개수를 저장
  • NFT의 개수가 있는지 확인
  • ERC721Enumerable 컨트랙트에 존재하는 메서드로 소유자의 NFT 목록 중 i번째 ID를 가져온다

minting 직후에 소유하고 있는 마지막 NFT 가져오는 함수

function getLatestToken(
    address _tokenOwner
  ) public view returns (TokenInfo memory) {
    uint balance = Token.balanceOf(_tokenOwner);
    uint tokenId = Token.tokenOfOwnerByIndex(_tokenOwner, balance - 1);
    uint Rank = Token.getTokenRank(tokenId);
    uint Type = Token.getTokenType(tokenId);
    uint price = tokenPrices[tokenId];

    return TokenInfo(tokenId, Rank, Type, price);
  }

 

전체 코드

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

import "./NftToken.sol";

contract SaleToken {
  NftToken public Token;

  constructor(address _tokenAddress) {
    Token = NftToken(_tokenAddress);
  }

  struct TokenInfo {
    uint tokenId;
    uint Rank;
    uint Type;
    uint price;
  }
  mapping(uint => uint) public tokenPrices;
  uint[] public SaleTokenList;

  function SalesToken(uint _tokenId, uint _price) public {
    address tokenOwner = Token.ownerOf(_tokenId);

    require(tokenOwner == msg.sender);
    require(_price > 0);
    require(Token.isApprovedForAll(msg.sender, address(this)));

    tokenPrices[_tokenId] = _price;
    SaleTokenList.push(_tokenId);
  }

  function PurchaseToken(uint _tokenId) public payable {
    address tokenOwner = Token.ownerOf(_tokenId);

    require(tokenOwner != msg.sender);
    require(tokenPrices[_tokenId] > 0);
    require(tokenPrices[_tokenId] <= msg.value);

    payable(tokenOwner).transfer(msg.value);
    Token.transferFrom(tokenOwner, msg.sender, _tokenId);

    tokenPrices[_tokenId] = 0;
    popSaleToken(_tokenId);
  }

  function cancelSaleToken(uint _tokenId) public {
    address tokenOwner = Token.ownerOf(_tokenId);

    require(tokenOwner == msg.sender);
    require(tokenPrices[_tokenId] > 0);

    tokenPrices[_tokenId] = 0;
    popSaleToken(_tokenId);
  }

  function popSaleToken(uint _tokenId) private returns (bool) {
    for (uint i = 0; i < SaleTokenList.length; i++) {
      if (SaleTokenList[i] == _tokenId) {
        SaleTokenList[i] = SaleTokenList[SaleTokenList.length - 1];
        SaleTokenList.pop();
        return true;
      }
    }
    return false;
  }

  function getSaleTokenList() public view returns (TokenInfo[] memory) {
    require(SaleTokenList.length > 0);

    TokenInfo[] memory list = new TokenInfo[](SaleTokenList.length);
    for (uint i = 0; i < SaleTokenList.length; i++) {
      uint tokenId = SaleTokenList[i];
      uint Rank = Token.getTokenRank(tokenId);
      uint Type = Token.getTokenType(tokenId);
      uint price = tokenPrices[tokenId];

      list[i] = TokenInfo(tokenId, Rank, Type, price);
    }
    return list;
  }

  function getOwnerTokens(
    address _tokenOwner
  ) public view returns (TokenInfo[] memory) {
    uint balance = Token.balanceOf(_tokenOwner);
    require(balance > 0);

    TokenInfo[] memory list = new TokenInfo[](balance);

    for (uint i = 0; i < balance; i++) {
      uint tokenId = Token.tokenOfOwnerByIndex(_tokenOwner, i);
      uint Rank = Token.getTokenRank(tokenId);
      uint Type = Token.getTokenType(tokenId);
      uint price = tokenPrices[tokenId];

      list[i] = TokenInfo(tokenId, Rank, Type, price);
    }
    return list;
  }

  function getLatestToken(
    address _tokenOwner
  ) public view returns (TokenInfo memory) {
    uint balance = Token.balanceOf(_tokenOwner);
    uint tokenId = Token.tokenOfOwnerByIndex(_tokenOwner, balance - 1);
    uint Rank = Token.getTokenRank(tokenId);
    uint Type = Token.getTokenType(tokenId);
    uint price = tokenPrices[tokenId];

    return TokenInfo(tokenId, Rank, Type, price);
  }
}