ERC-4906 EIP-721 메타데이터 갱신 확장 (EIP-721 Metadata Update Extension)
Final · NFT·멀티토큰 · 2022-03-13 작성
NFT의 메타데이터(이미지·속성 같은 설명 정보)가 바뀌었을 때 이를 알리는 표준 이벤트를 정합니다. NFT 마켓 같은 외부 서비스가 바뀐 정보를 제때 반영할 수 있습니다.
왜 필요한가
NFT의 이미지와 속성 같은 메타데이터는 한 번 정해지면 끝이 아니라 나중에 바뀔 수 있습니다. 많은 EIP-721 컨트랙트가 토큰의 메타데이터가 바뀔 때 이벤트를 내보내지만 그 이벤트의 이름과 형태가 컬렉션마다 다릅니다. NFT 마켓플레이스 같은 제3자 플랫폼이 이런 변경을 추적하는 것이 불가능하지는 않습니다. 다만 컬렉션 하나하나에 맞춘 처리 방식을 따로 만들어야 하므로 추가 작업이 계속 늘어납니다. 표준 이벤트가 하나 정해져 있으면 플랫폼은 같은 방식으로 수많은 NFT의 메타데이터 변경을 감지하고 제때 갱신할 수 있습니다. 이 제안은 그 표준 이벤트를 EIP-721의 선택 확장으로 추가합니다.
구현해야 하는 것
event MetadataUpdate(uint256 _tokenId);: 토큰 하나의 메타데이터가 바뀌었을 때 내보냅니다. NFT 마켓 같은 제3자 플랫폼이 이미지와 속성을 제때 갱신할 수 있게 합니다.event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId);: 연속된 토큰 ID 범위의 메타데이터가 바뀌었을 때 내보냅니다. 시작 ID와 끝 ID를 함께 알립니다.
심층 설명
EIP-721의 선택 확장
이 표준은 EIP-721의 확장이며 EIP-721 컨트랙트에 OPTIONAL입니다. 인터페이스 IERC4906은 IERC165와 IERC721을 상속하므로, 이를 구현하는 컨트랙트는 EIP-165 인터페이스 감지와 EIP-721 기본 기능을 함께 갖춰야 합니다. 요구 사항 표기(MUST, SHOULD, RECOMMENDED 등)는 RFC 2119를 따릅니다. 확장이 추가하는 것은 이벤트 두 개와 supportsInterface 응답 하나뿐이며 새로운 함수는 없습니다.
MetadataUpdate: 토큰 하나의 변경 알림
토큰의 JSON 메타데이터가 바뀌면 MetadataUpdate(uint256 _tokenId) 이벤트를 반드시(MUST) 내보내야 합니다. 이벤트에는 토큰 ID만 담기고 무엇이 어떻게 바뀌었는지는 담지 않습니다. 변경 내용을 알고 싶은 제3자는 이벤트를 받은 뒤 EIP-721의 tokenURI(uint256 _tokenId)를 호출해 새 메타데이터를 받아 갑니다. 즉 이벤트는 해당 토큰을 다시 읽으라는 신호 역할만 합니다.
BatchMetadataUpdate: 연속 범위의 변경 알림
연속된 토큰 ID 범위의 메타데이터가 바뀌면 BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId)를 내보냅니다. 단일 토큰 변경과 마찬가지로, JSON 메타데이터가 바뀌었을 때는 MetadataUpdate 또는 BatchMetadataUpdate 중 하나를 반드시 내보내야 합니다. 범위가 지정된 경우 제3자 플랫폼은 범위 안의 토큰 각각에 대해 tokenURI를 개별 조회해 갱신합니다. 원문은 범위를 연속(consecutive) 범위로 규정하므로, 흩어진 토큰 ID 여러 개를 한 이벤트로 묶는 용도는 아닙니다.
이벤트를 내지 않는 것이 권장되는 세 경우
원문은 세 상황에서 MetadataUpdate를 내지 않는 것을 RECOMMENDED로 둡니다. 첫째, 토큰이 민팅될 때입니다. 둘째, 토큰이 소각될 때입니다. 셋째, tokenURI는 바뀌었지만 그 URI가 가리키는 JSON 메타데이터 내용은 그대로일 때입니다. 즉 이벤트를 낼지는 JSON 메타데이터의 실제 변경을 기준으로 판단하고 URI 문자열의 변경 여부는 기준으로 삼지 않습니다. 이 세 경우는 MUST NOT이 아니라 RECOMMENDED이므로, 구현자가 반드시 따라야 하는 금지 조항은 아닙니다.
supportsInterface와 인터페이스 ID 0x49064906
이 확장을 지원하는 컨트랙트는 supportsInterface가 0x49064906으로 호출될 때 반드시 true를 반환해야 합니다. 참조 구현은 OpenZeppelin의 ERC721을 상속한 ERC4906 컨트랙트에서 supportsInterface를 오버라이드해 interfaceId가 bytes4(0x49064906)이면 true를 돌려주고 아니면 super.supportsInterface(interfaceId)에 넘깁니다. 생성자는 name_과 symbol_을 받아 ERC721에 그대로 전달합니다. 이벤트는 인터페이스에만 선언되어 있고 참조 구현에 이벤트를 발생시키는 코드는 따로 들어 있지 않습니다.
설계 이유: 이벤트에 변경 내용을 담지 않은 까닭
NFT마다 메타데이터 구성이 다르고 필드도 여러 개입니다. 바뀐 값을 bytes data 같은 형태로 이벤트에 담을 수도 있지만 제3자 플랫폼이 제각각인 bytes data의 종류를 해석하기는 어렵습니다. 불필요한 복잡성을 피하기 위해 MetadataUpdate 이벤트에는 임의의 메타데이터를 넣지 않았습니다. 대신 이벤트를 받은 쪽이 tokenURI로 전체 메타데이터를 다시 읽는 방식을 택했습니다. 하위 호환성 문제는 발견되지 않았고 원문 상태는 Final입니다.
쓸 때 주의할 점
- 오프체인 메타데이터 변경을 알리는 함수의 권한 관리: 메타데이터가 체인 밖에서 바뀌는 경우, MetadataUpdate를 발생시키는 함수를 컨트랙트에 추가할 수 있습니다. 이때 그 함수의 권한 제어가 올바르게 되어 있는지 반드시 확인해야 합니다.