Pular para o conteúdo

Como implementar o suporte EIP-6963 no seu Dapp Web3

Saiba como implementar o EIP-6963 no seu dapp web3 para uma experiência de carteira conectada mais fluida

6 min de leitura
Como implementar o suporte EIP-6963 no seu Dapp Web3

Em maio de 2023, um grupo de desenvolvedores de carteiras Ethereum se reuniu para estabelecer um novo padrão da indústria para conexão com dapps. Antes disso, a prática comum era que as carteiras expusessem um provedor EIP-1193 por meio do objeto global window.ethereum nos navegadores web. Essa técnica funcionava bem nos primeiros tempos, quando os usuários normalmente possuíam e se conectavam a apenas uma carteira.

À medida que o ecossistema cresceu e os usuários passaram a utilizar múltiplas carteiras ao mesmo tempo, ficou difícil para os desenvolvedores de dapps gerenciar adequadamente o processo de detectar a qual carteira um usuário estava conectado ou oferecer aos usuários a opção de selecionar qual carteira usar quando várias estavam instaladas.

Em outubro de 2023, esse padrão, agora conhecido como EIP-6963 ou Multi-Wallet Injected Provider Discovery, foi aceito. Desde então, os desenvolvedores de carteiras começaram a adotar o EIP-6963 como a forma padrão de descoberta de provedores de carteiras. A MetaMask não é exceção, pois garantimos que esse padrão seja suportado pela API de carteira da MetaMask e pelo MetaMask SDK.

Além disso, os dapps que dependem do window.ethereum têm suporte contínuo garantido. No entanto, incentivamos a implementação do suporte ao EIP-6963 em todos os dapps como o mecanismo preferencial de descoberta de provedores. A maneira mais fácil de fazer isso é integrando bibliotecas de terceiros com suporte ao EIP-6963. Acesse a documentação da MetaMask para uma lista crescente de bibliotecas disponíveis.

Este tutorial irá guiá-lo pelos passos básicos para implementar o suporte ao EIP-6963 no seu dapp, caso você decida não utilizar as bibliotecas de terceiros disponíveis. Ele usa React e TypeScript; no entanto, qualquer desenvolvedor JavaScript deve conseguir acompanhar e implementar isso no framework de sua preferência.

Defina as Interfaces e Tipos Suportados para Carteiras Web3

O primeiro passo para suportar a descoberta de múltiplas carteiras no seu dapp é definir todas as interfaces e tipos suportados, conforme descrito na proposta de melhoria. Essas interfaces e tipos fornecem uma maneira estruturada e padronizada de lidar com provedores de carteiras Ethereum, facilitando a integração e a descoberta de múltiplas carteiras Ethereum.

// 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
  }
}
Copiar

Adicionei comentários para ajudá-lo a entender o que cada interface e tipo representa. Consulte a proposta de melhoria para uma explicação mais detalhada.

Inscreva-se em Eventos e Seja Notificado sobre Novas Conexões

O próximo passo é estabelecer uma forma de alertar seu dapp quando novas conexões de carteiras forem detectadas. Lembre-se: o objetivo do EIP-6963 é permitir a conexão e a alternância fluidas entre múltiplas carteiras. No React, podemos alcançar isso com o código de exemplo abaixo.

// 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);
  }
}
Copiar

Veja a seguir uma análise do código de exemplo acima:

  • Estendemos o WindowEventMap com um evento personalizado chamado "eip6963:announceProvider", estabelecendo assim um tipo de evento global para anunciar a disponibilidade de novos provedores de carteiras Ethereum.

  • Inicializamos um store externo, representado por um array de provedores nomeados, que é usado para rastrear todos os provedores de carteiras detectados. Esse array armazena objetos em conformidade com a interface EIP6963ProviderDetail, que inclui informações sobre o provedor de carteira e seu provedor Ethereum correspondente.

  • Introduzimos um mecanismo de inscrição por meio da função subscribe dentro do objeto store. Essa função escuta o evento "eip6963:announceProvider" e atualiza o store externo com os detalhes de quaisquer novos provedores anunciados. Ela garante que os provedores sejam adicionados apenas se ainda não estiverem no store, evitando entradas duplicadas.

  • O objeto store oferece duas funcionalidades principais: 1. A função value permite recuperar o estado atual do store externo, ou seja, a lista de provedores de carteiras detectados.

  1. A função subscribe permite que componentes ou outras partes da aplicação se inscrevam para receber atualizações do store. Quando um novo provedor é anunciado e adicionado ao store, as entidades inscritas são notificadas por meio de um mecanismo de callback, permitindo que reajam à lista atualizada de provedores.

A seguir, vamos definir um hook useSyncExternalStore para sincronizar o estado local com o store externo definido no store.tsx acima:

// useSyncProviders.tsx

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

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

Exiba Botões Dinamicamente para cada Provedor de Carteira Web3 Detectado

Vamos definir um componente React que usa o hook useSyncProviders para renderizar dinamicamente um botão para cada provedor de carteira detectado:

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>
      )}
    </>
  );
};
Copiar

Veja a seguir uma análise do componente React acima:

  • Usamos dois hooks de estado, selectedWallet e userAccount, para rastrear o provedor de carteira Ethereum atualmente selecionado e o endereço da conta do usuário, respectivamente.

  • Usamos o hook useSyncProviders para detectar dinamicamente cada provedor de carteira Ethereum disponível. Esse hook retorna um array de provedores, cada um em conformidade com a interface EIP6963ProviderDetail, que inclui as informações do provedor e o objeto provedor Ethereum.

  • Definimos a função handleConnect, que é invocada quando o botão de um provedor de carteira é clicado. Essa função usa o método request do provedor com o método eth_requestAccounts para solicitar ao usuário o acesso à conta. Se o acesso for concedido, o primeiro endereço de conta é armazenado em userAccount, e os detalhes do provedor são armazenados em selectedWallet.

  • Por fim, renderizamos uma lista de botões para cada provedor de carteira detectado, exibindo o nome e o ícone do provedor.

Com nosso componente definido, o último passo é renderizá-lo.

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

function App() {

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

export default App
Copiar

Com esses poucos passos, implementamos com sucesso o suporte ao EIP-6963 no nosso dapp em um nível fundamental. Veja abaixo uma representação visual da funcionalidade que acabamos de construir. O código completo do dapp capturado no visual pode ser encontrado no GitHub.

EIP-6963 e o Caminho à Frente para as Carteiras Web3

À medida que continuamos a ver uma adoção mais ampla desse padrão por desenvolvedores de carteiras e bibliotecas de conexão de terceiros, estamos animados com os diversos casos de uso que ele abre para a comunidade Ethereum, especialmente na melhoria da UX para a integração de novos usuários.

Nós, da MetaMask, estamos ansiosos para colaborar com outros desenvolvedores de carteiras, desenvolvedores de dapps e bibliotecas de conexão de terceiros para garantir uma adoção ainda maior do EIP-6963. Para mais informações sobre nosso trabalho de suporte a esse padrão, consulte a documentação da MetaMask.

Traduzido por IA. Pode conter erros. Por favor, verifique sempre as informações.

Avalie a tradução

Este artigo foi escrito por:

  • Kingsley Okonkwo

    Kingsley, desenvolvedor certificado de Blockchain Ethereum, atua como redator técnico na Consensys, com foco especial em ferramentas para desenvolvedores. Antes de ingressar na Consensys, Kingsley trabalhou como desenvolvedor backend freelance em diversas plataformas, incluindo Gigster e Braintrust. Com 5 anos de experiência em desenvolvimento de software, ele domina ferramentas como JavaScript (Node.js e React), GoLang, SQL e Docker. Kingsley se destaca na criação de tutoriais passo a passo e guias práticos, utilizando o que há de mais moderno em tecnologia web3 para ajudar outros desenvolvedores a criar dapps melhores. Atualmente radicado em Dubai, Kingsley aproveita seu tempo livre jogando basquete e futebol.

    Ler todos os artigos