Web3 Dapp에서 EIP-6963 지원을 구현하는 방법

웹3 dapp에서 EIP-6963을 구현하여 더 원활한 연결 지갑 경험을 제공하는 방법을 알아보세요

6분 읽기
Web3 Dapp에서 EIP-6963 지원을 구현하는 방법

2023년 5월, 이더리움 지갑 개발자 그룹이 모여 dapp과의 연결을 위한 새로운 업계 표준을 수립했습니다. 그 이전까지는 지갑이 웹 브라우저의 window.ethereum 전역 객체를 통해 EIP-1193 provider를 노출하는 방식이 일반적이었습니다. 이 방식은 사용자가 주로 하나의 지갑만 소유하고 연결하던 초기에는 잘 작동했습니다.

생태계가 성장하고 사용자들이 여러 지갑을 동시에 사용하기 시작하면서, dapp 개발자들이 사용자가 연결된 지갑을 감지하는 프로세스를 적절히 관리하거나, 여러 지갑이 설치된 경우 사용자에게 사용할 지갑을 선택할 수 있는 옵션을 제공하기가 어려워졌습니다.

2023년 10월, 현재 EIP-6963 또는 Multi-Wallet Injected Provider Discovery로 알려진 이 표준이 승인되었습니다. 이후 지갑 개발자들은 EIP-6963을 wallet provider 검색의 기본 방식으로 도입하기 시작했습니다. MetaMask도 예외가 아니며, MetaMask 지갑 API와 MetaMask SDK에서 이 표준을 지원하도록 보장했습니다.

또한 window.ethereum에 의존하는 dapp은 지속적인 지원이 보장됩니다. 그러나 선호하는 provider 검색 메커니즘으로서 모든 dapp에 EIP-6963 지원을 구현할 것을 권장합니다. 가장 쉬운 방법은 EIP-6963을 지원하는 서드파티 라이브러리와 통합하는 것입니다. 사용 가능한 라이브러리의 목록은 MetaMask 문서를 방문하여 확인하세요.

이 튜토리얼은 사용 가능한 서드파티 라이브러리를 사용하지 않기로 결정한 경우, dapp에 EIP-6963 지원을 구현하는 기본 단계를 안내합니다. React와 TypeScript를 사용하지만, JavaScript 개발자라면 누구든 자신이 선택한 프레임워크에서 이를 따라 구현할 수 있습니다.

Web3 지갑을 위한 지원 인터페이스 및 타입 정의

dapp에서 멀티 지갑 검색을 지원하는 첫 번째 단계는 개선 제안서에 명시된 모든 지원 인터페이스와 타입을 정의하는 것입니다. 이러한 인터페이스와 타입은 이더리움 wallet provider를 처리하는 구조화되고 표준화된 방법을 제공하여, 여러 이더리움 지갑의 통합 및 검색을 용이하게 합니다.

// EthereumProviderTypes.d.ts

// Interface for provider information following EIP-6963.
interface EIP6963ProviderInfo {
  walletId: string; // Unique identifier for the wallet e.g io.metamask, io.metamask.flask 
  uuid: string; // Globally unique ID to differentiate between provider sessions for the lifetime of the page
  name: string; // Human-readable name of the wallet
  icon: string; // URL to the wallet's icon
}

// Interface for Ethereum providers based on the EIP-1193 standard.
interface EIP1193Provider {
  isStatus?: boolean; // Optional: Indicates the status of the provider
  host?: string; // Optional: Host URL of the Ethereum node
  path?: string; // Optional: Path to a specific endpoint or service on the host
  sendAsync?: (request: { method: string, params?: Array<unknown> }, callback: (error: Error | null, response: unknown) => void) => void; // For sending asynchronous requests
  send?: (request: { method: string, params?: Array<unknown> }, callback: (error: Error | null, response: unknown) => void) => void; // For sending synchronous requests
  request: (request: { method: string, params?: Array<unknown> }) => Promise<unknown>; // Standard method for sending requests per EIP-1193
}

// Interface detailing the structure of provider information and its Ethereum provider.
interface EIP6963ProviderDetail {
  info: EIP6963ProviderInfo; // The provider's info
  provider: EIP1193Provider; // The EIP-1193 compatible provider
}

// Type representing the event structure for announcing a provider based on EIP-6963.
type EIP6963AnnounceProviderEvent = {
  detail: {
    info: EIP6963ProviderInfo; // The provider's info
    provider: EIP1193Provider; // The EIP-1193 compatible provider
  }
}
복사

각 인터페이스와 타입이 무엇을 나타내는지 이해할 수 있도록 주석을 추가했습니다. 더 자세한 설명은 개선 제안서를 참조하세요.

이벤트 구독 및 새로운 연결 알림 받기

다음 단계는 새로운 지갑 연결이 감지될 때 dapp에 알림을 보내는 방법을 구축하는 것입니다. EIP-6963의 목적은 여러 지갑 간의 원활한 연결 및 전환을 가능하게 하는 것임을 기억하세요. React에서는 아래 샘플 코드로 이를 구현할 수 있습니다.

// store.tsx

declare global {
  interface WindowEventMap {
    "eip6963:announceProvider": CustomEvent<EIP6963AnnounceProviderEvent>;
  }
}

let providers: EIP6963ProviderDetail[] = [];

export const store = {
  value: () => providers,

  subscribe: (callback: () => void) => {
    function onAnnouncement(event: EIP6963AnnounceProviderEvent) {
      // Prevent adding a provider if it already exists in the list based on its uuid.
      if (providers.some(p => p.info.uuid === event.detail.info.uuid)) return;

      // Add the new provider to the list and call the provided callback function.
      providers = [...providers, event.detail];
      callback();
    }

    window.addEventListener("eip6963:announceProvider", onAnnouncement as EventListener);
    window.dispatchEvent(new Event("eip6963:requestProvider"));

    return () => window.removeEventListener("eip6963:announceProvider", onAnnouncement as EventListener);
  }
}
복사

위 코드 샘플에 대한 설명은 다음과 같습니다:

  • WindowEventMap을 "eip6963:announceProvider"라는 커스텀 이벤트로 확장하여, 새로운 이더리움 wallet provider의 가용성을 알리기 위한 전역 이벤트 타입을 설정합니다.

  • 감지된 모든 wallet provider를 추적하는 데 사용되는 외부 store를 named provider 배열로 초기화합니다. 이 배열은 wallet provider 및 해당 이더리움 provider에 대한 정보를 포함하는 EIP6963ProviderDetail 인터페이스를 준수하는 객체를 저장합니다.

  • store 객체 내의 subscribe 함수를 통해 구독 메커니즘을 도입합니다. 이 함수는 "eip6963:announceProvider" 이벤트를 수신하고 새로 발표된 provider의 세부 정보로 외부 store를 업데이트합니다. 중복 항목을 방지하기 위해 store에 없는 provider만 추가되도록 합니다.

  • store 객체는 두 가지 핵심 기능을 제공합니다: 1. value 함수는 외부 store의 현재 상태, 즉 감지된 wallet provider 목록을 검색할 수 있게 합니다.

  1. subscribe 함수는 컴포넌트 또는 애플리케이션의 다른 부분이 store의 변경 사항을 구독할 수 있게 합니다. 새 provider가 발표되어 store에 추가되면, 구독된 엔티티는 콜백 메커니즘을 통해 알림을 받아 업데이트된 provider 목록에 반응할 수 있습니다.

다음으로, 위의 store.tsx에 정의된 외부 store와 로컬 상태를 동기화하기 위한 useSyncExternalStore hook을 정의해 보겠습니다:

// useSyncProviders.tsx

import { useSyncExternalStore } from "react";
import { store } from "./store";

export const useSyncProviders = ()=> useSyncExternalStore(store.subscribe, store.value, store.value)
복사

감지된 각 Web3 Wallet Provider에 대한 버튼 동적 표시

useSyncProviders hook을 사용하여 감지된 각 wallet provider에 대한 버튼을 동적으로 렌더링하는 React 컴포넌트를 정의해 보겠습니다:

import { useState } from 'react';
import { useSyncProviders } from '../hooks/useSyncProviders';
import { formatAddress } from '~/utils';

export const DiscoverWalletProviders = () => {
  const [selectedWallet, setSelectedWallet] = useState<EIP6963ProviderDetail | undefined>();
  const [userAccount, setUserAccount] = useState<string>('');
  const providers = useSyncProviders();

  const handleConnect = async (providerWithInfo: EIP6963ProviderDetail) => {
    const accounts = await providerWithInfo.provider.request({ method: 'eth_requestAccounts' }).catch(console.error);

    if (accounts && accounts[0]) {
      setSelectedWallet(providerWithInfo);
      setUserAccount(accounts[0]);
    }
  };

  return (
    <>
      <h2>Wallets Detected:</h2>
      <div>
        {providers.length > 0 ? (
          providers.map((provider) => (
            <button key={provider.info.uuid} onClick={() => handleConnect(provider)}>
              <img src={provider.info.icon} alt={provider.info.name} />
              <div>{provider.info.name}</div>
            </button>
          ))
        ) : (
          <div>There are no announced providers.</div>
        )}
      </div>
      <hr />
      <h2>{userAccount ? 'Wallet Selected' : 'No Wallet Selected'}</h2>
      {userAccount && (
        <div>
          <img src={selectedWallet!.info.icon} alt={selectedWallet!.info.name} />
          <div>{selectedWallet!.info.name}</div>
          <div>({formatAddress(userAccount)})</div>
        </div>
      )}
    </>
  );
};
복사

위 React 컴포넌트에 대한 설명은 다음과 같습니다:

  • 두 개의 상태 hook인 selectedWalletuserAccount를 사용하여 현재 선택된 이더리움 wallet provider와 사용자의 계정 주소를 각각 추적합니다.

  • useSyncProviders hook을 사용하여 사용 가능한 각 이더리움 wallet provider를 동적으로 감지합니다. 이 hook은 provider 배열을 반환하며, 각 provider는 provider의 정보와 이더리움 provider 객체를 포함하는 EIP6963ProviderDetail 인터페이스를 준수합니다.

  • wallet provider 버튼이 클릭될 때 호출되는 handleConnect 함수를 정의합니다. 이 함수는 eth_requestAccounts 메서드와 함께 provider의 request 메서드를 사용하여 사용자에게 계정 접근을 요청합니다. 접근이 허용되면 첫 번째 계정 주소가 userAccount에 저장되고, provider의 세부 정보가 selectedWallet에 저장됩니다.

  • 마지막으로, 감지된 각 wallet provider에 대한 버튼 목록을 렌더링하여 provider의 이름과 아이콘을 표시합니다.

컴포넌트가 정의되었으므로, 마지막 단계는 이 컴포넌트를 렌더링하는 것입니다.

import './App.css'
import { DiscoverWalletProviders } from './components/DiscoverWalletProviders'

function App() {

  return (
    <>
      <DiscoverWalletProviders/>
    </>
  )
}

export default App
복사

이 몇 가지 단계를 통해 기본 수준에서 dapp에 EIP-6963 지원을 성공적으로 구현했습니다. 방금 구축한 기능의 시각적 표현은 아래를 참조하세요. 시각 자료에 캡처된 dapp의 전체 코드는 GitHub에서 확인할 수 있습니다.

EIP-6963과 Web3 지갑의 미래

지갑 개발자와 서드파티 연결 라이브러리에 의한 이 표준의 광범위한 채택이 계속되면서, 특히 신규 사용자 온보딩 UX 개선 측면에서 이더리움 커뮤니티에 열리는 다양한 사용 사례에 대해 기대가 큽니다.

MetaMask는 EIP-6963의 더욱 광범위한 채택을 보장하기 위해 다른 지갑 빌더, dapp 개발자, 서드파티 연결 라이브러리와 협력하기를 열망합니다. 이 표준을 지원하기 위한 저희의 작업에 대한 자세한 내용은 MetaMask 문서를 참조하세요.

AI가 번역했습니다. 오류가 있을 수 있습니다. 항상 정보를 확인하시기 바랍니다.

번역 평가하기
  • Kingsley Okonkwo
    Kingsley Okonkwo

    공인 이더리움 블록체인 개발자인 Kingsley는 Consensys에서 테크니컬 라이터로 활동하며, 특히 개발자 도구에 중점을 두고 있습니다. Consensys에 합류하기 전, Kingsley는 GigsterBraintrust를 포함한 여러 플랫폼에서 프리랜서 백엔드 개발자로 활동했습니다. 5년간의 소프트웨어 개발 경험을 바탕으로, JavaScript(Node.js 및 React), GoLang, SQL, Docker 등의 도구에 능숙합니다. Kingsley는 최신 web3 기술을 활용해 단계별 튜토리얼과 사용 방법 가이드를 제작하는 데 탁월하며, 동료 개발자들이 더 나은 dapp을 구축할 수 있도록 돕고 있습니다. 현재 두바이에 거주하고 있는 Kingsley는 여가 시간에 농구와 축구를 즐깁니다.

    모든 글 읽기