Back
EIP 6551 톺아보기
🔍

EIP 6551 톺아보기

Date
Dec 18, 2025
Published
Published
 

1. 개요

1-1. 기존의 한계

현재 ERC-721 NFT는 소유권 증명에 머무르는 경우가 대부분이라, 스스로 에이전트처럼 행동하거나 다른 온체인 자산을 직접 소유하지는 않는다.
  • 예: RPG 게임 캐릭터를 NFT로 발행하더라도, 캐릭터가 획득한 아이템은 캐릭터 NFT 안에 들어가는 것이 아니라 여전히 유저의 지갑에 별도로 존재한다.
  • 이 제안의 목표는 온체인 상에서 "캐릭터가 칼을 쥐고 있는 상태", "자동차가 바퀴와 엔진을 가지고 있는 상태" 같은 구성을 자연스럽게 표현할 수 있게 만드는 데 있다.

1.2. 해결책: Token Bound Account, TBA)

이 표준은 ERC-721 토큰마다 고유하고 결정론적인(Deterministic) 스마트 컨트랙트 계정 주소를 하나씩 붙인다.
  • 이 계정은 해당 NFT에 영구적으로 묶여 있다.
  • 계정의 제어권(Control) 은 항상 NFT의 현재 소유자(Holder)가 가진다.
  • 기존 ERC-721 컨트랙트 코드를 따로 수정할 필요는 없다.

2. 아키텍처 및 레지스트리 (Architecture & Registry)

시스템은 크게 두 부분으로 나뉜다.
  • 싱글톤 레지스트리(Registry)
  • 계정 구현체(Account Implementation)

2.1. 싱글톤 레지스트리 (Singleton Registry)

  • 역할: 모든 TBA 주소를 계산하고 생성하는 단일 진입점이다.
  • 특징:
    • 무허가형(Permissionless): 소유자가 없고, 누구나 사용할 수 있다.
    • 불변성(Immutable): 한 번 배포된 뒤에는 코드가 바뀌지 않는다.
    • 고정 주소: 모든 EVM 호환 체인에서 아래 주소를 사용하는 것을 목표로 한다.
      • 0x000000006551c19487814612e58FE06813775758
  • 주소 생성 원리 (CREATE2):
    • TBA 주소는 아래 값들을 조합해서 만들어진다.
    • implementation (계정 구현 컨트랙트 주소)
    • chainId
    • tokenContract (NFT 컨트랙트 주소)
    • tokenId
    • salt (구분자)
    • 이 방식을 쓰면 실제로 계정을 체인에 배포하기 전에도 미리 주소를 알 수 있는 카운터팩추얼(Counterfactual) 계정을 만들 수 있다.

2.2. 프록시 구조 (ERC-1167 Minimal Proxy)

  • 레지스트리는 TBA를 ERC-1167 Minimal Proxy 형태로 배포한다.
  • 이때 프록시 바이트코드 뒤에 다음과 같은 불변 데이터를 붙인다.
    • salt, chainId, tokenContract, tokenId
  • 이렇게 구성해 두면 TBA 컨트랙트는 자신의 코드 끝부분을 읽어 "어떤 NFT에 묶여 있는 계정인지" 스스로 알아낼 수 있다.

2.3. 레지스트리 인터페이스

  • createAccount
    • TBA를 실제로 생성(배포)하는 함수다.
    • 이미 같은 조합으로 만들어진 계정이 있다면 새로 배포하지 않고 그 주소만 돌려준다.
  • account
    • 계정을 생성하지 않고, 주어진 파라미터에 해당하는 계산된 주소만 돌려주는 view 함수다.

3. 계정 인터페이스 (Account Interface)

생성된 TBA는 반드시 IERC6551Account 인터페이스를 구현해야 한다.

3.1. 필수 기능

  • receive()
    • 계정이 ETH를 직접 받을 수 있어야 한다.
  • token()
    • 이 계정이 어떤 NFT에 묶여 있는지 나타내는 정보를 반환한다.
      • chainId, tokenContract, tokenId
    • 이 값은 계정이 만들어진 이후에는 변하지 않는다.
  • state()
    • 계정의 상태 값(예: 트랜잭션 nonce 등)을 반환한다.
    • 계정 상태가 바뀔 때마다 이 값도 함께 달라져야 한다.
  • isValidSigner(signer, context)
    • 특정 주소(signer)가 이 계정을 제어할 권한이 있는지 확인하는 함수다.
    • 기본적으로 NFT의 현재 소유자를 유효한 서명자로 본다.

3.2. 서명 검증 (ERC-1271)

  • TBA는 스마트 컨트랙트 계정이라 자체적인 개인키를 가지지 않는다.
  • 대신 isValidSignature를 구현해서 NFT 소유자가 서명한 메시지를 유효하다고 인정하는 방식을 쓴다.
  • 이렇게 하면 TBA를 DApp 로그인이나 메시지 서명의 주체로 활용할 수 있다.

4. 실행 인터페이스 (Execution Interface)

TBA가 외부로 트랜잭션을 날릴 때 사용하는 핵심 인터페이스가 IERC6551Executable이다.

4.1. execute 함수

function execute( address to, uint256 value, bytes calldata data, uint8 operation ) external payable returns (bytes memory);

4.2. 파라미터 및 operation 상세

  • to: 호출 대상 주소
  • value: 전송할 ETH 양
  • data: 실행할 데이터(함수 호출 인코딩 등)
  • operation: 실행 방식(opcode)
    • 0 (CALL): 일반적인 컨트랙트 호출 및 ETH 전송에 쓰는 가장 기본 모드다.
    • 1 (DELEGATECALL): 외부 코드를 TBA의 스토리지 컨텍스트에서 실행한다. 계정 기능을 확장할 때 활용할 수 있다.
    • 2 (CREATE): 새로운 컨트랙트를 배포한다.
    • 3 (CREATE2): 결정론적인 주소로 컨트랙트를 배포한다.

5. 설계 근거 (Rationale)

이 표준이 이런 구조를 택한 이유는 크게 다음과 같다.
  • 싱글톤 레지스트리
    • 여러 체인에서 동일한 주소 체계를 유지하기 쉽다.
    • 인덱싱이나 탐색 로직을 단순하게 가져갈 수 있다.
  • 카운터팩추얼(Counterfactual) 계정
    • 계정을 실제로 배포하기 전에 자산을 먼저 입금받는 패턴을 지원한다.
    • 초기에 불필요하게 드는 가스비를 줄일 수 있다.
  • Factory 대신 Registry라는 이름
    • 한 번 계정을 만드는 행위보다, 그 이후에 반복적으로 주소를 조회하는 행위가 더 핵심이라는 점을 드러내기 위한 네이밍이다.
  • 다중 계정 허용 (Account Ambiguity)
    • 하나의 NFT가 여러 개의 TBA를 가질 수 있다.
    • salt 값을 다르게 두어 "저축용", "지출용"처럼 계정을 용도별로 분리하는 패턴을 지원한다.

6. 보안 고려사항 (Security Considerations)

6.1. 사기 방지 (Fraud Prevention)

악의적인 판매자가 NFT에 묶인 자산을 빼돌릴 수 있는 전형적인 시나리오는 다음과 같다.
  1. 판매자 Alice가 TBA에 10 ETH가 들어 있는 NFT를 마켓플레이스에 올린다.
  1. 구매자 Bob이 이를 보고 구매 트랜잭션을 전송한다.
  1. 트랜잭션이 처리되기 직전, Alice가 TBA에서 10 ETH를 먼저 인출한다(Front-running).
  1. 결국 Bob은 속이 빈 계정이 묶인 NFT를 받게 된다.
  • 대응:
    • 마켓플레이스 측에서 거래가 체결되는 시점에 TBA의 잔고상태(state) 를 함께 검증하는 절차를 두는 식으로 막을 수 있다.

6.2. 소유권 순환 (Ownership Cycles)

  • NFT A의 TBA 안으로 다시 NFT A를 전송하면, 말 그대로 자기 자신을 소유하는 루프가 생긴다.
  • 이 경우 TBA를 제어할 수 있는 외부 주체가 사라지기 때문에, NFT와 그 안에 들어 있는 모든 자산이 영구적으로 잠길 수 있다.
  • 애플리케이션 레벨에서는 이런 전송을 애초에 불가능하게 막는 로직을 두는 편이 안전하다.

7. 구조

  • Registry 구현체
    • CREATE2를 사용해서 프록시를 배포한다.
    • 어셈블리(Assembly) 코드를 이용해 프록시 바이트코드 뒤에 불변 데이터를 붙이는 로직을 포함한다.
  • Account 구현체
    • execute 함수 내부에서 _isValidSigner(msg.sender)를 호출해 권한을 먼저 확인한다.
    • 이후 operation 값에 따라 call, delegatecall 등을 실행한다.
 
 
 
6551에 토큰 담아주기
 
담긴 토큰을 옮기기