PoCs

ERC-8141

네이티브 계정 추상화 설명 페이지 — 이더리움 프로토콜 레벨 Frame Transactions.

어떻게 보나

읽기 전용 설명 페이지 — 지갑 불필요.

기술 노트

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

ERC-8141완료

목적: 이 프로젝트의 애플리케이션 레벨 AA 데모(세션 키, 원자적 배치)가 스마트 컨트랙트와 위임으로 지금 하고 있는 일을, 프로토콜 네이티브 수준에서 미리 보여줍니다 — EIP-8141은 같은 속성을 이더리움의 기본 트랜잭션 포맷 자체로 옮기자는 제안입니다.

동작 방식: 구조상 실제 동작하는 데모가 될 수 없습니다: EIP-8141은 트랜잭션 하나가 암묵적 호출 한 번이 아니라 프레임의 시퀀스(서명·수수료 인가를 담당하는 VERIFY 프레임, 이어지는 하나 이상의 EXECUTE 프레임)를 담는 새 트랜잭션 타입을 정의하지만, 아직 어떤 클라이언트나 RPC도 이 타입을 보낼 수 없습니다 — 네트워크에 없는 실행 계층 지원이 필요하기 때문입니다. 이 글을 쓰는 시점 기준 향후 포크에 "포함 검토 중"인 단계라, 라이브 데모가 아니라 다이어그램·설명 페이지로 남습니다. ### ACDE 리드아웃, 그리고 두 종류의 "아니오" (2026-08-28) 이 제안과 EIP-8130 사이의 결론이 예정돼 있었고, 이 글을 쓰는 시점까지 **공개된 리드아웃은 확인되지 않습니다.** 그 공백에서 개발자 타임라인이 향한 곳이 쓸모 있는 부분입니다 — **논쟁이 내용이 아니라 무게로 옮겨갔습니다.** 반복된 문장은 *"방향은 맞는데 지금 단계에서 너무 무겁다"* 였고, 이건 **기술 반대가 아니라 일정 반대**입니다. 이 구분은 붙들 값이 있습니다 — **밖에서 보면 똑같이 생겼는데 요구하는 대응이 정반대**이기 때문입니다: | 반대 | 실제로 하는 말 | 다음에 할 일 | | --- | --- | --- | | **"이건 틀렸다"** | 설계가 목적을 달성하지 못한다 | **설계를 바꾼다** | | **"이건 지금이 아니다"** | 설계는 맞고, 여기 넣기엔 크다 | **범위를 쪼갠다** | **두 번째를 듣고 재설계하면 헛일**이고, **첫 번째를 듣고 미루기만 하면 더 나쁩니다.** **회의실에서 물어야 할 것은 지금 받은 게 어느 쪽인가**이고, 판별법은 대개 **제안을 작게 만들었을 때도 그 반대가 살아남는가**입니다.

검토 후 보완: ### 기본 개념, 처음부터 이더리움에는 계정이 두 종류인데, 행동할 수 있는 쪽이 멍청합니다. **EOA** 는 트랜잭션을 시작할 수 있지만 규칙이 프로토콜에 얼어붙어 있습니다: ECDSA 키 하나, 순차 논스 하나, 수수료는 본인이 ETH 로, 트랜잭션당 호출 하나 — 키를 잃으면 계정을 잃습니다. **컨트랙트 계정**은 완전히 프로그래머블하지만 아무것도 시작하지 못합니다. **계정 추상화(AA)** 는 이 분리를 지우는 프로젝트입니다: 행동하는 계정이 프로그래머블하게 — 누가 서명하는지, 수수료를 어떻게 내는지, 트랜잭션 하나에 무엇이 담기는지를. 오늘의 AA 는 한 층 위에 지은 에뮬레이션입니다: ERC-4337 은 컨트랙트로 트랜잭션 시스템을 재건축했고(UserOperation, 번들러, EntryPoint), EIP-7702 는 EOA 가 컨트랙트 코드를 빌리게 합니다. 작동하지만 — 추가 행위자, 추가 가스, 추가 신뢰 표면이 대가입니다. EIP-8141 은 에뮬레이션을 끝내자는 제안입니다: **프레임**으로 이루어진 새 베이스 트랜잭션 타입 — **VERIFY** 프레임 하나("인가 증명은 이것, 수수료는 이렇게") 뒤에 **EXECUTE** 프레임 하나 이상(호출들, 원자적으로: 전부 아니면 전무). **트랜잭션이 늘 갖고 있던 두 가지 일 — 허용됐음을 증명하기, 그다음 실행하기 — 이 포맷 자체의 명시적이고 분리된, 프로그래밍 가능한 부품이 됩니다.** ### 같은 동작, 코드 세 세대 오늘 노드의 검사는 하드코딩이라 바꿀 수 없습니다: ```js assert(ecrecover(tx.sig) == tx.from); // ECDSA 키 정확히 하나 assert(tx.nonce == account.nonce); // 논스 정확히 하나 assert(balance[tx.from] >= gas * price); // 본인이, ETH 로만 call(tx.to, tx.data); // 호출 정확히 하나 ``` ERC-4337 은 이 검사를 컨트랙트 안에서 재연합니다 — `EntryPoint.handleOps` 가 UserOperation 들을 돌며 `account.validateUserOp` 그리고 `account.execute` 를 호출하고, 번들러가 배치를 나르고 페이마스터가 선택적으로 지불합니다. 홉 하나하나가 에뮬레이션 세금입니다. EIP-8141 에서는 EntryPoint 가 흉내 내던 루프를 노드 자신이 돕니다: ```js tx = { type: FRAME_TX, frames: [ { kind: VERIFY, target: 내계정, input: { scheme: "p256-passkey", proof, feeToken: USDC } }, { kind: EXECUTE, target: USDC, data: approve(dex, 100e6) }, { kind: EXECUTE, target: dex, data: swap(USDC, ETH, 100e6) }, ]} ``` 기능이 사는 곳은 계정 자신의 verify 코드입니다 — 네이티브 세션 키: ```solidity function verify(Proof p, Frame[] fs) external view returns (bool) { if (p.signer == owner) return checkSig(p); Session s = sessions[p.signer]; // 임시 키 require(block.timestamp < s.expiry); // …만료되고 require(allCallsTo(fs, s.allowedContract)); // …이 앱만 require(totalValue(fs) <= s.perTxLimit); // …소액만 return checkSig(p); } ``` ### 누가 행복해지나 | 누가 | 오늘의 고통 | 프레임 트랜잭션에서는 | | --- | --- | --- | | 사용자 | 시드 문구 아니면 끝; 승인→스왑이 열린 승인을 남김; 먼저 ETH 필요 | 패스키가 네이티브 키; 승인+스왑 원자적; 수수료는 토큰이나 스폰서가 | | 지갑 개발자 | 번들러+페이마스터 인프라 운영/임대 | 그 서비스들이 대부분 사라짐; 체인이 직접 검증 | | dApp/게임/에이전트 개발자 | 세션 키에 제공자별 스마트 지갑 스택 | 유계 위임이 베이스 레이어 기능 | | 프로토콜 | 병렬 4337 멤풀; 공유 관문 EntryPoint | 정본 멤풀 하나, 신뢰할 단일 컨트랙트 없음 | 구체적으로 개선되는 상황: 열린 승인 사고(EXECUTE 프레임 둘이면 스왑 없는 승인이 존재 불가능); ETH 없는 온보딩(VERIFY 안에서 스폰서가 지불); 키 분실 복구가 수탁자의 기능이 아니라 계정 로직이 되는 것; 포스트퀀텀 마이그레이션(서명 스킴이 포크할 프로토콜 상수가 아니라 업그레이드할 계정 코드가 됨); 에이전트 커머스(만료·허용 컨트랙트·건당 한도가 붙은 세션 키를 쥔 AI 에이전트 = 내가 고른 폭발 반경을 가진 위임). 정직한 비용 — ACDE 가 "너무 무겁다"고 한 이유의 대부분: VERIFY 프레임은 *아무도 돈을 내기 전에* 멤풀 유효성을 결정하는 임의 코드라서, 엄격한 가스 상한과 값싼 재검사가 없으면 무효 트랜잭션이 공짜 DoS 벡터가 됩니다. ### "그럼 ERC-4337 은 일몰될 수 있겠네?" (jay, 2026-09-03) 방향은 맞고 시제가 틀렸습니다. 4337 은 처음부터 임시방편이었고 — 인슈라인먼트가 공공연한 최종 목표였으며, 8141 의 VERIFY/EXECUTE 분리는 본질적으로 `validateUserOp`/`execute` 의 포맷 승격입니다. 그러니 4337 은 죽는 게 아니라 **흡수됩니다** — 컨트랙트는 비어 가도 개념은 살아남습니다. 하지만 일몰은 10년 단위의 감쇠입니다. 이유 넷: 이더리움에서는 아무것도 제거되지 않고(EntryPoint 는 불멸, 배포됐거나 counterfactual 인 수백만 스마트 계정 주소는 스스로 이주하지 않음); 네이티브 AA 경쟁이 미결이며(8141 대 8130 대 Tempo — 사용자를 가진 임시방편이 포크 날짜 없는 제안을 이김); L2 가 대칭을 깨고(4337 은 오늘 모든 EVM 체인에서 동일하게 작동하지만 네이티브 AA 는 체인마다 따로 — 크로스체인 지갑이 마지막 요새); 번들러/페이마스터 벤더들이 옛 레일을 지키는 대신 새 레일로 피벗해 자기를 낡게 만드는 일몰을 가속할 것이기 때문입니다. 전략으로서 — 4337 배관을 해자로 삼지 마라 — 는 이미 참입니다. 예보로서 — 트래픽 0 — 는 10년과 별표 하나를 붙이십시오. ### 진행 상황 (2026-09-08) 비탈릭의 9/6 Frames 업데이트가 진전을 알렸습니다: 8/27 콜에서 제안이 CFI → SFI(포함 검토 → 포함 예정)로 승격, FOCIL 과 나란히 Hegotá(2027 Q2 목표)에 배정됐습니다. SFI 는 배정이지 완료가 아닙니다 — 9/10 데브넷 선호목록 이후 볼 판별점은 **8141 에 데브넷이 실제로 붙는가**입니다. 프레이밍 둘. 헤드라인 '스테이블코인으로 가스 지불'은 설계가 아니라 *결과*입니다: 설계는 오늘 한 서명자에 묶인 세 역할 — 인가·가스 지불·실행 — 을 분리하는 것이고, 스테이블코인 수수료는 가스 지불이 VERIFY 안의 독립된 가닥이 되면 따라 나옵니다. 그리고 경쟁은 타이밍이 비대칭입니다: 경쟁안 EIP-8130 은 9월 중 Base 배포를 목표로 해서, L2 구현이 L1 표준보다 먼저 실물이 됩니다 — 사용자를 가진 임시방편이 또 한 번 포크 날짜 있는 제안을 앞지릅니다. 불변식 카드가 말하는 런타임 대 명세 논점이 여기 반대에도 적용됩니다: `liquid-issuance-not-authorization` 가, 인슈라인된 VERIFY 프레임이 왜 엄격히 가스 상한이 걸리고 값싸게 재검사돼야 하는지의 이유입니다 — 아무도 돈 내기 전에 도는 임의 유효성 코드는, 네트워크가 무조건 믿어 줄 수 없는 전제입니다.

관련 코드:
# ERC-8141 — a transaction carries a sequence of frames instead of one implicit call:
# a VERIFY frame (signature/fee authorization) followed by one or more EXECUTE frames.
# Simulates the frame structure and runs it in sequence, aborting if VERIFY fails.

from dataclasses import dataclass, field
from typing import Callable


@dataclass
class Frame:
    kind: str  # "VERIFY" or "EXECUTE"
    description: str
    run: Callable[[dict], bool]


@dataclass
class FrameTransaction:
    frames: list[Frame] = field(default_factory=list)

    def execute(self, context: dict) -> bool:
        for frame in self.frames:
            print(f"  [{frame.kind}] {frame.description} ...", end=" ")
            ok = frame.run(context)
            print("OK" if ok else "FAILED")
            if frame.kind == "VERIFY" and not ok:
                print("  -> VERIFY frame failed: transaction aborted before any EXECUTE frame runs.")
                return False
            if frame.kind == "EXECUTE" and not ok:
                print("  -> EXECUTE frame failed: transaction reverts.")
                return False
        return True


def verify_signature_and_fee(ctx: dict) -> bool:
    return ctx.get("signature_valid", False) and ctx.get("balance", 0) >= ctx.get("max_fee", 0)


def execute_transfer(ctx: dict) -> bool:
    ctx["balance"] -= ctx["transfer_amount"]
    return ctx["balance"] >= 0


def execute_approve(ctx: dict) -> bool:
    ctx["allowance"] = ctx.get("allowance", 0) + ctx["approve_amount"]
    return True


def build_transaction() -> FrameTransaction:
    return FrameTransaction(frames=[
        Frame("VERIFY", "signature + fee authorization", verify_signature_and_fee),
        Frame("EXECUTE", "transfer 50 tokens", execute_transfer),
        Frame("EXECUTE", "approve spender for 20 tokens", execute_approve),
    ])


if __name__ == "__main__":
    print("EIP-8141 Frame Transaction — VERIFY then EXECUTE, EXECUTE, ...\n")

    print("Case 1: valid signature, sufficient balance")
    ctx = {"signature_valid": True, "balance": 100, "max_fee": 5, "transfer_amount": 50, "approve_amount": 20}
    ok = build_transaction().execute(ctx)
    print(f"Transaction succeeded: {ok}, final state: {ctx}\n")

    print("Case 2: invalid signature — VERIFY frame blocks all EXECUTE frames")
    ctx = {"signature_valid": False, "balance": 100, "max_fee": 5, "transfer_amount": 50, "approve_amount": 20}
    ok = build_transaction().execute(ctx)
    print(f"Transaction succeeded: {ok}")

docs/code/pocs/erc-8141.py