ERC-7943 범용 실물자산 토큰 인터페이스 (uRWA - Universal Real World Asset Interface)
Final · 토큰 · 2025-06-10 작성
증권·부동산 같은 실물자산(RWA) 토큰에 필요한 규제 대응 기능, 즉 전송 가능 여부 조회와 자산 동결, 강제 이전을 ERC-20·721·1155 같은 기존 토큰 표준 위에 얹는 공통 인터페이스를 정의합니다.
왜 필요한가
증권이나 부동산 같은 실물자산을 토큰으로 만들면 규제가 따라옵니다. 허가된 사용자에게만 전송을 허용하거나, 법 집행을 위해 자산을 동결하고 강제로 옮겨야 할 수 있습니다. 그런데 ERC-20, ERC-721, ERC-1155 같은 기존 토큰 표준에는 이런 요구를 담을 구조가 없습니다. 그래서 발행사마다 제각각의 방식으로 규제 기능을 붙였고 탈중앙 금융(DeFi) 프로토콜이 실물자산 토큰과 연동하려면 토큰마다 다른 방식을 일일이 알아야 했습니다. 과거의 범용 RWA 표준 시도는 세분화된 역할 기반 접근 제어, 의무적인 온체인 허용 명단, 특정 온체인 신원 시스템, 메타데이터 처리까지 표준이 강제해, 그런 기능이 필요 없는 단순한 용도에도 불필요한 복잡성과 가스 비용을 떠안겼습니다. 자산의 종류가 워낙 다양해 모든 것을 정해 주는 하나의 큰 틀로 담기도 어렵습니다. 구현 방법은 각자에게 맡기되 겉모습만 통일하는 최소한의 공통 인터페이스가 필요했습니다.
구현해야 하는 것
event ForcedTransfer(address indexed from, address indexed to, uint256 amount);: 토큰이 한 주소에서 압류되어 다른 주소로 옮겨졌을 때 발생하는 이벤트입니다.event Frozen(address indexed account, uint256 amount);: setFrozenTokens 호출로 계정의 동결 수량이 바뀌었을 때 발생하며 변경 후의 동결 수량을 담습니다.function forcedTransfer(address from, address to, uint256 amount) external returns (bool result);: 권한 있는 주체가 from의 토큰을 to로 강제로 옮깁니다. 규제 준수나 자산 복구에 쓰입니다.function setFrozenTokens(address account, uint256 amount) external returns (bool result);: 계정의 동결 수량을 새 값으로 덮어씁니다. 동결된 토큰은 그 계정이 전송할 수 없습니다.function canSend(address account) external view returns (bool allowed);: 이 계정이 토큰을 보낼 자격이 있는지 확인합니다. 허용 명단이나 KYC 확인에 흔히 쓰입니다.function canReceive(address account) external view returns (bool allowed);: 이 계정이 토큰을 받을 자격이 있는지 확인합니다.function getFrozenTokens(address account) external view returns (uint256 amount);: 계정의 현재 동결 수량을 반환합니다. 잔액보다 큰 값이 나올 수도 있습니다.function canTransfer(address from, address to, uint256 amount) external view returns (bool allowed);: 이 전송이 토큰 규칙상 지금 가능한지 확인합니다. 동결 수량 검증까지 포함합니다.function setFrozenTokens(address account, uint256 tokenId, bool frozenStatus) external returns (bool result);: ERC-721용 변형입니다. 특정 tokenId의 동결 여부를 참·거짓으로 지정합니다.function getFrozenTokens(address account, uint256 tokenId) external view returns (bool frozenStatus);: ERC-721용 변형입니다. 특정 tokenId가 그 계정에 대해 동결됐는지 반환합니다.function canTransfer(address from, address to, uint256 tokenId) external view returns (bool allowed);: ERC-721용 변형입니다. 특정 tokenId의 전송이 가능한지 확인합니다.function forcedTransfer(address from, address to, uint256 tokenId, uint256 amount) external returns (bool result);: ERC-1155용 변형입니다. 특정 tokenId 토큰을 지정한 수량만큼 강제로 옮깁니다.function setFrozenTokens(address account, uint256 tokenId, uint256 amount) external returns (bool result);: ERC-1155용 변형입니다. 특정 tokenId의 동결 수량을 덮어씁니다.function getFrozenTokens(address account, uint256 tokenId) external view returns (uint256 amount);: ERC-1155용 변형입니다. 특정 tokenId의 동결 수량을 반환합니다.function canTransfer(address from, address to, uint256 tokenId, uint256 amount) external view returns (bool allowed);: ERC-1155용 변형입니다. 특정 tokenId를 지정 수량만큼 전송할 수 있는지 확인합니다.
심층 설명
하나의 표준, 세 가지 인터페이스
ERC-7943 토큰은 ERC-20, ERC-721, ERC-1155, ERC-6909 중 하나의 기본 토큰 인터페이스를 반드시 확장해야 합니다. 기본 표준에 맞춰 세 인터페이스가 정의됩니다. IERC7943Fungible(ERC-20용), IERC7943NonFungible(ERC-721용), IERC7943MultiToken(ERC-1155·ERC-6909용)입니다. 함수 구성은 같지만 매개변수가 다릅니다. 대체가능 토큰은 수량(amount), NFT는 tokenId와 동결 여부(bool), 멀티토큰은 tokenId와 수량을 씁니다. ERC-165 supportsInterface는 대체가능 인터페이스에 0x3edbb4c4, 비대체가능 인터페이스에 0xbf1ef5fe, 멀티토큰 인터페이스에 0x41c4fbad의 interfaceId에 대해 true를 반환해야 합니다. forcedTransfer와 setFrozenTokens 같은 민감한 함수에는 onlyOwner나 역할 기반 접근 제어 같은 접근 제한을 반드시 적용해야 하며 어떤 방식을 쓸지는 이 표준이 정하지 않습니다.
canSend·canReceive는 계정 단위 자격 검사
canSend와 canReceive는 상대방, 수량, tokenId 같은 개별 전송 조건과 무관하게 계정 자체의 자격을 확인하는 함수입니다. 허용 명단이나 KYC/KYB/AML 확인에 흔히 쓰입니다. 두 함수는 revert하면 안 되고(MUST NOT), 컨트랙트 저장소를 바꾸면 안 되며 현재 타임스탬프나 블록 번호, msg.sender 같은 온체인 상태에는 의존할 수 있습니다(MAY). 잔액 검사나 수량 제한 같은 전송별·수량적 규칙을 여기에 넣으면 안 되며 그런 규칙은 canTransfer에 속합니다. 보내기와 받기를 나눈 덕분에 받기는 막되 보내기는 허용하는 한 방향 제한이 가능합니다. KYC 만료, AML 경보, 관할권 제약처럼 규제 환경에서 흔한 상황을 표현할 수 있습니다.
canTransfer는 전송 단위 허가 검사
canTransfer는 특정 전송이 허가형 준수 규칙상 가능한지 평가합니다. revert하면 안 되고 저장소를 바꾸면 안 됩니다. 전송량이 동결되지 않은 수량(현재 잔액에서 동결 수량을 뺀 값)을 넘지 않는지 반드시 검증해야 하고 from에 대한 canSend와 to에 대한 canReceive 검사를 반드시 수행해야 합니다. ERC-3643은 canTransfer 안에서 이 검사를 하지 않는다는 점이 명시된 차이입니다. 일시정지 장치나 권한 있는 주체가 관여하는 어떤 허가형 규칙이든 전송을 막을 상황이면 false를 반환해야 합니다. 반대로 잔액이나 승인(allowance) 검사처럼 기본 토큰 표준에 속하는 비허가형 검증을 이유로 false를 반환하면 안 됩니다. 그리고 transfer, transferFrom, safeTransferFrom 같은 공개 전송 함수는 canTransfer가 false를 반환할 상황, 또는 from의 canSend나 to의 canReceive가 false일 상황에서 성공하면 안 됩니다.
forcedTransfer의 동작 규칙
forcedTransfer는 잔액이나 소유권을 직접 조작해 자산을 from에서 to로 옮겨야 하며, 직접 전송하거나 from에서 소각하고 to로 발행하는 방식 모두 가능합니다. 접근이 제한돼야 하고 잔액 충분성 같은 검증을 수행해야 하며 사용한 방식에 맞는 기본 표준의 전송 이벤트와 함께 ForcedTransfer 이벤트를 반드시 발생시켜야 합니다. 단일 당사자 허가 환경에서는 canTransfer 검사를 건너뛸 수 있는데, 이때 동결로 집계된 토큰이 관련되면 먼저 동결을 해제하고 기본 전송 이벤트보다 앞서 Frozen 이벤트를 내야 합니다. ERC-721·ERC-1155처럼 수신자에게 외부 호출을 하는 토큰의 재진입 공격을 막는 데 이 순서가 중요합니다. 이 경우에도 to에 대한 canReceive 검사는 수행하는 것이 좋습니다(SHOULD). 다자 허가 환경에서는 canTransfer 검사를 수행하고 동결 제약을 우회하지 않는 것이 좋습니다. 검증이나 canReceive 검사가 실패하면 반드시 revert해야 합니다. 이름은 압류나 회수 같은 특정 동기 대신 행위 자체를 나타내는 중립적 표현으로 골랐고 forceTransfer가 아닌 forcedTransfer를 쓴 것은 ERC-3643과의 호환성 때문입니다.
setFrozenTokens는 절대값을 덮어씁니다
setFrozenTokens는 freeze와 unfreeze를 나누지 않고 ERC-20의 approve처럼 동결 수량을 새 값으로 덮어쓰는 단일 함수입니다. 표준을 간결하게 유지하려고 단일 함수와 단일 Frozen 이벤트를 택했습니다. 반드시 Frozen 이벤트를 내야 하고 접근이 제한돼야 하며 논리적 문제나 검증 실패 시 revert해야 합니다. 특히 현재 보유량보다 많은 수량의 동결을 반드시 허용해야 하는데, 앞으로 들어올 잔액까지 묶어 두기 위해서입니다. 절대값 덮어쓰기 방식이라 권한 계정이 여럿이면 경합이 생길 수 있어서 원문은 확장 예시로 기대값을 확인하는 setFrozenTokensIf와 증감분으로 조정하는 setFrozenTokensDelta를 보여 줍니다. 다만 이런 보조 함수도 같은 블록 안의 충돌이나 멤풀 순서 경합까지 막지는 못한다고 적고 있습니다. 법적 증빙을 첨부하는 setFrozenTokensWithProof나 ERC-6357과 유사한 multicall 패턴 같은 확장도 예시로 제시됩니다.
발행과 소각에도 규칙이 적용됩니다
공개 mint 함수 같은 비허가형 발행은 수신자의 canReceive가 false면 성공하면 안 됩니다. 권한 있는 역할이 수행하는 허가형 발행은 canReceive를 존중하는 것이 좋지만(SHOULD), 운영이나 준수상 필요하면 건너뛸 수 있습니다(MAY). 공개 burn 함수 같은 비허가형 소각은 canTransfer 검사와 보유자에 대한 canSend 검사를 반드시 지켜야 하고 동결되지 않은 수량을 넘는 소각을 허용하면 안 됩니다. 허가형 소각은 보유자의 canSend가 false여도 성공할 수 있고 동결 수량을 넘겨 소각할 수도 있는데, 그 경우 컨트랙트는 동결 상태를 반드시 갱신하고 기본 토큰의 전송 이벤트보다 먼저 Frozen 이벤트를 내야 합니다.
에러 체계는 구체성을 우선합니다
표준은 ERC7943CannotSend, ERC7943CannotReceive, ERC7943CannotTransfer, ERC7943InsufficientUnfrozenBalance 네 가지 에러를 정의합니다. 앞의 세 에러는 내부에서 canSend·canReceive·canTransfer가 false를 반환할 때 일반적인 revert 수단으로 쓸 수 있고(MAY), 수행한 검사에 따라 더 구체적인 에러로 대체하거나 아예 쓰지 않아도 됩니다. 전체 원칙은 에러 구체성 우선입니다. ERC7943InsufficientUnfrozenBalance는 전송량이 잔액 이하이지만 동결되지 않은 잔액보다 클 때, 또는 동결된 tokenId를 전송하려 할 때 쓰는 것이 좋습니다(SHOULD). 전송량이 잔액 전체를 넘거나 계정이 그 tokenId를 보유하지 않은 경우처럼 동결과 무관한 실패에는 기본 표준의 더 구체적인 에러를 쓰는 것이 좋습니다. 에러 이름은 ERC-6093 지침에 따라 지었습니다. 이 표준의 상태는 Final로 확정됐습니다.
쓸 때 주의할 점
- forcedTransfer·setFrozenTokens의 접근 제어: 두 함수에 대한 접근 제한 장치의 보안이 가장 중요합니다. 권한이 뚫리면 자산 탈취로 이어질 수 있습니다. 원문은 멀티시그나 타임락 같은 안전한 패턴을 강하게 권장합니다.
- 선행매매(front-running) 가능성: forcedTransfer와 setFrozenTokens 모두 ERC-20의 approve처럼 선행매매에 노출될 수 있습니다. 잔액보다 많이 동결하는 것을 허용하지 않는 구현이라면, 계정이 새 자금을 받을 때 자기 잔액 동결 시도를 앞질러 회피할 유인이 생깁니다. 동결 수량을 점진적으로 늘리거나 줄이는 기능을 추가로 고려할 수 있습니다.
- 재진입과 수신자 훅: 구현은 재진입 가드, 검사·효과·상호작용(checks-effects-interactions) 순서 같은 일반 보안 관행을 지켜야 합니다. 특히 ERC-721·ERC-1155처럼 수신자 쪽에 훅이 있는 토큰에서는 원치 않는 실행을 막기 위해 재진입 가드를 두는 것이 편리할 수 있습니다.