ERC-4973 계정 귀속 토큰(ABT) (Account-bound Tokens)
Review · NFT·멀티토큰 · 2022-04-01 작성
전송할 수 없이 하나의 계정에 묶이는 NFT인 계정 귀속 토큰(Account-bound Token, ABT)의 발행·수락·해제·추적 인터페이스를 정의합니다. 받는 쪽의 서명 동의로만 발행되고, 받은 사람은 언제든 해제할 수 있습니다.
왜 필요한가
일반 NFT는 자유롭게 사고팔 수 있어서, 토큰을 갖고 있다는 사실이 그 사람이 직접 무언가를 해냈다는 증명이 되지 못합니다. 이더리움 커뮤니티는 게임 월드 오브 워크래프트의 소울바운드(soulbound) 아이템처럼 시장 가격이 붙지 않고 계정에 영구히 묶이는 토큰이 필요하다는 요구를 표현해 왔고 실제로 인기 컨트랙트들이 이런 계정 귀속 상호작용 권리를 암묵적으로 구현하고 있습니다. 하지만 표준 없이 각자 전송 함수를 실패하게 만드는 식으로 구현하면, 지갑이나 마켓플레이스가 이 토큰이 전송 불가인지 기계적으로 알아낼 방법이 없고 온체인 데이터 인덱싱도 제각각이 됩니다. 이 표준은 ERC-721과 최대한 호환되면서도 최소한의 인터페이스만으로 계정 귀속 토큰을 만들고 할당하고 회수하고 추적하는 방법을 통일해 상호운용성과 인덱싱을 개선하려 합니다.
구현해야 하는 것
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);: ABT의 소유가 바뀔 때마다 발생하는 이벤트입니다. 토큰이 주어지거나 장착·해제될 때(해제는 to가 영주소) 발생합니다.function balanceOf(address owner) external view returns (uint256);: 한 주소에 할당된 ABT 개수를 셉니다. 영주소에 대한 조회는 실패합니다.function ownerOf(uint256 tokenId) external view returns (address);: 해당 ABT가 묶여 있는 소유자 주소를 돌려줍니다. 영주소에 할당된 토큰 조회는 실패합니다.function unequip(uint256 tokenId) external;: 받은 사람이 언제든 호출해 자신과 ABT의 연결을 공개적으로 끊습니다. to가 영주소인 Transfer 이벤트를 발생시켜야 합니다.function give(address to, bytes calldata metadata, bytes calldata signature) external returns (uint256);: msg.sender가 to에게 ABT를 만들어 줍니다. to가 EIP-712 합의 데이터에 서명한 signature가 유효해야 하며 새 tokenId를 돌려줍니다.function take(address from, bytes calldata metadata, bytes calldata signature) external returns (uint256);: msg.sender가 from으로부터 ABT를 만들어 받아옵니다. from의 EIP-712 서명이 유효해야 하며 새 tokenId를 돌려줍니다.function decodeURI(bytes calldata metadata) external returns (string memory);: 불투명한 metadata 바이트열을, 토큰이 생성될 때 연결될 토큰 URI 문자열로 해석해 돌려줍니다.
심층 설명
ERC-721 호환 전략과 인터페이스 감지
ABT 컨트랙트는 ERC-165의 ERC165(0x01ffc9a7)와 ERC-721의 ERC721Metadata(0x5b5e139f)를 반드시 구현해야 하고 ERC-721의 전송·추적 인터페이스(0x80ac58cd)는 구현하면 안 됩니다. 이 인터페이스 자체의 ERC-165 식별자는 0xeb72bb7c입니다. 지갑·마켓플레이스가 supportsInterface로 0x80ac58cd가 없음을 확인하면 전송을 UI 옵션으로 제시하지 않아야 하고 ERC721Metadata는 그대로 지원하므로 변경 없이 토큰을 표시할 수 있습니다. 전송 함수를 모두 되돌리는 다른 구현 방식도 가능하지만 원문은 ERC-165 기능 감지를 지원하는 이 방식이 더 낫다고 설명합니다. ownerOf·balanceOf 같은 이름과 Transfer 이벤트 시그니처도 기존 구현자의 학습 비용을 줄이려고 ERC-721 용어를 그대로 재사용했습니다.
give와 take: 양쪽 합의로만 발행
ABT는 give나 take로만 만들어집니다. give는 msg.sender가 to에게 주는 함수이고 take는 msg.sender가 from으로부터 받아오는 함수입니다. 두 함수 모두 상대방이 EIP-712 구조화 데이터 Agreement(address active,address passive,bytes metadata)에 서명해 msg.sender 및 metadata와 공개적으로 연결되는 데 명시적으로 동의했음을 증명하는 signature가 없으면 실패해야 합니다. signature가 비어 있거나 상대 주소가 컨트랙트면 EIP-1271 호환 isValidSignatureNow(...) 호출로 검증해야 하므로, 다중서명 지갑 같은 컨트랙트 계정도 참여할 수 있습니다. tokenId는 이 EIP-712 해시(bytes32)를 uint256으로 형변환해 만들며 같은 tokenId가 이미 존재하면 give·take는 실패해야 합니다. 성공하면 Transfer 이벤트가 발생합니다.
unequip: 언제든 분리할 권리
받은 사람은 언제든 unequip(tokenId)을 호출해 자신과 ABT의 연결을 공개적으로 끊을 수 있어야 합니다. 실행 시 to가 영주소인 Transfer 이벤트를 발생시켜야 하고 이후 원래 give 또는 take를 호출할 때 쓴 것과 같은 파라미터로 토큰을 다시 장착할 수 있어야 합니다. 원문은 이 요구사항을, 자기 계정 앞으로 발급된 것과 자신을 공개적으로 분리할 소유자의 권리를 보장하는 장치로 설명합니다. 월드 오브 워크래프트에서 플레이어가 소울바운드 아이템을 장착·해제하거나 버려서 업적을 보여 줄지 스스로 선택했던 것에 대응합니다.
이벤트 기반 인덱싱 규칙
ABT는 Transfer(address indexed from, address indexed to, uint256 indexed tokenId) 이벤트 발생을 추적해 인덱싱할 수 있습니다. from과 to가 모두 영주소가 아니면 계정 간 지급이고 to가 영주소면 장착 해제(unequip)입니다. from이 영주소인 민트(mint) 연산은 존재하지 않습니다. 악의적으로 Transfer 이벤트를 흉내 내는 컨트랙트에 속지 않으려면, 인덱서는 트랜잭션의 발신자가 이벤트의 from 값과 같은지 확인해야 합니다.
키 탈취·교체 시의 예외 처리
ABT는 계정 간 전송이 불가능하므로, 계정이나 컨트랙트의 키가 탈취되거나 교체되면 사용자가 토큰과 자신을 연결할 능력을 잃을 수 있습니다. 경우에 따라서는 이것이 의도된 효과일 수도 있습니다. 원문은 이런 경우의 처리 방식을 의도적으로 표준화하지 않는 대신, 구현자가 구제 수단으로 재발급과 회수 절차를 만들 것을 권하고 엄격하게 탈중앙화되고 무허가적이며 검열 저항적인 재발급 절차를 권장합니다. 여러 계정이 함께 쓸 수 있게 하려면 다중서명 기능을 구현한 컨트랙트 계정 앞으로 토큰을 발행하는 방법을 제안합니다.
메타데이터 처리와 제안 상태
decodeURI(bytes metadata)는 give·take에 전달되는 불투명한 metadata 바이트열을, 토큰이 체인에 만들어질 때 연결될 토큰 URI 문자열로 풀어 주는 함수입니다. 메타데이터 JSON 스키마는 ERC-721의 정의를 그대로 따릅니다. 서명 생성을 위한 Node.js 참조 구현(index.mjs 등)과 Solidity 참조 구현(ERC4973-flat.sol)이 원문에 첨부되어 있습니다. 이 제안의 현재 상태는 Review로, 아직 검토 중이어서 내용이 바뀔 수 있는 제안입니다.