NFT에 대해 정리한 글
정보
표준
스마트 컨트랙트가 준수해야되는 규칙이며 프로토콜과 비슷하다. 해당 표준을 준수하는 인터페이스가 있고, 인터페이스의 메서드(ERC-721 기준 transferFrom, approve, ownerOf)들을 구현해둬야 표준을 준수했다고 할 수 있게 된다.
기본적인것만 구현하면, 직접 사용할 것들은 추가로 구현해도 괜찮다. 표준을 준수해야 다른 앱들과 연동했을때 문제 없이 동작되게 할 수 있다.
ERC-721
하나의 컨트랙트에 하나의 콜렉션, 여러개의 토큰ID 마다 각각의 고유한 NFT를 의미하며 진정한 의미의 NFT(NonFungibleToken)
하나의 컨트랙트에서 하나의 콜렉션만 만들 수 있기 때문에 여러개의 콜렉션을 관리하려면 팩토리 컨트랙트에서 NFT 컨트랙트를 생성하는 방식(팩토리패턴)으로 구현해야 한번의 배포로 여러개의 콜렉션을 관리할 수 있게 된다.
OpenZeppelin라이브러리를 사용하면 아래의 표준 함수나 이벤트들이 이미 구현된 상태로 시작할 수 있고 필요한 부분만 오버라이드 해서 사용하면 된다.
contract MyCustomNFT is ERC721 {
constructor(string memory name, string memory symbol) ERC721(name, symbol) {}
// OpenZeppelin의 _mint 함수를 오버라이드
function _mint(address to, uint256 tokenId) internal override {
// 커스텀 로직 추가
super._mint(to, tokenId); // 부모 컨트랙트의 _mint 함수 호출
}
}
표준 함수
balanceOf(address _owner): _owner가 소유한 NFT의 수를 반환합니다. 이 함수는 0이 아닌 주소를 입력으로 받아야 합니다.ownerOf(uint256 _tokenId): 토큰 ID _tokenId의 소유자 주소를 반환합니다.safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data): _from 주소에서 _to 주소로 토큰 ID _tokenId를 안전하게 전송합니다. data는 추가 정보를 전달하는 데 사용됩니다.safeTransferFrom(address _from, address _to, uint256 _tokenId): 위의 함수와 유사하지만, data 파라미터 없이 토큰을 전송합니다.transferFrom(address _from, address _to, uint256 _tokenId): _from에서 _to로 토큰 ID _tokenId를 전송합니다. 이 함수는 토큰의 소유권이나 전송 권한을 검증해야 합니다.approve(address _approved, uint256 _tokenId): 다른 주소 _approved에게 단일 토큰 _tokenId의 전송 권한을 부여합니다.setApprovalForAll(address _operator, bool _approved): _operator가 호출자의 모든 NFT를 대신 전송할 수 있는 권한을 부여하거나 철회합니다.getApproved(uint256 _tokenId): 토큰 ID _tokenId에 대해 현재 승인된 주소를 반환합니다.isApprovedForAll(address _owner, address _operator): _operator가 _owner의 모든 토큰을 전송할 수 있는 권한을 가지고 있는지 여부를 반환합니다.tokenURI(uint256 tokenId): 토큰 ID로 URI를 구하는 함수인데, 필수는 아니고 선택이지만, 대부분 구현한다.
표준 이벤트
Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId): 토큰이 전송될 때마다 발생합니다.Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId): 토큰의 전송 권한이 부여될 때 발생합니다.ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved): 소유자가 자신의 모든 NFT에 대한 전송 권한을 _operator에게 부여하거나 철회할 때 발생합니다.
팩토리 컨트랙트 구현
팩토리에서 생산할 NFT 컨트랙트
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract NFTCollection is ERC721URIStorage, Ownable {
constructor(string memory name, string memory symbol) ERC721(name, symbol) {}
function mintNFT(address recipient, uint256 tokenId) public onlyOwner {
_mint(recipient, tokenId);
}
}
팩토리 컨트랙트
이렇게 구현하면 ERC-721로 동적으로 컬렉션을 만들고 관리할 수 있다는게 장점이지만, 동적으로 컬렉션을 만들때마다 NFT 컨트랙트가 블록체인 네트워크에 배포되기 때문에 배포에 대한 상당한 가스비용이 발생한다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./NFTCollection.sol";
contract NFTFactory {
// 생성된 NFT 컬렉션의 주소를 추적하는 배열
address[] public collections;
event CollectionCreated(address collectionAddress, string name, string symbol);
// NFT 컬렉션을 생성하는 함수
function createCollection(string memory name, string memory symbol) public {
NFTCollection newCollection = new NFTCollection(name, symbol);
collections.push(address(newCollection));
// 콜렉션이 생성됐다는걸 모든 노드들에게 알린다.
// 수신자가 없을거라면 굳이 만들지 않아도된다.
emit CollectionCreated(address(newCollection), name, symbol);
// 콜렉션을 생성한 주체에게 생성한 NFT 민팅
newCollection.mintNFT(msg.sender, symbol);
}
// 생성된 모든 컬렉션의 주소를 반환하는 함수
function getCollections() public view returns (address[] memory) {
return collections;
}
}
ERC-1155
하나의 컨트랙트에서 여러개의 콜렉션을 만들 수 있고, 하나의 토큰 id도 여러개를 발행할 수 있으며 배치전송이 가능한 표준. ERC-721의 단점을 보완하기 위해 나온 방식.
배치 전송
한꺼번에 여러 토큰을 전송하는 방식인데, ERC-721은 기본적으로 지원하지 않는다.
커스텀해서 구현하고, NFT를 생성한 지갑에 전송할때만 사용할 것이다.
이더리움에서는 한번의 트랜잭션에서 여러번의 mint를 한다고 해도 한번의 트랜잭션으로 처리되기 때문에 batchMint 함수에서 for문 돌면서 mint해서 구현할 수 있다.
contract NFTFactory {
function batchMint(string[] memory tokenURIs, uint256[4][] memory attributesList) public {
for (uint256 i = 0; i < tokenURIs.length; i++) {
uint256 tokenId = i;
nftContract.mintNFT(msg.sender, tokenId, tokenURIs[i], attributesList[i]);
}
}
}
규칙
- 언더바로 시작하는 함수는 내부 함수의 네이밍 규칙이다. (필수 규칙이 아니라 그냥 개발자들 사이의 약속)
Traits
OpenSea에서 NFT를 눌러보면 각 NFT를 표현하는 Traits라는게 있는걸 알 수 있다. 이것은 사실 NFT 표준은 아니고 OpenSea에 제공하는 NFT의 메타데이터일뿐이다.
메타데이터는 _tokenURI 링크를 통해 가져올 수 있어야 하는데 보통은 ipfs에 NFT 메타데이터를 저장하고 그 링크를 _tokenURI로 저장하게 된다.
메타데이터를 OpenSea가 인식하도록 구현하려면 opensea의 docs를 확인해서 메타데이터의 포맷을 맞춰줘야 한다.
opensea:metadata-standards
대충 아래와 같은 형식이다.
{
"description": "Friendly OpenSea Creature that enjoys long swims in the ocean.",
"external_url": "https://openseacreatures.io/3",
"image": "https://storage.googleapis.com/opensea-prod.appspot.com/puffs/3.png",
"name": "Dave Starbelly",
"attributes": [
{"trait_type": "Color", "value": "Red"},
{"trait_type": "Shape", "value": "Circle"},
{"trait_type": "Size", "value": "Large"}
]
}
Comments