728x90
오픈제플린 표준규격을 활용하여 컨트랙트 구성
1. NFTToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "../node_modules/openzeppelin-solidity/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "../node_modules/openzeppelin-solidity/contracts/access/Ownable.sol";
import "../node_modules/openzeppelin-solidity/contracts/utils/Strings.sol";
contract NFTToken is ERC721Enumerable, Ownable{
// NFT 발행량을 제한 할 상태 변수
uint constant public MAX_TOKEN_COUNT = 1000;
// NFT민팅시 지불할 이더 민팅의 가격을 나타재누는 상태 변수;
uint public mint_price = 1 ether;
string public metadataURI;
constructor(string memory _name, string memory _symbol, string memory _metadataURI) ERC721(_name,_symbol){
metadataURI = _metadataURI;
}
// token 구조체 생성
struct TokenData{
uint Rank;
uint Type;
}
// uint 키값은 토큰의 ID
mapping(uint=>TokenData) public TokenDatas;
// 어떤 타입의 토큰이 몇개나 발행 되었는지 확인하기 위한 상태 변수
uint [4][4] public tokenCount;
function mintToken() public payable {
// mintToken을 하면 이더를 지급하게 만들고 CA에게 이더를 보내서 NFT를 구매한다.
// 본인의 잔액이 mint_price보다 클때
require(msg.value> mint_price);
// 총발행량이 MAX_TOKEN_COUNT보다 작을때
require(MAX_TOKEN_COUNT > ERC721Enumerable.totalSupply());
// 총발행량에 1을 더해서 도큰 아이디 변수에 담아둠
uint tokenId = ERC721Enumerable.totalSupply() + 1;
// tokenId에 맞춰서 Rank랑 Type을 랜덤 생성해서 TokenData로 저장
// 랜덤 숫자 생성
TokenData memory random = getRandomTokenData(msg.sender, tokenId);
// 총 발행량의 +1 더해진 토큰 아이디
TokenDatas[tokenId] = random;
// 랜덤으로 생성한 Rank와 Type을 가진 Token의 갯수가 몇개인지 확인하기 위한 상태 변수
tokenCount[TokenDatas[tokenId].Rank-1][TokenDatas[tokenId].Type-1] += 1;
//CA => 컨트랙트 배포자 계정에 지급받은 이더를 전송해준다.
payable(Ownable.owner()).transfer(msg.value);
// mintToken함수를 호출한 계정에 NFT발행
_mint(msg.sender, tokenId);
}
// TokenData를 랜덤하게 만들어줄 함수
function getRandomTokenData(address _owner, uint _tokenId) private pure returns(TokenData memory){
// 솔리디티에서는 랜덤함수를 만들수 없기 때문에 특정한값을 해싱해서 나머지 연산으로 랜덤을 구해야 한다.
// abi.encodePacked(_owner, _tokenId); -> 타입과 상관없이 합쳐주는 메소드
uint randomNum = uint(keccak256(abi.encodePacked(_owner, _tokenId)))%100;
// 토큰의 데이터를 저장할 변수
TokenData memory data;
if(randomNum < 5){
if(randomNum == 1){
data.Rank = 4;
data.Type = 1;
}
else if(randomNum == 2){
data.Rank = 4;
data.Type = 2;
}
else if(randomNum == 3){
data.Rank = 4;
data.Type = 3;
}
else{
data.Rank = 4;
data.Type = 4;
}
}else if(randomNum < 13){
if(randomNum < 7){
data.Rank = 3;
data.Type = 1;
}
else if(randomNum < 9){
data.Rank = 3;
data.Type = 2;
}
else if(randomNum < 11){
data.Rank = 3;
data.Type = 3;
}
else{
data.Rank = 3;
data.Type = 4;
}
}else if(randomNum < 37){
if(randomNum < 19){
data.Rank = 2;
data.Type = 1;
}
else if(randomNum < 25){
data.Rank = 2;
data.Type = 2;
}
else if(randomNum < 31){
data.Rank = 2;
data.Type = 3;
}
else{
data.Rank = 2;
data.Type = 4;
}
}else{
if(randomNum < 52){
data.Rank = 1;
data.Type = 1;
}
else if(randomNum < 68){
data.Rank = 1;
data.Type = 2;
}
else if(randomNum < 84){
data.Rank = 1;
data.Type = 3;
}
else{
data.Rank = 1;
data.Type = 4;
}
}
return data;
}
function tokenURI (uint _tokenId) public override view returns(string memory){
// uint에서 바로 문자열로 형변환이 불가능하기 때문에 uint에서 bytes로 변환하고 문자열로 형변환 해준다.
string memory Rank = Strings.toString(TokenDatas[_tokenId].Rank);
string memory Type = Strings.toString(TokenDatas[_tokenId].Type);
// http://localhost:3000/metadata/1/3/.json 이런 형태로 만들어 줄것
return string(abi.encodePacked(metadataURI, "/",Rank,"/",Type,".json"));
}
// metadataURI을 수정해야 할때 사용할 함수
// onlyOwner : 컨트랙트 배포자만 호출할수 있는 함수
function setMetadataURI(string memory _uri) public onlyOwner{
metadataURI = _uri;
}
// TokenData의 Rank를 조회하는 함수
function getTokenRank(uint _tokenId) public view returns(uint){
return TokenDatas[_tokenId].Rank;
}
function getTokenType(uint _tokenId) public view returns(uint){
return TokenDatas[_tokenId].Type;
}
// 배열전체 내용을 조회하는 함수
// 솔리디티에서 배열을 getter로 전체 조회하는건 불가능한데 요소하나만 return 하기 때문에
// 배열천제를 return해주는 view함수를 만들어 주면 된다.
function getTokenCount() public view returns(uint[4][4] memory){
return tokenCount;
}
}
2. SaleToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "./NFTToken.sol";
contract SaleToken{
// 상태 변수인데 NftToken과 상호작용을 하기 위해서 만든 상태변수
NFTToken public Token;
constructor(address _tokenAddress){
Token = NFTToken(_tokenAddress);
}
// Token info 구조체
struct TokenInfo{
uint tokenId;
uint Rank;
uint Type;
uint price;
}
// token의 아이디 => price(토큰의 가격);
mapping(uint => uint) public tokenPrices;
//판매중인 nft의 토큰아이디 값을 담아놓은 상태변수/
uint[] public SaleTokenList;
// 판매등록함수
function SalesToken(uint _tokenId, uint _price) public {
// 토큰의 소유자 계정
address tokenOwner = Token.ownerOf(_tokenId);
// 토큰의 소유자를 가져왔을때 소유자가 맞으면 판매 가능
require(tokenOwner == msg.sender);
// 판매 가격이 0보다 큰값인지 확인
require(_price > 0);
// SalesToken() 함수를 실행한 사람이 모든 토큰의 권한을 위임했는지 확인
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);
// 판매중인 토큰만 구매할 수 있게 체크
// 여기서 tokenPrices의 값이 0이상인 경우 판매중으로 인식
require(tokenPrices[_tokenId] > 0);
// 구매자가 지불한 이더가 판매 가격 이상인지 체크
require(tokenPrices[_tokenId] < msg.value);
// CA가 토큰 판매자에게 이더 전송
payable(tokenOwner).transfer(msg.value);
// 토큰 전달
Token.transferFrom(tokenOwner,msg.sender, _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);
// SaleTokenList 리스트의 길이 만큼 빈값을 가지게 배열을 만듬
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;
}
// 소유하고 있는 NFT리스트 view함수
function getOwnerToken(address _tokenOwner) public view returns (TokenInfo[] memory){
uint balance = Token.balanceOf(_tokenOwner);
require(balance != 0);
// balance크기의 빈배열 만들기 list
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를 보여주는 view함수 민팅했을때 바로 보여주기 위한 용도로 사용
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);
}
}
3. 2_deploy_SaleToken.js
const NFTToken = artifacts.require("NFTToken");
const SaleToken = artifacts.require("SaleToken");
module.exports = async (deployer) => {
// 이름, 심볼, 메타데이터 URI
// http://localhost:3000/metadata 이런 json 파일 있는 url
await deployer.deploy(NFTToken, "MyNFT", "MFT", "url넣는곳");
// NFTToken 배포를 진행하고 배포 완료된 인스턴스 가져오기
const token = await NFTToken.deployed();
// CA값을 넣어주기 위해 NFTToken을 먼저 배포
await deployer.deploy(SaleToken, token.address);
// 배포 완료된 인스턴스 가져오기
const saleToken = await SaleToken.deployed();
};
728x90
'개발 > BlockChain' 카테고리의 다른 글
[BlockChain] 블록체인의 트릴레마 (0) | 2023.02.05 |
---|---|
[BlockChain] 블록체인이란 무엇인가? (0) | 2023.01.24 |
[ERC토큰] ERC20 ERC721 ERC777 ERC1155 (0) | 2022.12.12 |
[BlockChain] NFT 만들기 (goerliETH) (0) | 2022.12.06 |
[BlockChain] localhost에서 remix 연동 (0) | 2022.12.06 |