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, a certified Ethereum Blockchain developer, serves as a technical writer at Consensys, with a special focus on developer tools. Before joining Consensys, Kingsley worked as a freelance backend developer on sevral platforms including Gigster and Braintrust. Boasting 5 years of software development experience, he's proficient with tools such as JavaScript (Node.js & React), GoLang, SQL, and Docker. Kingsley excels in producing step-by-step tutorials and how-to guides, utilizing the latest in web3 technology to help fellow developers build better dapps. Currently based in Dubai, Kingsley enjoys playing basketball and soccer in his free time

    모든 글 읽기