Cómo implementar compatibilidad con EIP-6963 en tu Dapp Web3

Aprende a implementar EIP-6963 en tu dapp web3 para una experiencia de conexión de wallet más fluida

6 min de lectura
Cómo implementar compatibilidad con EIP-6963 en tu Dapp Web3

En mayo de 2023, un grupo de desarrolladores de wallets de Ethereum se reunió para establecer un nuevo estándar de la industria para conectarse con dapps. Antes de eso, la práctica habitual era que las wallets expusieran un proveedor EIP-1193 a través del objeto global window.ethereum en los navegadores web. Esta técnica funcionaba bien en los primeros tiempos, cuando los usuarios generalmente poseían y se conectaban a una sola wallet.

A medida que el ecosistema creció y los usuarios comenzaron a utilizar múltiples wallets al mismo tiempo, se volvió difícil para los desarrolladores de dapps gestionar correctamente el proceso de detectar a qué wallet estaba conectado un usuario, o darles la opción de seleccionar qué wallet usar cuando tenían varias instaladas.

En octubre de 2023, este estándar, ahora conocido como EIP-6963 o Multi-Wallet Injected Provider Discovery, fue aceptado. Desde entonces, los desarrolladores de wallets han comenzado a introducir EIP-6963 como la forma predeterminada de descubrir proveedores de wallets. MetaMask no es la excepción, ya que nos hemos asegurado de que este estándar sea compatible con la API de wallet de MetaMask y el SDK de MetaMask.

Además, las dapps que dependen de window.ethereum tienen garantizada la continuidad del soporte. Sin embargo, recomendamos implementar la compatibilidad con EIP-6963 en todas las dapps como el mecanismo de descubrimiento de proveedores preferido. La forma más sencilla de hacerlo es integrándose con bibliotecas de terceros que soporten EIP-6963. Visita la documentación de MetaMask para ver una lista en crecimiento de las bibliotecas disponibles.

Este tutorial te guiará a través de los pasos básicos para implementar la compatibilidad con EIP-6963 en tu dapp si decides no utilizar las bibliotecas de terceros disponibles. Utiliza React y TypeScript; sin embargo, cualquier desarrollador de JavaScript debería poder seguirlo e implementarlo en el framework de su elección.

Define las Interfaces y Tipos Compatibles para Wallets Web3

El primer paso para soportar el descubrimiento de múltiples wallets en tu dapp es definir todas las interfaces y tipos compatibles tal como se describen en la propuesta de mejora. Estas interfaces y tipos proporcionan una forma estructurada y estandarizada de gestionar los proveedores de wallets de Ethereum, facilitando la integración y el descubrimiento de múltiples wallets de 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

He añadido comentarios para ayudarte a entender qué representa cada interfaz y tipo. Consulta la propuesta de mejora para obtener una explicación más detallada.

Suscríbete a Eventos y Recibe Notificaciones de Nuevas Conexiones

El siguiente paso es establecer una forma de alertar a tu dapp cuando se detecten nuevas conexiones de wallets. Recuerda que el propósito de EIP-6963 es permitir la conexión y el cambio fluido entre múltiples wallets. En React, podemos lograrlo con el código de ejemplo que se muestra a continuación.

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

A continuación, se explica el código de ejemplo anterior:

  • Extendemos el WindowEventMap con un evento personalizado llamado "eip6963:announceProvider", estableciendo así un tipo de evento global para anunciar la disponibilidad de nuevos proveedores de wallets de Ethereum.

  • Inicializamos un almacén externo, representado por un array de proveedores con nombre, que se utiliza para llevar un registro de todos los proveedores de wallets detectados. Este array almacena objetos que se ajustan a la interfaz EIP6963ProviderDetail, que incluye información sobre el proveedor de la wallet y su proveedor de Ethereum correspondiente.

  • Introducimos un mecanismo de suscripción a través de la función subscribe dentro del objeto store. Esta función escucha el evento "eip6963:announceProvider" y actualiza el almacén externo con los detalles de cualquier nuevo proveedor anunciado. Garantiza que los proveedores solo se añadan si no están en el almacén, evitando entradas duplicadas.

  • El objeto store proporciona dos funcionalidades clave: 1. La función value permite recuperar el estado actual del almacén externo, es decir, la lista de proveedores de wallets detectados.

  1. La función subscribe permite que los componentes u otras partes de la aplicación se suscriban a los cambios en el almacén. Cuando se anuncia un nuevo proveedor y se añade al almacén, las entidades suscritas reciben una notificación a través de un mecanismo de callback, lo que les permite reaccionar ante la lista actualizada de proveedores.

A continuación, definamos un hook useSyncExternalStore para sincronizar el estado local con el almacén externo definido en store.tsx anteriormente:

// useSyncProviders.tsx

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

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

Muestra Dinámicamente Botones para cada Proveedor de Wallet Web3 Detectado

Definamos un componente de React que utilice el hook useSyncProviders para renderizar dinámicamente un botón por cada proveedor de wallet 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

A continuación, se explica el componente de React anterior:

  • Utilizamos dos hooks de estado, selectedWallet y userAccount, para llevar un registro del proveedor de wallet de Ethereum actualmente seleccionado y la dirección de cuenta del usuario, respectivamente.

  • Utilizamos el hook useSyncProviders para detectar dinámicamente cada proveedor de wallet de Ethereum disponible. Este hook devuelve un array de proveedores, cada uno conforme a la interfaz EIP6963ProviderDetail, que incluye la información del proveedor y el objeto proveedor de Ethereum.

  • Definimos la función handleConnect, que se invoca cuando se hace clic en el botón de un proveedor de wallet. Esta función utiliza el método request del proveedor con el método eth_requestAccounts para solicitar al usuario acceso a su cuenta. Si se concede el acceso, la primera dirección de cuenta se almacena en userAccount y los detalles del proveedor se almacenan en selectedWallet.

  • Por último, renderizamos una lista de botones para cada proveedor de wallet detectado, mostrando el nombre e ícono del proveedor.

Con nuestro componente definido, el último paso es renderizar este componente.

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

function App() {

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

export default App
Copiar

Con estos pocos pasos, hemos implementado exitosamente la compatibilidad con EIP-6963 en nuestra dapp a un nivel fundamental. A continuación, encontrarás una representación visual de la funcionalidad que acabamos de construir. El código completo de la dapp capturado en el visual se puede encontrar en GitHub.

EIP-6963 y el Camino por Delante para las Wallets Web3

A medida que seguimos viendo una adopción más amplia de este estándar por parte de los desarrolladores de wallets y las bibliotecas de conexión de terceros, estamos entusiasmados con los diversos casos de uso que abre para la comunidad de Ethereum, especialmente en la mejora de la experiencia de usuario para la incorporación de nuevos usuarios.

En MetaMask estamos ansiosos por colaborar con otros desarrolladores de wallets, desarrolladores de dapps y bibliotecas de conexión de terceros para garantizar una adopción aún mayor de EIP-6963. Para obtener más información sobre nuestro trabajo en apoyo de este estándar, consulta la documentación de MetaMask.

Traducido por IA. Puede contener errores. Por favor, verifique siempre la información.

Califica la traducción
  • Kingsley Okonkwo
    Kingsley Okonkwo

    Kingsley, desarrollador certificado de Ethereum Blockchain, trabaja como escritor técnico en Consensys, con un enfoque especial en herramientas para desarrolladores. Antes de unirse a Consensys, Kingsley trabajó como desarrollador backend freelance en varias plataformas, entre ellas Gigster y Braintrust. Con 5 años de experiencia en desarrollo de software, domina herramientas como JavaScript (Node.js y React), GoLang, SQL y Docker. Kingsley destaca en la creación de tutoriales paso a paso y guías prácticas, aprovechando las últimas tecnologías web3 para ayudar a otros desarrolladores a construir mejores dapps. Actualmente reside en Dubái y en su tiempo libre disfruta jugando al baloncesto y al fútbol.

    Leer todos los artículos