기록

MetaMask 지갑 연결과 권한 요청

TL;DR

  • eth_accounts는 이미 허용된 계정을 읽는 호출이다.
  • eth_requestAccounts는 사이트에 계정 접근 권한을 요청하는 호출이다.
  • ethers v5의 signer.getAddress()는 권한 요청 UI를 띄우지 않는다.
  • 지갑 연결 상태는 provider, permission, chain, address, signature, token으로 나눠 관리해야 한다.

MetaMask 로그인 구현에서 eth_requestAccountsetherssigner.getAddress()로 바꾸는 수정이 있었다. 두 코드 모두 지갑 주소를 얻는 것처럼 보이지만, 실제 RPC 메서드는 다르다.

const accounts = await window.ethereum.request({
  method: "eth_requestAccounts",
});
setAddress(accounts[0]);
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const address = await signer.getAddress();
setAddress(address);

두 번째 코드는 권한 요청을 포함하지 않는다. 이미 권한을 준 브라우저에서는 정상 동작하지만, 처음 방문한 브라우저에서는 주소를 얻지 못할 수 있다.

주소 읽기와 권한 요청

ethers v5의 JsonRpcSigner.getAddress()는 내부적으로 eth_accounts를 호출한다.

return this.provider.send("eth_accounts", []).then((accounts) => {
  if (accounts.length <= this._index) {
    logger.throwError("unknown account #" + this._index, ...);
  }
  return this.provider.formatter.address(accounts[this._index]);
});

eth_accounts는 계정 접근 권한을 새로 요청하지 않는다. MetaMask에서 해당 사이트에 권한이 없으면 빈 배열을 반환할 수 있다. 이 경우 ethersunknown account #0 에러를 던진다. 권한 요청은 eth_requestAccounts의 역할이다. 이 호출은 사용자가 계정 접근을 승인하거나 거절할 수 있는 MetaMask UI를 띄운다.

구분 eth_accounts eth_requestAccounts
역할 허용된 계정 조회 계정 접근 권한 요청
권한 없음 빈 배열 가능 승인 UI 표시
사용자 팝업 없음 있음
ethers v5 연결 signer.getAddress() provider.send("eth_requestAccounts", [])

기존에 사용하던 window.ethereum.enable()도 같은 계열이다. EIP-1102 기준으로는 deprecated된 이름이고, 현재는 eth_requestAccounts를 쓰는 편이 명확하다.

체인 전환은 별도 상태

서비스가 Polygon 네트워크를 전제로 동작하면 지갑 연결 전에 chain도 확인해야 한다.

await window.ethereum.request({
  method: "wallet_switchEthereumChain",
  params: [{ chainId: CHAIN_ID_HEX }],
});

사용자의 MetaMask에 Polygon이 등록되어 있지 않으면 MetaMask는 4902 에러를 반환할 수 있다. 이 경우 wallet_addEthereumChain으로 네트워크 추가를 요청한다.

if (error.code === 4902) {
  await window.ethereum.request({
    method: "wallet_addEthereumChain",
    params: [{ chainId: CHAIN_ID_HEX, chainName, nativeCurrency, rpcUrls }],
  });
}

체인 전환과 계정 권한은 같은 상태가 아니다. 네트워크 전환 UI가 떠도 계정 접근 권한이 있다는 뜻은 아니다. 따라서 체인 전환 성공을 지갑 연결 성공으로 처리하면 안 된다.

로그인 상태 분리

MetaMask 로그인은 하나의 boolean으로 표현하기 어렵다. 버튼 하나 뒤에 서로 다른 실패 조건이 있다.

상태 확인 대상 실패 처리
provider window.ethereum 존재 여부 MetaMask 설치 안내
permission 계정 접근 권한 eth_requestAccounts 호출
chain 요구 네트워크 연결 여부 wallet_switchEthereumChain 또는 wallet_addEthereumChain
address 계정 주소 조회 서버 요청 중단
registration 서비스 가입 여부 signup/login 분기
signature challenge 서명 검증 주소 연결 상태 초기화
token 서비스 access token 로그인 완료

권한을 한 번 승인한 개발 브라우저에서는 eth_accounts만으로도 주소가 반환된다. 이 상태에서 구현하면 처음 방문한 사용자의 권한 요청 경로가 빠진 것을 놓치기 쉽다.

정리

signer.getAddress()는 지갑 주소를 읽는 호출이지 지갑을 여는 호출이 아니다. MetaMask 연결 흐름에서 먼저 필요한 것은 계정 접근 권한 요청이고, 그 다음이 주소 조회다. 따라서 eth_requestAccountssigner.getAddress()로 대체하면 안 된다. 두 호출은 같은 값을 반환할 수 있지만 같은 책임을 갖지 않는다. 권한 요청, 주소 조회, 체인 전환, 서명 검증은 각각 별도의 상태로 다루는 편이 안전하다.

관련 글