ERC-7994 조건부 해제 용도 제한 토큰 (Purpose-Bound ERC-20 with Conditional Unlock)

Final · 토큰 · 2025-07-29 작성

ERC-20 토큰 전송에 시간, 신원 확인, 사용처 같은 여러 해제 조건을 함께 걸어 두고, 받는 사람이 모든 조건을 충족했을 때만 토큰을 찾아갈 수 있게 하는 표준입니다.

왜 필요한가

일반 ERC-20 토큰은 한번 보내면 그걸로 끝입니다. 받은 사람이 언제, 어디에, 어떤 자격으로 쓰는지 보내는 쪽이 통제할 방법이 없습니다. 앞선 표준인 ERC-7291은 토큰을 어떻게, 어디서 쓸 수 있는지 제한하는 용도 제한 화폐(purpose-bound money) 개념을 도입했지만 현실의 많은 사례는 조건 하나로는 부족합니다. 예를 들어 장학금은 받는 사람이 신원 확인(KYC)을 마쳤고 25세 미만이며 대학에 등록되어 있어야 한다는 세 조건을 동시에 만족해야 합니다. 구호 단체 지원금은 특정 해제 날짜 이후에 식품과 의약품에만 쓰여야 하고 급여 토큰은 매달 풀리면서 은행이나 의료기관 같은 허가된 거래처에만 쓰여야 할 수 있습니다. 이 표준은 이런 복수 조건을 ERC-20 위에 겹겹이 얹을 수 있는 공통 구조를 정의합니다.

구현해야 하는 것

심층 설명

퍼포즈 바인딩과 UnlockCondition 구조

이 표준에서 조건부 전송 한 건을 퍼포즈 바인딩(purpose binding)이라고 부릅니다. 바인딩에는 recipient와 amount, UnlockCondition 객체의 집합, 그리고 선택적인 expiry 타임스탬프가 들어갑니다. expiry가 지나면 그 전송은 더 이상 찾아갈 수 없습니다. UnlockCondition은 두 필드로 구성됩니다. conditionType은 "TIME", "KYC", "WHITELIST" 같은 조건 종류를 나타내는 bytes32 값이고, conditionData는 그 조건을 평가하는 데 쓰는 bytes 데이터로 타임스탬프나 머클 루트 등이 들어갑니다. 조건 종류를 열거형으로 고정하지 않고 bytes32와 bytes 조합으로 두어 새 조건 유형을 자유롭게 정의할 수 있게 했습니다.

bindPurpose의 동작과 bindingId 생성

참조 구현에서 bindPurpose는 recipient가 0 주소가 아니고 amount가 0보다 큰지 확인한 뒤, 보내는 사람의 토큰을 _transfer로 컨트랙트 자신에게 옮겨 잠급니다. bindingId는 keccak256(abi.encodePacked(msg.sender, recipient, amount, conditions, expiry, block.timestamp))로 만들며, 같은 bindingId의 바인딩이 이미 있으면(stored.amount != 0) 되돌립니다. 이후 조건 배열을 저장소에 복사하고 recipient, amount, expiry를 기록한 뒤 PurposeBound 이벤트를 발생시킵니다. 호출 시각(block.timestamp)이 해시에 들어가므로 같은 내용의 바인딩도 시점이 다르면 다른 ID를 갖습니다.

isUnlocked의 조건 평가 방식

참조 구현의 isUnlocked는 세 단계로 평가합니다. 먼저 이미 claimed된 바인딩이면 false를 돌려줍니다. 다음으로 expiry가 0보다 크고 현재 block.timestamp가 expiry를 넘었으면 false입니다. 마지막으로 바인딩에 담긴 모든 UnlockCondition을 순회하면서 conditionType에 등록된 검사기 컨트랙트를 conditionResolvers 매핑에서 찾아 isConditionMet(recipient, conditionData)를 호출합니다. 검사기가 등록되어 있지 않으면 "Checker not set"으로 되돌리고 조건 하나라도 충족되지 않으면 false입니다. 모든 조건을 통과해야 true가 됩니다.

claim의 검증과 지급

claim은 받는 사람 본인만 호출할 수 있습니다. 참조 구현은 msg.sender가 binding.recipient인지, 아직 claimed되지 않았는지, isUnlocked(bindingId)가 true인지를 차례로 확인한 뒤 claimed를 true로 바꾸고 컨트랙트가 보관하던 토큰을 recipient에게 _transfer합니다. 마지막으로 Claimed 이벤트를 발생시킵니다. 보안 고려사항은 claim()이 모든 조건을 원자적으로(atomic) 검증해야 한다고 요구합니다. 즉 조건 검증과 지급이 한 트랜잭션 안에서 함께 이루어져 검증과 지급 사이에 상태가 바뀌는 틈이 없어야 합니다.

플러그인 방식의 조건 검사기

조건 검사 로직은 토큰 컨트랙트 안에 하드코딩되지 않고 IConditionChecker 인터페이스를 구현한 별도 컨트랙트에 위임됩니다. 검사기는 isConditionMet(address recipient, bytes calldata conditionData)를 구현해 true 또는 false를 돌려줍니다. 관리자는 setConditionResolver(conditionType, checker)로 조건 유형별 검사기를 등록하거나 교체합니다. 참조 구현에서는 이 함수가 데모 목적으로 공개되어 있으며 실제 운영에서는 onlyOwner나 AccessControl로 보호해야 한다고 주석에 적혀 있습니다. 예시로 제공되는 TimeConditionChecker는 conditionData를 uint256 타임스탬프로 디코드해 현재 시각이 그 이후인지 검사합니다.

설계 근거: 모듈성과 온체인 증명

Rationale은 세 가지를 듭니다. 첫째, 유연성입니다. 조건이 모듈식이고 확장 가능해 새로운 조건 유형을 더할 수 있습니다. 둘째, 조합 가능성입니다. DAO, 급여, 교육, 규제 준수(compliance) 토큰에 통합할 수 있습니다. 셋째, 보안입니다. KYC 같은 오프체인 검증을 머클 루트 같은 온체인 증명으로 뒷받침하는 구조를 취합니다. 즉 개인정보가 담긴 심사는 체인 밖에서 하되, 그 결과를 체인 위에서 검증 가능한 형태로 올려 조건 충족을 확인합니다. 이 표준은 ERC-20과 ERC-7291을 필요로 하며 상태는 Final로 확정되었습니다.

쓸 때 주의할 점

ERC 원문 · ← ERCs