데모

EIP-7702 — 주소를 바꾸지 않는 스마트 계정

EOA는 서명만, 컨트랙트는 실행만 — 이더리움의 오래된 이분법을 EIP-7702가 깹니다. 내 주소가 코드를 가리키게 만들어, 주소·잔액·이력을 그대로 둔 채 스마트 계정 기능을 얻습니다. 아래 인스펙터는 읽기 전용이라 가스도 지갑도 필요 없습니다.

↓ 기술 노트 보기 (목적·동작 방식)

Loading…

이 스펙으로 만들 수 있는 것들

"내 EOA가 아무 코드나 가리키게 한다"는 한 줄이 전부라, 응용은 구현체를 무엇으로 두느냐에 달려 있습니다.

배치 실행

approve + swap을 서명 한 번으로 원자적 트랜잭션 하나에 — 현재 가장 흔한 실사용 사례입니다.

가스 대납

EOA가 ERC-4337 계정이 되어 paymaster가 가스를 대신 냅니다 — 새 지갑 없이, 같은 주소로.

세션 키 / 위임

다른 키에 한도와 만료가 걸린 권한을 부여합니다 — /live/aa가 시연하는 것이 이것입니다.

패스키 서명자

시드 구문 대신 Face ID / WebAuthn으로 서명하면서, 이미 쓰던 주소를 그대로 유지합니다.

소셜 리커버리

자금을 옮기지 않고, 이미 자금이 든 주소에 복구 보호자를 추가합니다.

지출 정책

일일 한도나 허용 컨트랙트 목록을, UI가 아니라 계정 스스로 강제합니다.

⚠️ 트레이드오프

힘이 큰 만큼 위험도 큽니다. 7702 인가에 서명하는 것은 지정한 컨트랙트에 계정의 완전한 통제권을 넘기는 일이고, 해제하기 전까지 지속됩니다. 더 미묘한 위험도 있습니다 — 구현체를 바꿔도 이전 구현체가 쓴 스토리지는 남기 때문에, 잘못 설계된 구현체는 기존 데이터와 충돌할 수 있습니다.

기술 노트

각 항목이 실제로 무엇을 보여주고 어떻게 동작하는지 — 위 카드보다 자세한 기술 설명입니다.

EIP-7702 — 주소 그대로 스마트 계정라이브

목적: EIP-7702는 이곳의 다른 모든 계정 추상화 데모의 전제조건이지만 눈에 보이지 않습니다 — 지갑이 한 번 조용히 처리할 뿐, 화면에는 아무 흔적도 남지 않습니다. 이 페이지는 그 보이지 않는 단계를 관찰 가능하게 만들고, 사람들이 흔히 뒤섞는 세 표준을 분리합니다(7702는 능력을 부여하고, 7715는 권한을 요청하고, 7710은 그것을 강제합니다).

동작 방식: EIP-7702 인가는 계정의 코드 슬롯에 23바이트짜리 위임 지정자를 씁니다 — 3바이트 마커 0xef0100 뒤에 20바이트 구현체 주소가 붙습니다. 계정의 주소·잔액·nonce·이력은 그대로이고 코드 슬롯만 바뀌며, 0 주소를 가리키게 하면 되돌릴 수 있습니다. 인스펙터는 공개 Sepolia RPC로 eth_getCode를 호출하고 결과에 따라 갈라집니다: 비어 있으면 서명은 하지만 실행은 못 하는 평범한 EOA, 0xef0100으로 시작하면 위임된 계정이며 나머지 20바이트를 구현체 주소로 디코딩해 링크합니다. 그 외에는 일반 배포 컨트랙트로 보고 바이트코드 길이를 함께 표시합니다. eth_getCode는 읽기 호출이므로 지갑도 서명도 가스도 필요 없고, 남의 주소를 포함해 어떤 주소든 확인할 수 있습니다.

업그레이드, 그리고 이 페이지가 그것을 관찰하는 법
관련 코드:
"""EIP-7702 PoC -- delegation designator at a fixed address.
Illustrates the core mechanism: an account keeps its address; only its "code slot"
changes to point at an implementation. Calls resolve through that pointer.
"""

DELEGATION_MARKER = "0xef0100"


class Implementation:
    def __init__(self, name: str):
        self.name = name

    def execute(self, account: str, calldata: str) -> str:
        return f"[{self.name}] running as {account}: {calldata}"


# The chain's global code-slot mapping: address -> designator (or None = plain EOA).
code_slots: dict[str, str | None] = {}
implementations = {
    "0xDeleGator...": Implementation("DeleGatorV1"),
    "0xZeroAddr...": None,  # pointing back here undoes the delegation
}


def get_code(address: str) -> str | None:
    return code_slots.get(address)


def authorize(address: str, implementation_address: str) -> None:
    """A type-4 tx: write the 23-byte designator (marker + impl address) into the code slot."""
    code_slots[address] = f"{DELEGATION_MARKER}{implementation_address}"


def call(address: str, calldata: str) -> str:
    designator = get_code(address)
    if designator is None:
        return f"{address} is a plain EOA: can sign, cannot execute calldata"
    if not designator.startswith(DELEGATION_MARKER):
        return f"{address} is an ordinary deployed contract"
    impl_address = designator[len(DELEGATION_MARKER):]
    impl = implementations[impl_address]
    if impl is None:  # pointed at the zero address -> delegation revoked
        return f"{address} delegation revoked: behaves like a plain EOA again"
    return impl.execute(address, calldata)


if __name__ == "__main__":
    owner = "0xOwnerEOA..."
    print(f"before authorization: {call(owner, 'transfer(USDC, 10)')}")

    authorize(owner, "0xDeleGator...")
    print(f"eth_getCode({owner}) -> {get_code(owner)}")
    print(f"after authorization: {call(owner, 'transfer(USDC, 10)')}")

    print("\nowner, balance, nonce untouched -- only the code slot changed")
    print("revoking by pointing the designator back at the zero address:")
    code_slots[owner] = f"{DELEGATION_MARKER}0xZeroAddr..."
    print(f"after revoking: {call(owner, 'transfer(USDC, 10)')}")

docs/code/pocs/erc-7702.py