EIP-5792 지갑 호출 API (Wallet Call API)

최종 확정 · 계정 추상화 · 원문 상태 Final · 2022-10-17 작성

앱이 지갑에 여러 온체인 호출을 한 번에 묶어 보내고 그 묶음의 처리 상태를 확인하고 지갑이 지원하는 기능을 미리 조회할 수 있는 JSON-RPC 메서드 4개를 추가합니다.

왜 필요한가

지금 앱이 지갑에 트랜잭션을 보내는 방법은 eth_sendTransaction이고, 결과 확인은 eth_getTransactionReceipt입니다. 이 이름 자체가 노드가 지갑 역할을 하던 시절의 유산이며 현대 개발자의 요구를 채우지 못합니다. 오늘날 개발자는 여러 호출을 하나로 묶어 보내고 싶어 합니다. 많은 스마트 계정은 이를 한 트랜잭션 안에서 원자적으로(전부 성공하거나 전부 취소) 실행할 수 있습니다. ERC-4337의 페이마스터(수수료 대납 서비스)처럼 새 트랜잭션 형식이 주는 기능도 쓰고 싶어 합니다. 그러나 eth_sendTransaction에는 이런 것을 표현할 방법이 없습니다. 이 EIP는 wallet_ 이름공간의 새 메서드를 정의해 지갑과 앱의 역할을 깔끔하게 나누고 묶음 호출과 페이마스터 같은 기능을 쓸 수 있게 하며 앞으로 새 기능을 최소한의 조율로 안전하게 추가할 통로를 만듭니다.

무엇이 바뀌나

지금

EIP-5792 이후

누구에게 영향이 있나

심층 설명

wallet_sendCalls: 요청 형식과 지갑의 의무

요청은 version, 선택적 id와 from, 16진수 chainId(EIP-155, 0x 접두사, 앞자리 0 금지), atomicRequired, calls 배열로 구성됩니다. 각 call은 {to, data, value} 튜플이며 call별 capabilities를 붙일 수 있습니다. 지갑은 호출을 요청된 순서대로, 요청의 chainId가 가리키는 같은 체인에서, from 주소로부터 보내야 합니다(MUST). from이 없으면 확인 화면에서 사용자가 주소를 보고 선택할 기회를 줘야 합니다(SHOULD). 호출의 확정(finalization)을 기다렸다가 배치를 완료해서는 안 되고 사용자가 거부하면 어떤 호출도 보내면 안 됩니다. from이 활성 계정과 다르거나, 순차 시뮬레이션에서 일부 호출이 실패할 것으로 예상되면 요청을 거부할 수 있습니다(MAY).

atomicRequired와 atomic 능력

atomic 능력의 status 값은 supported, ready, unsupported 세 가지입니다. supported는 호출을 원자적이고 연속적으로 실행한다는 뜻입니다. ready는 사용자 승인을 거쳐 supported로 업그레이드할 수 있다는 뜻이고 unsupported는 어떤 원자성 보장도 없고 업그레이드 제안도 하지 않는다는 뜻입니다. 이 능력은 체인별로 표현되며 특정 체인에 atomic 능력이 없으면 그 체인에서 배칭을 지원하지 않는다는 의미입니다. atomicRequired가 true이면 지갑은 모든 호출을 원자적으로(전부 성공하거나 온체인에 실질 효과가 남지 않게), 그리고 다른 트랜잭션이 끼어들지 않게 연속으로 실행해야 합니다. ready 상태의 지갑은 제출 전에 반드시 업그레이드해야 합니다. false이면 순차 실행도 허용되고 가능하다면 원자 실행을 선택할 수도 있습니다.

capabilities와 optional 표시

capabilities 객체는 앱이 지갑의 기능을 요청하는 통로입니다. 예를 들어 ERC-4337 지갑이 paymasterAndData 입력을 받아올 페이마스터 서비스 URL을 여기에 넣습니다. 각 능력은 전역 필드와 call별 필드를 정의할 수 있습니다. 지갑은 자신이 지원하지 않는 능력이 요청에 있고 그 능력이 optional: true로 표시되지 않았다면 요청을 거부해야 합니다(MUST, 오류 5700). optional로 표시된 능력은 지원하지 않는 지갑도 능력이 없었던 것처럼 요청을 처리합니다. 구체적 능력의 정의는 이 EIP 범위 밖이며 별도 ERC로 정해집니다.

배치 식별자(id) 규칙

응답의 id는 묶음을 추적하는 열쇠입니다. 앱이 id를 제공하면 지갑은 이를 존중해 응답에 그대로 돌려줘야 합니다. 식별자는 최대 4096바이트(0x 포함 8194자)의 고유 문자열이어야 하고 앱 제공 id는 보내는 주소와 앱(도메인으로 식별) 단위로 고유해야 합니다. 중복 id 요청은 거부해야 하며(오류 5720) 지갑은 wallet_sendCalls 후 24시간 동안은 같은 id로 wallet_getCallsStatus가 호출되면 상태를 돌려줘야 합니다(SHOULD). 보안상 식별자는 예측 불가능해야 악의적 앱이 다른 사용자의 트랜잭션 정보를 유추하는 것을 막을 수 있습니다.

wallet_getCallsStatus: 상태 코드와 영수증

응답은 status 코드, atomic 필드, receipts 배열을 담습니다. 코드는 1xx 대기, 2xx 확정, 4xx 오프체인 실패, 5xx 체인 규칙 실패로 나뉩니다. 100은 접수됐지만 온체인 실행 미완료, 200은 리버트 없이 포함됨, 400은 포함되지 않았고 재시도도 없음, 500은 전체 리버트(가스 차감 관련 변경만 남을 수 있음), 600은 부분 리버트입니다. 원자적으로 실행했다면 atomic을 true로 명시해야 하고 receipts는 단일 영수증이거나 배열일 수 있습니다. 비원자 실행이면 atomic을 false로 하고 리버트된 것까지 포함해 온체인에 포함된 모든 트랜잭션의 영수증 배열을 돌려줘야 합니다. 영수증의 logs는 이 묶음과 관련된 것만 담아야 하며 같은 번들의 다른 유저 오퍼레이션이나 지갑 업그레이드 과정에서 나온 로그를 포함하면 안 됩니다.

wallet_showCallsStatus와 wallet_getCapabilities

wallet_showCallsStatus는 지갑 UI에 해당 묶음의 정보를 띄우라는 요청으로, 아는 id면 아무 값도 반환하지 않고 모르는 id나 실패 시 RPC 오류를 돌려줍니다. wallet_getCapabilities는 능력을 체인 id별 객체로 반환합니다. 모든 체인에서 지원하는 능력은 특수 chainId 값 "0x0"에 한 번만 넣고 체인별 객체에 반복하지 않아야 합니다(SHOULD). 지원하지 않는 체인은 오류 대신 응답에서 키를 빼야 합니다(MUST). 앱과 주소가 아직 연결 승인되지 않았으면 4100 Unauthorized를 돌려줘야 합니다(SHOULD). 같은 능력 정보를 CAIP-25의 sessionProperties나 EIP-6963 rdns에서 파생한 위치 등 대역 외로도 노출할 수 있으나, 실시간 RPC 응답과 모순되면 후자가 우선합니다.

오류 코드와 설계 배경

정의된 오류는 -32602(요청 파싱 불가), 4001(사용자 거부), 4100(미연결 주소), 5700(비선택 능력 미지원), 5710(미지원 체인), 5720(중복 id), 5730(알 수 없는 묶음 id), 5740(묶음이 너무 큼), 5750(업그레이드로 원자성 지원 가능하나 사용자가 업그레이드 거부), 5760(원자 실행 요구를 지갑이 미지원)입니다. 이름은 eth_sendTransaction 수정 대신 wallet_ 이름공간을 택했는데, EOA 지갑은 한 요청이 여러 트랜잭션이 될 수 있고 ERC-4337 지갑은 여러 호출이 한 트랜잭션이 될 수 있어 wallet_sendCalls로 정했습니다. 처음에는 원자 실행을 의무화하려 했으나 지나치게 단정적이라 판단해 atomic 능력으로 분리했고 call별 gas 필드도 ERC-4337과 EOA 양쪽을 만족시킬 수 없어 제거했습니다.

프라이버시 고려와 현재 상태

능력 조회 설계는 기능 미지원과 권한 없음을 의도적으로 구분하지 못하게 해 점진적 권한 부여 패턴을 보호합니다. 지갑이 능력을 신뢰할 수 없는 호출자에게 노출하면 클라이언트 지문 채취(fingerprinting)로 사용자 비식별성이 깨질 수 있어 필요 이상으로 노출하지 않기를 권고합니다. 앱이 능력을 과도하게 조회하거나 과공유를 유도하는 것도 피해야 할 안티패턴입니다. 이 EIP는 Final 상태의 인터페이스 표준으로 프로토콜 포크 없이 지갑과 앱이 채택하는 방식이며 08.29 AMA #14에서 논의되었고 결정은 없었습니다.

EIP 원문 · ← EIPs