BlockChain
(BlockChain) NFT 토큰 컨트랙트
JJeongHyun
2023. 3. 14. 16:55
반응형
OpenSea 등 NFT 마켓에서 사용하는 컨트랙트
- NFT 토큰 컨트랙트 구현
import "../node_modules/@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "../node_modules/@openzeppelin/contracts/access/Ownable.sol";
import "../node_modules/@openzeppelin/contracts/utils/Strings.sol";
- ERC721 기본 컨트랙트
- owner 관련 컨트랙트, _owner 등을 추가
- 기존에 int, uint 등을 string화 하려면 byte로 바꿨다가 변경해야 하는데 String 라이브러리에서 제공하는 기능을 사용
uint public constant MAX_TOKEN_COUNT = 1000;
uint public mint_price = 1 ether;
string public metadataURI;
- NFT 최대 발행량
- constant : JS에서 const를 의미한다, 바뀌지 않는 변수(상수), solidity에서의 표현
- 상수의 변수명을 정할 때 전부 대문자로 선언
- minting의 가격, 사용자가 NFT를 올릴 때 마다 1 ether씩 받는다
- 연산으로 양을 표현하게 될 경우 가스비 소모
- ether, gwei, second, minute, day 등 단위로 사용
- ether인 경우 solidity에서 10 ** 18으로 인식
- NFT의 tokenId 값에 매칭되는 tokenURI의 앞부분, 웹페이지에서의 baseURL과 같은 기능
struct TokenData {
uint Rank;
uint Type;
}
- 토큰의 데이터
- 현재 구현된 코드에서는 랜덤하게 넣을 예정
- tokenId 값에 따라 랜덤한 Rank, Type을 부여하기 위함
- OpenSea에 attributes에 출력
mapping(uint => TokenData) public TokenDatas;
- tokenId →TokenData
uint[4][4] public tokenCount;
- 토큰 데이터에 따른 NFT 토큰 발행량 확인, 4 * 4 이중 배열
tokenURL을 생성하는 함수
function tokenURI(uint _tokenId) public view override returns (string memory) {
string memory Rank = Strings.toString(TokenDatas[_tokenId].Rank);
string memory Type = Strings.toString(TokenDatas[_tokenId].Type);
return string(abi.encodePacked(metadataURI, "/", Rank, "/", Type, ".json"));
}
- Rank, Type은 uint 타입인데 바로 string으로 형변환이 불가능
- 이에 라이브러리를 사용해서 uint → bytes → string으로 변환
- NFT에 대한 데이터를 저장한 URL 주소를 찾아 데이터를 받아올 수 있도록 구현
NFT 생성하는 함수
function mintToken() public payable {
require(msg.value >= mint_price);
require(MAX_TOKEN_COUNT > ERC721Enumerable.totalSupply());
uint tokenId = ERC721Enumerable.totalSupply() + 1;
TokenData memory random = getRandomTokenData(msg.sender, tokenId);
TokenDatas[tokenId] = random;
tokenCount[random.Rank - 1][random.Type - 1] += 1;
payable(Ownable.owner()).transfer(msg.value);
_mint(msg.sender, tokenId);
}
- CA에게 ether를 지급해서 NFT를 구매한다는 느낌
- 생성 시 ether를 받고 가격을 확인, 돈을 받고 NFT를 생성
- NFT 최대 개수 확인, 현재 1000개 이하로만 생성 가능
- NFT 총 수량을 기준으로 ID 생성, 총발행량의 +1로 tokenId 생성
- 무작위 Rank, Type을 만듦
- 생성한 토큰 데이터를 ID와 매칭하여 저장
- Rank, Type을 기준으로 NFT 수량 정리
- 받은 Ether 컨트랙트 소유자에게 전달(NFT 토큰 컨트랙트 등록자) 후 NFT 생성
TokenData를 랜덤으로 만들어주는 함수
function getRandomTokenData(address _owner, uint _tokenId) private pure returns (TokenData memory) {
uint randomNum = uint(keccak256(abi.encodePacked(_owner, _tokenId))) & 100;
TokenData memory data;
if (randomNum < 5) {
data.Rank = 4;
if (randomNum == 1) data.Type = 1;
else if (randomNum == 2) data.Type = 2;
else if (randomNum == 3) data.Type = 3;
else data.Type = 4;
} else if (randomNum < 13) {
data.Rank = 3;
if (randomNum < 7) data.Type = 1;
else if (randomNum < 9) data.Type = 2;
else if (randomNum < 11) data.Type = 3;
else data.Type = 4;
} else if (randomNum < 37) {
data.Rank = 2;
if (randomNum < 19) data.Type = 1;
else if (randomNum < 25) data.Type = 2;
else if (randomNum < 31) data.Type = 3;
else data.Type = 4;
} else {
data.Rank = 1;
if (randomNum < 52) data.Type = 1;
else if (randomNum < 68) data.Type = 2;
else if (randomNum < 84) data.Type = 3;
else data.Type = 4;
}
return data;
}
- solidity에서는 random 함수(메서드)가 없다
- 따라서, 유일한 값인 tokenId를 가져와서 암호화한 후 나머지 연산으로 0 ~ 99까지 랜덤 한 수를 만든다
metadataURI를 수정할 수 있는, 변경하는 함수
function setMetadataURI(string memory _uri) public onlyOwner {
metadataURI = _uri;
}
- 컨트랙트 배포자, 등록자(소유자)만 실행할 수 있도록 하는 접근 제한자를 사용
- onlyOwner
TokenData의 Rank 조회
function getTokenRank(uint _tokenId) public view returns (uint) {
return TokenDatas[_tokenId].Rank;
}
TokenData의 Type 조회
function getTokenType(uint _tokenId) public view returns (uint) {
return TokenDatas[_tokenId].Type;
}
Rank, Type을 구분하여 NFT의 수량을 확인
function getTokenCount() public view returns (uint[4][4] memory) {
return tokenCount;
}
- 배열의 전체를 반환하기 위한 함수
- getter 함수는 요소 하나만 반환해주고 배열의 전체를 조회하는 것은 불가능하기에 따로 view 함수를 생성
전체 코드
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "../node_modules/@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "../node_modules/@openzeppelin/contracts/access/Ownable.sol";
import "../node_modules/@openzeppelin/contracts/utils/Strings.sol";
contract nftToken is ERC721Enumerable, Ownable {
uint public constant MAX_TOKEN_COUNT = 1000;
uint public mint_price = 1 ether;
string public metadataURI;
struct TokenData {
uint Rank;
uint Type;
}
mapping(uint => TokenData) public TokenDatas;
uint[4][4] public tokenCount;
constructor(
string memory _name,
string memory _symbol,
string memory _metadataURI
) ERC721(_name, _symbol) {
metadataURI = _metadataURI;
}
function tokenURI(
uint _tokenId
) public view override returns (string memory) {
string memory Rank = Strings.toString(TokenDatas[_tokenId].Rank);
string memory Type = Strings.toString(TokenDatas[_tokenId].Type);
return string(abi.encodePacked(metadataURI, "/", Rank, "/", Type, ".json"));
}
function mintToken() public payable {
require(msg.value >= mint_price);
require(MAX_TOKEN_COUNT > ERC721Enumerable.totalSupply());
uint tokenId = ERC721Enumerable.totalSupply() + 1;
TokenData memory random = getRandomTokenData(msg.sender, tokenId);
TokenDatas[tokenId] = random;
tokenCount[random.Rank - 1][random.Type - 1] += 1;
payable(Ownable.owner()).transfer(msg.value);
_mint(msg.sender, tokenId);
}
function getRandomTokenData(
address _owner,
uint _tokenId
) private pure returns (TokenData memory) {
uint randomNum = uint(keccak256(abi.encodePacked(_owner, _tokenId))) & 100;
TokenData memory data;
if (randomNum < 5) {
data.Rank = 4;
if (randomNum == 1) data.Type = 1;
else if (randomNum == 2) data.Type = 2;
else if (randomNum == 3) data.Type = 3;
else data.Type = 4;
} else if (randomNum < 13) {
data.Rank = 3;
if (randomNum < 7) data.Type = 1;
else if (randomNum < 9) data.Type = 2;
else if (randomNum < 11) data.Type = 3;
else data.Type = 4;
} else if (randomNum < 37) {
data.Rank = 2;
if (randomNum < 19) data.Type = 1;
else if (randomNum < 25) data.Type = 2;
else if (randomNum < 31) data.Type = 3;
else data.Type = 4;
} else {
data.Rank = 1;
if (randomNum < 52) data.Type = 1;
else if (randomNum < 68) data.Type = 2;
else if (randomNum < 84) data.Type = 3;
else data.Type = 4;
}
return data;
}
function setMetadataURI(string memory _uri) public onlyOwner {
metadataURI = _uri;
}
function getTokenRank(uint _tokenId) public view returns (uint) {
return TokenDatas[_tokenId].Rank;
}
function getTokenType(uint _tokenId) public view returns (uint) {
return TokenDatas[_tokenId].Type;
}
function getTokenCount() public view returns (uint[4][4] memory) {
return tokenCount;
}
}