Toy Project - DAPP(WATTO)(1)
안녕하세요!
이번 프로젝는 웹 애플리케이션에서 klaytn 바오밥 네트워크와, eth 롭스텐 네트워크를 이용해서
Erc-20 / KIP-7 토큰을 이용해서 NFT를 구매(고정가,옥션가),
배팅 서비스를 제공하는 컨텐츠 배팅 프로젝트에 대해서 설명해보려고 합니다.
- 주된 설명의 내용은 contract가 될것같습니다.
- 전체코드는 github를 참고해주세요.
- contract부분을 보시게 되면 KIP와 eth를 구분해놨습니다.
- contract 함수의 설명 && node.js 서버에서 배포된 contract를 web3.js에서 연결하는 방법 설명구현한 전체 프로젝트  contract이용해서 민팅하고 구매하는 화면
전체 코드는 github를 참고해주세요!
https://github.com/ms3221/BEB_01_final3
👍🏻 eth -NFT CONTRACT(전체코드)
- remix IDE OR VSCode에서 작성하시면됩니다. 그러나 openzeppelin부분은 각 IDE에 맞게 수정하셔야지 import가 됩니다.
- 밑에있는 코드는 VSCode에 맞게 import된 코드입니다.
- Klaytn도 오픈제플린을 사용해서 알맞게 배포할 수 있습니다. 그러나 Klaytn에서 만든 KIP를 최대한 사용해서 배포해 보았습니다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../node_modules/@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "../node_modules/@openzeppelin/contracts/utils/Counters.sol";
import "../node_modules/@openzeppelin/contracts/access/Ownable.sol";
import "../node_modules/@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract WATTONFT is ERC721URIStorage, Ownable {
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
IERC20 public token; //IERC20 함수를 사용한다. 위함이다 setToken을 먼저 해줄 수 있도록
uint256 public nftPrice; // nft 가격을 정하기위해서 만들어논 상태변수
event NewNft(address owner,uint256 tokenId,string tokenUri);
event Start();
event Bid(address indexed sender, uint amount);
event Withdraw(address indexed bidder, uint amount);
event End(address winner, uint amount);
event Endedat(uint a);
mapping (string => uint256) public getTokenId; // tokenUri를 key값으로하는 tokenId를 얻기위한 맵핑 tokenId가 재대로반환이 되지않아서 만들어 놓았다.
mapping (uint256 => uint256) public tokenPrice; // 토큰아이디를 통해서 해당 nft의 가격을 알 수 있게 만들어 놓은 맵핑
mapping(uint => Auction) auction;
mapping(uint => mapping(address => uint)) public bids;
// auction을 위한 구조체
struct Auction {
bool started;
address owner;
uint nftId;
bool status;
uint endAt;
address highestBidder;
uint highestBid;
bool ended;
}
constructor() ERC721("WATTONFTs", "WTNFT") {}
//1. setToken erc20컨트랙주소를 가져와서 IERC20에 접근하여 IERC20의 함수를 사용가능
// IERC20함수의 member가 무엇이 있는지 정확하게 파악하고 사용할 것.
function setToken (address tokenAddress) public onlyAuthorized returns (bool) {
require(tokenAddress != address(0x0));
token = IERC20(tokenAddress);
return true;
}
//2. mintNFT erc721에 있는 _mint함수를 이용해서 유일한 토큰을 생성하는 함수
// 밑에 nft발행방식은 누군가에게 발헹해주는 방식이 아닌 서버계정 자체에 nft를 발행해주는 함수.
function mintNFT(string memory tokenURI) public onlyAuthorized returns (uint256) {
_tokenIds.increment();
uint256 newItemId = _tokenIds.current();
_mint(msg.sender, newItemId);
_setTokenURI(newItemId, tokenURI);
emit NewNft(msg.sender,newItemId,tokenURI);
return newItemId;
}
// 3. approveSale함수를 사용해서 소유자가 msg.seder에게 대납소유권을 허락해주는 방식. 그래야지 safeTransferFrom을 사용할 수 가 있다.
function approveSale(address receipent) onlyAuthorized public {
_setApprovalForAll(receipent, msg.sender, true);
}
// 4.setForSale함수를 이용해서 소유자가 tokenId에 가격을 지정할 수 있다.
function setForSale(uint256 _tokenId, uint256 _price) public onlyAuthorized {
//토큰의 소유자 계정만 판매하도록 만드는 함수
address tokenOwner = ownerOf(_tokenId);
require(tokenOwner != address(0x0));
if(tokenOwner == msg.sender){
require(_price > 0,'price is zero or lower');
tokenPrice[_tokenId] = _price;
}else{
require(_price > 0,'price is zero or lower');
require(isApprovedForAll(tokenOwner, msg.sender),'token owner did not approve');
tokenPrice[_tokenId] = _price;
}
}
// 5. purchaseToken함수를 사용해서 seller 와 buyer간에 교환이 이루어 질 수 있다.
function purchaseToken(uint256 _tokenId,address buyer) public onlyAuthorized {
uint bal = token.balanceOf(buyer);
uint256 price = tokenPrice[_tokenId];
address tokenSeller = ownerOf(_tokenId);
require(buyer != address(0x0));
require(tokenSeller != address(0x0));
require(bal >= price,"buyer is not enough money");
require(tokenSeller != buyer,"owner not buy itself"); //본인은 구매를 못함
token.transferFrom(buyer,tokenSeller,price); // 구매자가 판매자게에 erc20토큰을 보내는 함수.
safeTransferFrom(tokenSeller, buyer, _tokenId); // 판매자가 구매자에게 tokenId를 넘기는 함수.
}
function startAuction(uint nftId, address owner, uint _startingBid) public {
auction[nftId].nftId = nftId;
auction[nftId].owner = owner;
auction[nftId].started = true;
auction[nftId].highestBid = _startingBid;
transferFrom(owner, address(this), nftId);
auction[nftId].endAt = block.timestamp + 1 days;
emit Endedat(auction[nftId].endAt);
}
function bid(uint nftId, address buyer, uint amount) public {
require(auction[nftId].started, "not started");
require(block.timestamp < auction[nftId].endAt, "ended");
require(amount > auction[nftId].highestBid, "value < highest");
if (auction[nftId].highestBidder != address(0)) {
bids[nftId][auction[nftId].highestBidder] += auction[nftId].highestBid;
//현재 가장 높은 입찰자의 입찰 금액에
//가장 높은 입찰자가 누군지도 알아야되서
//bids[highestBidder] 선언
//300이더
}
token.transferFrom(buyer, address(this), amount);
auction[nftId].highestBidder = buyer;
auction[nftId].highestBid = amount;
}
function withdraw(address addr, uint nftId) public {
uint bal = bids[nftId][addr];
bids[nftId][addr] = 0;
token.transferFrom(address(this), addr , bal);
//NftWt가 Erc20을 호출, msg.sender = NftWt
//
emit Withdraw(addr, bal);
}
function end(uint nftId) public {
require(auction[nftId].started, "not started");
require(block.timestamp >= auction[nftId].endAt, "not ended");
require(!auction[nftId].ended, "ended");
auction[nftId].ended = true;
if (auction[nftId].highestBidder != address(0)) {
safeTransferFrom(address(this), auction[nftId].highestBidder, nftId);
token.transferFrom(address(this), auction[nftId].owner, auction[nftId].highestBid);
} else {
safeTransferFrom(address(this), auction[nftId].owner, nftId);
}
emit End(auction[nftId].highestBidder, auction[nftId].highestBid);
}
}
👍🏻 Klaytn -NFT CONTRACT(전체코드)
- remix IDE OR VSCode에서 작성하시면됩니다. 그러나 openzeppelin부분은 각 IDE에 맞게 수정하셔야지 import가 됩니다.
- 밑에있는 코드는 VSCode에 맞게 import된 코드입니다.
- Klaytn도 오픈제플린을 사용해서 알맞게 배포할 수 있습니다. 그러나 Klaytn에서 만든 KIP를 최대한 사용해서 배포해 보았습니다.
- 대납기능 사용. 대납기능에 관한 트랜잭션은 caver.js부분을 자세히 보시면 좋습니다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.5.6;
import "../klaytn-contracts-master/contracts/token/KIP7/KIP7.sol";
import "../klaytn-contracts-master/contracts/token/KIP17/KIP17.sol";
import "../klaytn-contracts-master/contracts/drafts/Counters.sol";
import "../klaytn-contracts-master/contracts/token/KIP17/KIP17Full.sol";
contract WATTONFT is KIP17Full{
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
IKIP7 public token; //IERC20 함수를 사용한다. 위함이다 setToken을 먼저 해줄 수 있도록
uint256 public nftPrice; // nft 가격을 정하기위해서 만들어논 상태변수
event NewNft(address owner, uint256 tokenId, string tokenUri);
event Start();
event Bid(address indexed sender, uint amount);
event Withdraw(address indexed bidder, uint amount);
event End(address winner, uint amount);
event Endedat(uint a);
mapping (string => uint256) public getTokenId; // tokenUri를 key값으로하는 tokenId를 얻기위한 맵핑 tokenId가 재대로반환이 되지않아서 만들어 놓았다.
mapping (uint256 => uint256) public tokenPrice; // 토큰아이디를 통해서 해당 nft의 가격을 알 수 있게 만들어 놓은 맵핑
mapping(uint => Auction) auction;
mapping(uint => mapping(address => uint)) public bids;
//tokenId에 대한 auction을 진행하기위한 구조체
struct Auction {
bool started;
address owner;
uint nftId;
bool status;
uint endAt;
address highestBidder;
uint highestBid;
bool ended;
}
constructor() public KIP17Full ("WATTONFTs", "WTNFT") {}
function setToken (address tokenAddress) public returns (bool) {
require(tokenAddress != address(0x0));
token = IKIP7(tokenAddress);
return true;
}
function mintNFT(string memory tokenURI) public returns (uint256) {
_tokenIds.increment();
uint256 newItemId = _tokenIds.current();
_mint(msg.sender, newItemId);
_setTokenURI(newItemId, tokenURI);
emit NewNft(msg.sender,newItemId,tokenURI);
return newItemId;
}
function approveSale(address receipent) public {
setApprovalForAll(receipent, true);
}
function setForSale(uint256 _tokenId, uint256 _price) public {
//토큰의 소유자 계정만 판매하도록 만드는 함수
address tokenOwner = ownerOf(_tokenId);
require(tokenOwner != address(0x0));
if(tokenOwner == msg.sender){
require(_price > 0,'price is zero or lower');
tokenPrice[_tokenId] = _price;
}else{
require(_price > 0,'price is zero or lower');
require(isApprovedForAll(tokenOwner, msg.sender),'token owner did not approve');
tokenPrice[_tokenId] = _price;
}
}
function purchaseToken(uint256 _tokenId,address buyer) public {
uint bal = token.balanceOf(buyer);
uint256 price = tokenPrice[_tokenId];
address tokenSeller = ownerOf(_tokenId);
require(buyer != address(0x0));
require(tokenSeller != address(0x0));
require(bal >= price,"buyer is not enough money");
require(tokenSeller != buyer,"owner not buy itself"); //본인은 구매를 못함
token.transferFrom(buyer,tokenSeller,price); // 구매자가 판매자게에 erc20토큰을 보내는 함수.
safeTransferFrom(tokenSeller, buyer, _tokenId); // 판매자가 구매자에게 tokenId를 넘기는 함수.
}
function startAuction(uint nftId, address owner, uint _startingBid) public {
auction[nftId].nftId = nftId;
auction[nftId].owner = owner;
auction[nftId].started = true;
auction[nftId].highestBid = _startingBid;
transferFrom(owner, address(this), nftId);
auction[nftId].endAt = block.timestamp + 1 days;
emit Endedat(auction[nftId].endAt);
}
function bid(uint nftId, address buyer, uint amount) public {
require(auction[nftId].started, "not started");
require(block.timestamp < auction[nftId].endAt, "ended");
require(amount > auction[nftId].highestBid, "value < highest");
if (auction[nftId].highestBidder != address(0)) {
bids[nftId][auction[nftId].highestBidder] += auction[nftId].highestBid;
//
//현재 가장 높은 입찰자의 입찰 금액에
//가장 높은 입찰자가 누군지도 알아야되서
//bids[highestBidder] 선언
//300이더
}
token.transferFrom(buyer, address(this), amount);
auction[nftId].highestBidder = buyer;
auction[nftId].highestBid = amount;
}
function withdraw(address addr, uint nftId) public {
uint bal = bids[nftId][addr];
bids[nftId][addr] = 0;
token.transferFrom(address(this), addr , bal);
//NftWt가 Erc20을 호출, msg.sender = NftWt
//
emit Withdraw(addr, bal);
}
function end(uint nftId) public {
require(auction[nftId].started, "not started");
require(block.timestamp >= auction[nftId].endAt, "not ended");
require(!auction[nftId].ended, "ended");
auction[nftId].ended = true;
if (auction[nftId].highestBidder != address(0)) {
safeTransferFrom(address(this), auction[nftId].highestBidder, nftId);
token.transferFrom(address(this), auction[nftId].owner, auction[nftId].highestBid);
} else {
safeTransferFrom(address(this), auction[nftId].owner, nftId);
}
emit End(auction[nftId].highestBidder, auction[nftId].highestBid);
}
}
- counter => tokenId를 사용하기 위해서 contract에서 ERC721URIStoraged를 상속받고 있다. 또 컨트랙트를 보면
Counter를 상속받는 것을 확인할 수 있다. 밑에사진은 Counter.sol contract의 위치를 제공하는 사진.
오픈제플린 -> utils -> Counter.sol contract를 들어가서 확인해볼 수 있다.
결국 들어가서 확인해 보면 알 수 있겠지만, tokenId값을 자동적으로 +1 해주고 있는 모습을 확인해 볼 수 있다. 다시 말하자면 mint함수를 구현시 들어가는 tokenId값을 자동으로 +1 해준다고 생각하면 될 것같다.
😾 IERC20 public token =>
Erc721 컨트랙트에서 사고판매시 다른 싸이트 들과 다르게 Erc20 토큰으로 구매, 판매를 해보기 위해서 이 변수를 사용합니다.
😾 mapping(uint 256 => uint256) public tokenPrice =>
토큰아이디를 통해서 해당 nft의 가격을 알 수 있게 만들어 놓은 맵핑 입니다.
지금까지 전체 Contract 코드를 제공하고 간다한 변수명에대해서 설명만 했다. 다음 글에서 기능에 해당되는 Contract 함수를 web3.js || caver.js와 같이 어떻게 사용했는지 설명해보려고 한다. 궁금한점이 있거나 논의해볼 사항이 있다면 댓글로 남겨주세요!