일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 아이디 중복체크기능
- css기초
- nodejs파일업로드
- express실행
- FormData()
- 블록 만들기
- 라우트 매개변수
- 시퀄라이즈 기본설정
- 세션으로 로그인 구현
- 라우터 분리
- node.js path
- javascript기초
- 라우터와 미들웨어
- JWT 만들어보기
- next 매개변수
- useContext
- express router
- cookie-parser 만들어보기
- mysql wsl
- ws 라이브러리
- buffer.from
- OAuth 카카오
- 라우터미들웨어 분리
- express.static
- JWT 하드코딩
- Uncaught Error: could not find react-redux context value; please ensure the component is wrapped in a <Provider>
- 비동기파일업로드
- useEffect clean up
- JWT 로그인 기능 구현
- express session
- Today
- Total
즐코
ERC721 / nft 민팅, 거래 기능 컨트랙트 본문
해당 포스팅은 정리할까말까 고민하다가 3일 내리 놀다 굳어버린 뇌를 깨워주기위해 정리해본다,,,
저번 포스팅에선 ERC721과 그를 상속해서 nft 리스트 인덱스 정리 및 tokenId를 생성해주는 기능을 하는 ERC721Enumerable 의 흐름과 일부 함수들에 대해서 정리해보았다.
하지만 이러한 컨트랙트들은 다 openzeppelin에서 제공해주기 때문에 이번 포스팅에선 굳이 저번 포스팅처럼 일일이 컨트랙트를 작성하지 않고 오픈제플린에서 필요한 컨트랙트들을 import해서 쓸 것이다. 따라서 ,이번엔 ERC721 즉 nft 민팅, 위임, 전송과 관련된 컨트랙트와 그에 따른 nft 판매/구매를 담당하는 컨트랙트에 대해서 작성해보고자 한다.
또한 다음 포스팅에서 Next를 사용하여 nft 민팅 및 거래를 할 수 있는 웹사이트를 간단하게 만들것이다.
우선 오픈제플린에서 제공해주는 2가지 기능 컨트랙트가 있는데, 민팅 컨트랙트 작성 시 써먹을 것이라 미리 언급해둔다.
1. Ownable.sol
import "../node_modules/openzeppelin-solidity/contracts/access/Ownable.sol";
배포한 컨트랙트에 대한 소유권을 행사할 수 있게끔 해준다. 그 컨트랙트 배포자는 Ownable.owner()라는 view 함수를 통해 조회가 가능하다. 컨트랙트 배포자가 곧 소유자이며 이에 대한 관리업무도 소유자에게 있다. 이런 소유권을 다른 계정에게 부여할수도, 포기할수도 있다.
따라서, 특정 컨트랙트 내의 특정 함수가 꼭 배포자, 소유권을 가지고 있는자만이 실행할 수 있도록 만들고 싶다면, 이 Ownable 컨트랙트를 상속하여 onlyOwner라는 함수 속성을 사용하면 된다.
2. Strings.sol
import "../node_modules/openzeppelin-solidity/contracts/utils/Strings.sol";
솔리디티에선 uint를 쉽게 ASCII 문자로 바꾸지 못한다. 따라서 오픈제플린에선 이를 쉽게 문자화시켜주는 Strings.sol이라는 util을 제공해준다. Strings 를 import 해와서 Strings.toString(string으로 바꿔줄 uint값) 메소드를 사용해주면 된다.
나만의 nft를 민팅해주는 컨트랙트부터 작성한다.
1. 나만의 nft mint 용 컨트랙트 : HoochuToken.sol
- 배포 시 토큰 이름, 심볼, nft에 실릴 자산 데이터(이미지, 음원, 영상 등)를 가리킬 metadataURI를 인자로 주고 배포하게끔 만든다.
- 토큰의 희소성을 위해 발행할 최대량을 정해둔다. 이는 상수로 선언한다. 상수 선언시엔 constant라는 키워드를 사용해준다.
- 민팅 즉 발행 가격을 설정해준다. 이 때, 1 * (10*18) 이렇게 wei 단위로 작성해주면, 쓸데없는 계산으로 인해 가스비가 발생함을 방지하기위해 wei/gwei/ether등 단위 사용이 가능하므로 이를 활용해준다.
- 상태변수로 metadataURI 를 추가해준다.
- 토큰에 대한 Rank, Type을 명시하는 구조체를 선언, 이를 tokenId로 조회할 수 있는 TokenDatas 매핑도 추가해준다.
- Rank/Type별로 몇개의 토큰이 발행됐는지 조회할 수 있는 tokenCount라는 상태변수도 만들어주었다.
여기서 uint[4][4]는 경우의 수가 4 x 4 임을 명시해줌
// truffle/contracts/HoochuToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
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 HoochuToken is ERC721Enumerable, Ownable {
uint constant public MAX_TOKEN_COUNT = 1000; // 상수 선언
uint public mint_price = 1000000 gwei; // 1000000000 대신 gwei 단위 사용
string public metadataURI;
constructor(string memory _name, string memory _symbol, string memory _metadataURI) ERC721(_name, _symbol){
metadataURI = _metadataURI;
}
struct TokenData {
uint Rank;
uint Type;
}
mapping(uint => TokenData) public TokenDatas;
uint[4][4] public tokenCount; // 랭크/타입별로 몇개의 토큰이 만들어졌는지 보여주기 위해서 만들었음
토큰 발행 시 json 데이터를 랜덤으로 발행하기 위해 TokenData의 Rank와 Type을 랜덤으로 만들어주는 함수를 만들었다.
토큰 아이디와 토큰 소유자 계정을 조합하여 만들고자 한다. 이 때 중요한 건 이 함수 상에서의 TokenData는 한번 만들어지면 굳이 상태변수로 저장될 필요가 없기 때문에, 해당 함수의 리턴값인 data는 메모리에 임시 저장되게끔 memory 키워드를 붙여준다!
Rank와 Type의 배정 비율은 컨트랙트 작성자가 알아서 분배하면 되는 부분이니 설명 생략
function getRandomNum(address _owner, uint _tokenId) private pure returns(TokenData memory){
uint randomNum = uint(keccak256(abi.encodePacked(_owner, _tokenId))) % 100;
TokenData memory data; // data라는 TokenData 형태를 가진 변수를 메모리에 임시 저장
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;
}
위에서 만든 getRandomNum() 함수를 사용하여 토큰을 랜덤하게 발행하려고 한다.
토큰을 발급하려면 이더 결제가 필수이므로 payable 속성을 붙여준다.
이때 이더를 지급받는 계정은 해당 nft를 배포한 계정이므로 Ownable.owner() 이다!
이더를 받고 해당 함수를 실행한 계정에 tokenId를 발급해준다.
function mintToken() public payable {
require(msg.value >= mint_price); // 보내는 이더의 양이 발행 가격과 같거나 커야함
require(MAX_TOKEN_COUNT > totalSupply()); // 최대 토큰 개수가 총 발행량보다 큰 상태여야함
uint tokenId = ERC721Enumerable.totalSupply() + 1;
TokenDatas[tokenId] = getRandomNum(msg.sender, tokenId); // 랜덤 배정
tokenCount[TokenDatas[tokenId].Rank-1][TokenDatas[tokenId].Type-1] += 1; // 이게 뭐지
payable(Ownable.owner()).transfer(msg.value); // 토큰 소유자에게 이더 전송
_mint(msg.sender, tokenId); // 민팅 요청 계정에게 토큰 아이디 발급
}
TokenData에 기반하여 tokenURI를 만들어주는 함수 또한 작성해준다.
포스팅 초반에 설명한 Strings.toString() 으로 uint를 string화 시켜준다.
그리고, abi.encodePacked는 js의 concat처럼 각 문자열을 합쳐주는 역할도 하므로 이를 활용해준다.
function tokenURI(uint256 _tokenId) public override view returns(string memory){
// String 라이브러리 사용하여 uint를 string 으로 바꿔줌
string memory Rank = Strings.toString(TokenDatas[_tokenId].Rank);
string memory Type = Strings.toString(TokenDatas[_tokenId].Type);
return string(abi.encodePacked(metadataURI, "/", Rank, "/", Type, ".json"));
}
이외에 나머지 거래용 컨트랙트에서 필요할 것 같은 함수들을 추가해주면 전체코드는 아래와 같다.
이 때 metadataURI 같은 경우 배포하는 자가 이를 변경하고 싶을 수도 있으므로, 이에 대한 함수를 setMetadataURI() 로 만들어주고 onlyOwner 속성을 붙여줬다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import "../node_modules/openzeppelin-solidity/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "../node_modules/openzeppelin-solidity/contracts/access/Ownable.sol"; // onlyOwner
import "../node_modules/openzeppelin-solidity/contracts/utils/Strings.sol"; // Strings.toString
contract HoochuToken is ERC721Enumerable, Ownable {
uint constant public MAX_TOKEN_COUNT = 1000;
uint public mint_price = 1000000 gwei;// 0.001 eth
string public metadataURI;
constructor(string memory _name, string memory _symbol, string memory _metadataURI) ERC721(_name,_symbol){
metadataURI = _metadataURI;
}
struct TokenData{
uint Rank;
uint Type;
}
mapping(uint => TokenData) public TokenDatas; // tokenId로 tokenData 조회
uint[4][4] public tokenCount; // Rank/Type별로 몇개의 토큰이 만들어졌는지 보여주기 위해서
// metadataURI 업데이트 (onlyOwner : 배포한 계정만 해당함수 실행 가능)
function setMetadataURI(string memory _uri) public onlyOwner {
metadataURI = _uri;
}
// 토큰 Rank
function getTokenRank(uint _tokenId) public view returns(uint){
return TokenDatas[_tokenId].Rank;
}
// 토큰 Type
function getTokenType(uint _tokenId) public view returns(uint){
return TokenDatas[_tokenId].Type;
}
// TokenData 에 기반하여 tokenURI 를 만들어주는 함수
function tokenURI(uint256 _tokenId) public override view returns(string memory){
// String 라이브러리 사용하여 uint를 string 으로 바꿔줌
string memory Rank = Strings.toString(TokenDatas[_tokenId].Rank);
string memory Type = Strings.toString(TokenDatas[_tokenId].Type);
return string(abi.encodePacked(metadataURI, "/", Rank, "/", Type, ".json"));
}
// 계정과 tokenId를 조합하여 랜덤으로 TokenData를 만들어주는 함수
function getRandomNum(address _owner, uint _tokenId) private pure returns(TokenData memory){
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 mintToken() public payable {
require(msg.value >= mint_price); // 보내는 이더의 양이 발행 가격과 같거나 커야함
require(MAX_TOKEN_COUNT > totalSupply()); // 최대 토큰 개수가 총 발행량보다 큰 상태여야함
uint tokenId = ERC721Enumerable.totalSupply() + 1;
TokenDatas[tokenId] = getRandomNum(msg.sender, tokenId);
tokenCount[TokenDatas[tokenId].Rank-1][TokenDatas[tokenId].Type-1] += 1; // 이게 뭐지
payable(Ownable.owner()).transfer(msg.value);
_mint(msg.sender, tokenId);
}
}
2. nft 거래용 컨트랙트 : SaleToken.sol
거래 컨트랙트는 위에서 만든 nft에 대한 거래이므로 nft 발급 컨트랙트를 무조건 import해와야하며, 배포시엔 그 컨트랙트의 CA가 꼭 필요함을 항상 인지하자,,
해당 거래용 컨트랙트는 아래와 같은 기능을 구현하고자 한다.
- 판매할 토큰 등록
- 토큰 구매하기
- 판매 토큰 등록 취소하기
- 판매 토큰 리스트 조회
- 계정별 소유한 토큰 리스트 정보 가져오기
- 내가 소유하고 있는 마지막 토큰 조회
// truffle/contracts/SaleToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import "./HoochuToken.sol";
contract SaleToken {
HoochuToken public Token;
constructor(address _tokenAddress){
Token = HoochuToken(_tokenAddress); // 배포시 관련 nft CA 필요
}
mapping(uint=>uint) public tokenPrices; // 토큰 아이디로 토큰 가격 조회
uint[] public TokenListForSale; // 판매중인 토큰 아이디 리스트
// 토큰 정보 (토큰 아이디, 랭크, 타입, 가격)
struct TokenInfo{
uint tokenId;
uint Rank;
uint Type;
uint price;
}
}
- 판매할 토큰 등록
중요한건, 판매할 토큰을 포함한 모든 토큰이 오픈씨와 같은 플랫폼 상에 approve되있는 상태여야한다는 것이다. 중간역할인 플랫폼에서 위임을 받은 상태여야 다른 계정으로도 대리 판매가 가능한 것이다. 여기선 거래를 담당하는 이 SaleToken 컨트랙트가 오픈씨와 같은 역할을 하므로 해당 거래용 컨트랙트 계정인 address(this)에게 모든 토큰이 위임 상태여야한다!
function SalesToken(uint _tokenId, uint _price) public {
address tokenOwner = Token.ownerOf(_tokenId);
require(tokenOwner == msg.sender);
require(_price > 0);
// 토큰을 판매하겠단 뜻은 이미 오픈씨에 토큰을 approve 시켜둔 상태
// isApprovedForAll 확인 필요
// 해당 CA 가 approve 승인을 받아야 가능
require(Token.isApprovedForAll(msg.sender, address(this)));
tokenPrices[_tokenId] = _price;
TokenListForSale.push(_tokenId);
}
- 토큰 구매하기
Token.transferFrom(from, to, tokenId) - HoochuToken가 상속받은 ERC721의 함수를 써서 위임받은 거래 컨트랙트 계정이 nft 전송을 실행해준다. 전송 완료 후 판매 등록된 토큰 리스트 (TokenListForSale) 상에서 이미 팔린 토큰은 지워줘야하므로 이에 대한 함수는 popSaledToken이라는 함수로 따로 작성해주었다.
판매 리스트 상에서 마지막 인덱스의 토큰 아이디를 해당 팔린 토큰 인덱스 자리에 넣어주고, 이로 인해 판매리스트 상에 마지막 인덱스의 토큰 아이디가 2개 존재하므로 마지막 인덱스 토큰아이디를 간단하게 pop해버리면 판매 리스트 정리는 끝이다!
간단히 배열로만 설명하자면 아래와 같다.
tokenPrices = [ 1, 3, 5, 6 ] 인데 3을 팔았다치면 => 마지막 인덱스의 6을 팔린 3의 인덱스 자리에 넣어주면 tokenPrices = [ 1, 6, 5, 6 ] 가 된다.=> 여기서 중복된 6을 마지막 자리에서만 없애주면 => tokenPrices = [ 1, 6, 5 ] 이 되는 것
function BuyToken(uint _tokenId) public payable {
address tokenOwner = Token.ownerOf(_tokenId);
require(tokenOwner != msg.sender);
require(tokenPrices[_tokenId] > 0);
require(tokenPrices[_tokenId] <= msg.value);
Token.transferFrom(tokenOwner, msg.sender, _tokenId);
tokenPrices[_tokenId] = 0;
// TokenListForSale 상에서 특정 _tokenId 빼내야함 => 따로 함수로 만들어줌
popSaledToken(_tokenId);
}
function popSaledToken(uint _tokenId) private returns(bool){
for(uint i = 0; i < TokenListForSale.length; i++){
if(TokenListForSale[i] == _tokenId){
TokenListForSale[i] = TokenListForSale[TokenListForSale.length-1];
TokenListForSale.pop();
return true;
}
}
return false;
}
- 판매 토큰 등록 취소하기
function cancelSaleToken(uint _tokenId) public {
address tokenOwner = Token.ownerOf(_tokenId);
require(tokenOwner == msg.sender);
require(tokenPrices[_tokenId] > 0);
tokenPrices[_tokenId] = 0;
popSaledToken(_tokenId);
}
- 판매 토큰 리스트 조회
솔리디티 문법 중 하나로 길이가 정해진 빈 배열을 만들 수 있다.
TokenInfo[] memory list = new TokenInfo[](4);
// const arr = new Array(4) 와 같다
이 함수도 이 TokenInfo[] 배열을 굳이 상태변수로 저장할 필욘 없으므로 memory 키워드를 써준다.
function getSaleTokenList() public view returns(TokenInfo[] memory){
require(TokenListForSale.length > 0);
TokenInfo[] memory list = new TokenInfo[](TokenListForSale.length);
// const arr = new Array(4) 와 같음
for(uint i = 0; i < TokenListForSale.length; i++){
uint tokenId = TokenListForSale[i];
uint Rank = Token.getTokenRank(tokenId);
uint Type = Token.getTokenType(tokenId);
uint price = tokenPrices[tokenId];
list[i] = TokenInfo(tokenId, Rank, Type, price);
}
return list;
}
- 계정별 소유한 토큰 리스트 정보 가져오기
여기선, ERC721의 balanceOf()와 tokenOfOwnerByIndex() 를 사용해야한다.
balanceOf()는 특정 계정의 토큰 개수를 조회할 수 있는 함수이며 tokenOfOwnerByIndex()는 특정 계정이 소유한 토큰 리스트 상에서의 인덱스로 각 토큰 아이디를 조회할 수 있는 함수이다.
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 getLastestToken(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.15;
import "./HoochuToken.sol";
contract SaleToken {
HoochuToken public Token;
constructor(address _tokenAddress){
Token = HoochuToken(_tokenAddress);
}
mapping(uint=>uint) public tokenPrices; // 토큰 아이디로 토큰 가격 조회
uint[] public TokenListForSale; // 판매중인 토큰 아이디 리스트
// 토큰 정보 (토큰 아이디, 랭크, 타입, 가격)
struct TokenInfo{
uint tokenId;
uint Rank;
uint Type;
uint price;
}
// 판매할 토큰 등록 함수
function SalesToken(uint _tokenId, uint _price) public {
address tokenOwner = Token.ownerOf(_tokenId);
require(tokenOwner == msg.sender);
require(_price > 0);
// 토큰을 판매하겠단 뜻은 이미 오픈씨에 토큰을 approve 시켜둔 상태
// isApprovedForAll 확인 필요
// 해당 CA 가 approve 승인을 받아야 가능
require(Token.isApprovedForAll(msg.sender, address(this)));
tokenPrices[_tokenId] = _price;
TokenListForSale.push(_tokenId);
}
// 토큰 구매 함수
function BuyToken(uint _tokenId) public payable {
address tokenOwner = Token.ownerOf(_tokenId);
require(tokenOwner != msg.sender);
require(tokenPrices[_tokenId] > 0);
require(tokenPrices[_tokenId] <= msg.value);
Token.transferFrom(tokenOwner, msg.sender, _tokenId);
tokenPrices[_tokenId] = 0;
// TokenListForSale 상에서 특정 _tokenId 빼내야함 => 따로 함수로 만들어줌
popSaledToken(_tokenId);
}
function popSaledToken(uint _tokenId) private returns(bool){
for(uint i = 0; i < TokenListForSale.length; i++){
if(TokenListForSale[i] == _tokenId){
TokenListForSale[i] = TokenListForSale[TokenListForSale.length-1];
TokenListForSale.pop();
return true;
}
}
return false;
}
// 토큰 판매 취소 함수
function cancelSaleToken(uint _tokenId) public {
address tokenOwner = Token.ownerOf(_tokenId);
require(tokenOwner == msg.sender);
require(tokenPrices[_tokenId] > 0);
tokenPrices[_tokenId] = 0;
popSaledToken(_tokenId);
}
// 나의 판매 토큰 리스트 조회
function getSaleTokenList() public view returns(TokenInfo[] memory){
require(TokenListForSale.length > 0);
TokenInfo[] memory list = new TokenInfo[](TokenListForSale.length);
// const arr = new Array(4) 와 같음
for(uint i = 0; i < TokenListForSale.length; i++){
uint tokenId = TokenListForSale[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 getLastestToken(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);
}
}
다음 포스팅에선 위의 두 컨트랙트를 사용하여 nft 민팅과 거래를 할 수 있는 간단한 웹사이트 만들기에 대해 정리할 예정이다.
'BlockChain' 카테고리의 다른 글
POA 기반 프라이빗 네트워크 구축 (feat. docker) (2) | 2022.08.13 |
---|---|
ERC-721 살펴보기 (0) | 2022.07.28 |
ERC-721 / NFT / Remix로 컨트랙트 배포 및 실행 테스트 (0) | 2022.07.27 |
ether-token 스왑 / 토큰발행 컨트랙트+스왑전용 컨트랙트 (0) | 2022.07.25 |
ERC-20 나만의 토큰 작성 / fallback, receive? / 이더로 토큰 사기 (0) | 2022.07.22 |