ERC-6105 중개 없는 NFT 거래 프로토콜 (No Intermediary NFT Trading Protocol)

Final · NFT·멀티토큰 · 2022-12-02 작성

NFT 컨트랙트 자체에 판매 등록과 구매 기능을 넣어 중개 거래 플랫폼 없이 ERC-721 토큰을 사고팔 수 있게 하는 표준입니다. 창작자는 더 다양한 로열티 방식을 구현할 수 있습니다.

왜 필요한가

지금의 NFT 거래 대부분은 중개 플랫폼을 거칩니다. 거래하려면 setApprovalForAll 함수로 플랫폼에 내 NFT 전체를 다룰 권한을 줘야 하는데, 플랫폼 컨트랙트에 문제가 생기면 큰 손실을 볼 수 있고 피싱 사기가 이 권한을 악용해 낮은 가격의 주문에 서명하게 만들 수도 있습니다. 플랫폼이 늘어날수록 유동성이 흩어져 여러 곳에 승인과 주문을 반복해야 하므로 가스 비용과 위험이 커지고 플랫폼 수수료도 부담입니다. 주문 정보가 체인 밖에 있어 애그리게이터(여러 플랫폼의 매물을 모아 보여 주는 서비스)의 효율도 플랫폼 API에 좌우됩니다. 또 로열티 적용 여부가 플랫폼의 중앙화된 결정에 달려 있어 일부 플랫폼은 프로젝트 동의 없이 로열티를 선택 사항으로 만들었고 상장 폐지 기준도 불투명해 검열에 취약합니다.

구현해야 하는 것

심층 설명

기본 인터페이스: 등록, 취소, 구매, 조회

listItem은 판매 등록을 만들거나 갱신합니다. tokenId가 존재해야 하고 호출자는 소유자이거나 승인된 운영자, 승인된 주소여야 하며 salePrice는 0이 될 수 없고 expires는 유효해야 합니다. 반드시 UpdateListing 이벤트를 발생시킵니다. delistItem은 등록된 토큰의 판매를 취소하며 역시 UpdateListing 이벤트를 남깁니다. buyItem은 호출자가 대금을 내고 토큰을 받는 함수로, 전달한 salePrice와 supportedToken이 등록된 값과 일치해야 하고 체결 시 Purchased 이벤트를 발생시킵니다. getListing은 (판매 가격, 만료 시각, 결제 토큰, 기준 가격)을 반환하며 가격이나 만료가 0이면 판매 중이 아니고 결제 토큰 주소가 0이면 ETH 결제를 뜻합니다.

salePrice, expires, supportedToken 설계 근거

salePrice를 0으로 두지 못하게 한 이유는 세 가지입니다. 가격을 0으로 거는 일은 대부분 조작 실수여서 자산 손실로 이어지기 쉽고 가스를 써서 등록하는데 가격이 0이면 수익이 음수가 되어 경제적으로 맞지 않으며 가격 0을 비매물 표시로 쓰면 구현이 단순해집니다. expires를 두면 등록이 자동으로 만료되므로 소유자가 delistItem을 따로 호출할 필요가 없어 가스를 아낍니다. supportedToken을 두면 판매자나 컨트랙트 소유자가 받을 결제 토큰을 고를 수 있고 하나의 토큰에 묶이지 않습니다. 0 주소는 ETH를 뜻합니다. acceptCollectionOffer와 acceptItemOffer의 변수 설계 근거도 같습니다.

benchmarkPrice와 다양한 로열티 방식

listItem, acceptCollectionOffer, acceptItemOffer에 benchmarkPrice 매개변수를 더하면 ERC-2981의 royaltyInfo(uint256 _tokenId, uint256 _salePrice)에 들어가는 _salePrice를 taxablePrice로 바꿔 쓸 수 있어 로열티 방식이 다양해집니다. 원문이 드는 예시는 네 가지입니다. 차익 로열티(VAR)는 taxablePrice=max(salePrice-historicalPrice, 0)으로 판매자의 이익 부분에만 로열티를 매깁니다. 판매 로열티(SR)는 taxablePrice=salePrice, 상한 로열티(CR)는 taxablePrice=min(salePrice, constant), 정액 로열티(QR)는 taxablePrice=constant로 거래마다 고정 로열티를 냅니다.

선택 확장: 컬렉션 오퍼와 아이템 오퍼

두 확장은 모두 선택 사항입니다. 컬렉션 오퍼 확장에서는 구매 희망자가 makeCollectionOffer로 컬렉션의 아무 토큰이나 개당 salePrice에 amount개까지 사겠다고 제안합니다. 호출자는 충분한 결제 토큰을 보유하고 컨트랙트에 충분한 금액을 승인해 두어야 합니다. 토큰 소유자나 승인된 주소가 acceptCollectionOffer로 수락하면 Purchased 이벤트가 발생하고 구매 희망 수량 amount가 1 줄어듭니다. 아이템 오퍼 확장에서는 makeItemOffer로 특정 tokenId에 제안을 걸고 acceptItemOffer로 수락하며 체결되면 오퍼 정보가 삭제됩니다. 두 확장 모두 취소 함수는 오퍼를 낸 본인만 호출할 수 있고 benchmarkPrice를 받는 수락 함수 오버로드가 있습니다.

참조 구현이 보여 주는 처리 방식

참조 구현은 차익 로열티(VAR) 방식을 씁니다. Listing 구조체에 판매자가 이 토큰을 마지막으로 산 가격인 historicalPrice를 저장하고 _calculateRoyalties에서 salePrice가 historicalPrice보다 클 때만 그 차액을 royaltyInfo에 넘깁니다. buyItem은 nonReentrant로 보호되며 ETH 결제면 msg.value가 salePrice와 같아야 하고 ERC-20 결제면 allowance가 salePrice 이상인지 확인한 뒤 transferFrom으로 로열티 수령인과 판매자에게 나눠 지급하고 토큰을 전송합니다. 또 _beforeTokenTransfer에서 판매 중인 토큰이 전송되면 리스팅을 자동 삭제해 소유자가 바뀐 뒤에도 옛 등록이 남는 일을 막습니다.

블록리스트는 표준 밖의 선택

로열티를 지키지 않는 중개 마켓에서 토큰이 거래되지 못하게 막아야 한다는 견해가 있지만 이 표준은 중개 없는 거래 기능만 제공하고 그런 마켓을 차단하는 표준 인터페이스는 정의하지 않습니다. 프로젝트 팀과 커뮤니티 보호가 필요하다고 판단하면 구현 컨트랙트에 블록리스트를 추가해 로열티 정책을 지키지 않는 플랫폼에서 이뤄지는 거래를 막는 것을 고려할 수 있습니다. 이 표준은 Final 상태로 확정되었고 ERC-721, ERC-2981과 호환되며 ERC-20, ERC-165, ERC-721, ERC-2981을 전제로 합니다.

쓸 때 주의할 점

ERC 원문 · ← ERCs