ERC-6220 장착형 파츠 조합 NFT (Composable NFTs utilizing Equippable Parts)
Final · NFT·멀티토큰 · 2022-12-20 작성
NFT가 카탈로그에 미리 정의된 파츠를 골라 조합하고 다른 NFT를 슬롯에 장착하거나 해제할 수 있게 하는 ERC-721 확장 표준입니다. 장착해도 새 토큰이 생기지 않고 조회 시 함께 렌더링됩니다.
왜 필요한가
기존에는 여러 NFT를 하나의 구성물로 합치려면 계정에 있는 호환 NFT를 모두 긁어모아 무차별로 쓰거나, 스마트 컨트랙트나 체인 밖 데이터베이스에 별도 장부를 만들어 관리해야 했습니다. 같은 슬롯에 쓰려는 NFT가 둘 이상이면 어떤 것이 적용될지 예측할 수 없는 문제도 있었습니다. 또 게임 캐릭터가 성장하며 아이템을 얻는 경우 아이템마다 NFT를 따로 들고 다녀야 했고 장착 상태를 중앙 운영자가 관리해야 했습니다. 희소성 문제도 있습니다. 같은 모자 NFT를 500개의 게임 인스턴스에서 500명의 아바타에 동시에 씌울 수 있는데, 시각 표현이 클라이언트 쪽 처리일 뿐이기 때문입니다. 이 표준은 어떤 파츠를 전체의 일부로 쓸지 NFT 하나가 직접 선택하고 그 정보를 체인 위에 두는 틀을 만들어, 예측 가능한 조합과 실제 디지털 희소성을 제공합니다.
구현해야 하는 것
event SetEquippables(uint64 indexed partId, address[] equippableAddresses);: 파츠의 장착 가능 주소 목록이 새 목록으로 덮어써졌음을 알리는 이벤트입니다.event SetEquippableToAll(uint64 indexed partId);: 해당 파츠가 모든 주소에서 장착 가능하도록 표시되었음을 알리는 이벤트입니다.function getMetadataURI() external view returns (string memory);: 카탈로그의 기본 메타데이터 URI를 반환합니다.function getType() external view returns (string memory);: 카탈로그의 itemType을 반환합니다.function checkIsEquippable(uint64 partId, address targetAddress) external view returns (bool);: 지정한 주소의 컬렉션이 해당 파츠에 장착될 수 있는지 확인합니다.function checkIsEquippableToAll(uint64 partId) external view returns (bool);: 해당 파츠가 모든 주소에서 장착 가능한지 확인합니다.function getPart(uint64 partId) external view returns (Part memory);: 파츠 ID로 Part 구조체 하나를 조회합니다.function getParts(uint64[] calldata partIds) external view returns (Part[] memory);: 여러 파츠 ID에 해당하는 Part 구조체 배열을 한 번에 조회합니다.
심층 설명
두 종류의 파츠: 고정과 슬롯
고정(Fixed) 파츠는 자체 메타데이터를 가진 완결된 구성 요소로, NFT의 수명 동안 바뀌지 않고 교체할 수 없습니다. 한 번만 정의하면 여러 컬렉션의 여러 토큰이 장착할 수 있어 반복 자산을 효율적으로 배포합니다. 슬롯(Slot) 파츠는 자체 메타데이터가 없고 선택된 NFT 컬렉션을 그 안에 장착할 수 있게 합니다. 장착된 토큰이 자신의 메타데이터를 제공하므로, 소유자가 제어하는 교체 가능한 콘텐츠가 됩니다. 두 종류를 모두 쓸 필요는 없고 필요한 구성으로만 적용할 수 있습니다.
카탈로그 구조와 Part 구조체
카탈로그는 고정·슬롯 파츠의 모음이며 하나의 컬렉션에 묶이지 않고 여러 NFT 컬렉션을 지원할 수 있습니다. Part 구조체는 itemType(None, Slot, Fixed), 렌더링 깊이를 정하는 z(uint8), 장착을 허용하는 주소 배열 equippable, metadataURI로 구성됩니다. 파츠는 IntakeStruct로 partId를 지정해 추가하며 이미 쓰인 partId를 쓰면 추가가 되돌려집니다. 카탈로그 항목 하나는 최소 3개의 스토리지 슬롯(베를린 하드포크 기준 약 60,000 가스)이 필요하고 IPFS URI 길이를 고려하면 5~7 슬롯이 현실적입니다. 에셋 250개당 25,000,000~35,000,000 가스가 들며 메인넷 최대 블록 크기는 30M입니다.
equip·unequip과 Equipment 저장 구조
equip은 IntakeEquip 구조체(tokenId, childIndex, assetId, slotPartId, childAssetId)를 입력으로 받습니다. 장착해도 새 토큰이 생성되지 않고 토큰을 조회할 때 함께 렌더링될 구성 요소가 추가될 뿐입니다. unequip은 토큰 소유자 또는 소유자가 관리 권한을 준 계정만 호출할 수 있습니다. 장착 상태는 Equipment 구조체(assetId, childAssetId, childId, childEquippableAddress)로 저장되며 getEquipment로 슬롯별로 조회합니다. 각 동작은 ChildAssetEquipped, ChildAssetUnequipped 이벤트로 알립니다.
장착 권한 검증 체계
무엇이 어디에 장착될 수 있는지는 여러 층에서 검증됩니다. 카탈로그 쪽에서는 checkIsEquippable(partId, targetAddress)로 특정 컬렉션이 파츠에 장착 가능한지, checkIsEquippableToAll(partId)로 모든 주소에 열린 파츠인지 확인합니다. 토큰 쪽에서는 에셋이 equippableGroupId라는 장착 그룹에 속하고 ValidParentEquippableGroupIdSet 이벤트가 알리듯 특정 부모 컬렉션의 특정 슬롯에 그룹 단위로 장착 가능 여부가 지정됩니다. canTokenBeEquippedWithAssetIntoSlot으로 장착 전에 가능 여부를 조회할 수 있습니다. NFT를 아무 감독 없이 직접 장착하게 하면 결과 조합을 예측할 수 없으므로, 카탈로그로 파츠를 사전 검증해 의도대로 조합되게 하는 것이 설계 의도입니다.
증명 가능한 디지털 희소성
기존 NFT는 공급량이 제한되어도 활용은 제한되지 않았습니다. 같은 지갑과 같은 NFT로 500개의 게임 인스턴스에 접속해 같은 모자를 500명의 아바타에 동시에 씌울 수 있는데, 시각 표현이 클라이언트 쪽 처리일 뿐이기 때문입니다. 이 표준은 모자가 한 아바타에 장착되면(아바타 토큰 안으로 전송된 뒤 장착) 다른 아바타에는 장착될 수 없도록 강제합니다. isChildEquipped는 장착된 자식의 전송을 막는 용도로 쓰여 이 보장을 뒷받침합니다.
의존 표준과 호환성
이 표준은 ERC-165, ERC-721, ERC-5773, ERC-6059를 필요로 합니다. IERC6220 인터페이스는 IERC5773을 상속하며 ERC-165 식별자는 0x28bc9ae4, ICatalog의 식별자는 0xd912401f입니다. ERC-721과 호환되게 설계되어 기존 ERC-721 도구와 인프라를 그대로 활용할 수 있습니다. 장착된 파츠 전체를 한 번에 가져오는 메서드는 모든 구현자에게 필요한 것이 아니어서 포함하지 않았고 확장이나 훅으로 추가하거나 인덱서로 대신할 수 있습니다. 상태는 Final로 확정된 표준입니다.
쓸 때 주의할 점
- 숨은 로직 주의: ERC-721과 같은 보안 고려 사항이 적용됩니다. 소각(burn), 리소스 추가, 리소스 수락 등 어떤 함수에든 숨은 로직이 있을 수 있으므로, 감사받지 않은 컨트랙트를 다룰 때는 주의해야 합니다.