Skip to content

EIP-1193

EIP-1193

Overview

EIP-1193 (Ethereum Provider JavaScript API) is the standard JavaScript API specification for Ethereum providers, defining the communication interface between browser wallets (such as MetaMask) and Web3 applications (DApps). This standard unifies the API behavior of different wallets, enabling developers to write DApps that are compatible with all standards-compliant wallets.

Before EIP-1193, different wallets had varying API implementations, requiring developers to write different adapter code for each wallet. The introduction of EIP-1193 greatly improved this situation.

Background

Wallet Fragmentation Problem In the early Ethereum ecosystem: - MetaMask, Trust Wallet, Coinbase Wallet, and other wallets each had their own approach - Each wallet had slightly different APIs - Event triggering mechanisms were inconsistent - Developers had to write extensive compatibility code

The Chaos of Window.ethereum Historically, wallets injected provider objects via window.ethereum: - Conflicts arose when multiple wallets injected simultaneously - Later-loaded wallets would overwrite earlier ones - Users could not choose which wallet to use - This led to unpredictable behavior

Core Concepts

Provider The provider is a JavaScript object that provides access to Ethereum: - Exposes standard API methods - Emits standardized events - Handles RPC requests

Event-Driven Architecture EIP-1193 adopts an event-driven design: - Events are emitted when wallet state changes - DApps listen for events and respond accordingly - Avoids polling, improving efficiency

Transport Agnostic The standard is agnostic to the underlying transport protocol: - Can communicate via HTTP, WebSocket, and other protocols - Can interact with different Ethereum clients - Flexible architecture design

Core Interface

Request Method

// Core method: send JSON-RPC request
provider.request({
  method: string,
  params?: Array<any>
}): Promise<any>

// Example: request account access
await provider.request({
  method: 'eth_requestAccounts'
})

// Example: send transaction
await provider.request({
  method: 'eth_sendTransaction',
  params: [{
    from: '0x...',
    to: '0x...',
    value: '0x...',
    // ...
  }]
})

// Example: get balance
const balance = await provider.request({
  method: 'eth_getBalance',
  params: ['0x...', 'latest']
})

Event Listening

// Listen for account changes
provider.on('accountsChanged', (accounts) => {
  console.log('Current account:', accounts[0])
  // Update UI
})

// Listen for chain switch
provider.on('chainChanged', (chainId) => {
  console.log('Switched to chain:', chainId)
  // Reload page or update state
  window.location.reload()
})

// Listen for connection status
provider.on('connect', (connectInfo) => {
  console.log('Connected to chain:', connectInfo.chainId)
})

// Listen for disconnection
provider.on('disconnect', (error) => {
  console.log('Disconnected:', error)
  // Show reconnection UI
})

// Listen for messages (optional)
provider.on('message', (message) => {
  console.log('Message received:', message)
})

Removing Listeners

// Follows the Node.js EventEmitter API
provider.removeListener('accountsChanged', handler)
provider.off('chainChanged', handler)

Standard Events

accountsChanged Triggered when the user switches accounts or authorization state changes:

provider.on('accountsChanged', (accounts: string[]) => {
  if (accounts.length === 0) {
    // User disconnected all accounts
    console.log('Please connect MetaMask')
  } else {
    // User switched accounts
    const currentAccount = accounts[0]
  }
})

chainChanged Triggered when the user switches networks:

provider.on('chainChanged', (chainId: string) => {
  // chainId is a hexadecimal string, e.g., '0x1' (mainnet)
  // It is recommended to reload the page to avoid state inconsistency
  window.location.reload()
})

connect Triggered when the provider connects to a chain:

provider.on('connect', (connectInfo: { chainId: string }) => {
  console.log('Connected to chain ID:', connectInfo.chainId)
})

disconnect Triggered when the provider disconnects:

provider.on('disconnect', (error: ProviderRpcError) => {
  console.error('Disconnected:', error.message, error.code)
})

Error Handling

Standard Error Codes EIP-1193 defines standard JSON-RPC error codes:

try {
  await provider.request({ method: 'eth_sendTransaction', params: [...] })
} catch (error) {
  if (error.code === 4001) {
    // User rejected the request
    console.log('User cancelled the transaction')
  } else if (error.code === -32002) {
    // Request already being processed
    console.log('Please confirm in your wallet')
  } else if (error.code === -32603) {
    // Internal error
    console.error('Transaction failed:', error.message)
  }
}

Common Error Codes - 4001: User rejected the request - 4100: Requested method or parameters not supported - 4200: Provider not authorized to perform the operation - 4900: Provider disconnected - -32700: Parse error - -32600: Invalid request - -32601: Method not found - -32602: Invalid parameters - -32603: Internal error

Practical Examples

Connecting a Wallet

async function connectWallet() {
  if (typeof window.ethereum !== 'undefined') {
    try {
      // Request account access
      const accounts = await window.ethereum.request({
        method: 'eth_requestAccounts'
      })

      const account = accounts[0]
      console.log('Connected account:', account)

      // Get chain ID
      const chainId = await window.ethereum.request({
        method: 'eth_chainId'
      })
      console.log('Current chain ID:', chainId)

      return account
    } catch (error) {
      if (error.code === 4001) {
        console.log('User rejected the connection')
      } else {
        console.error('Connection failed:', error)
      }
    }
  } else {
    console.log('Please install MetaMask')
  }
}

Sending a Transaction

async function sendTransaction(to, value) {
  try {
    const transactionHash = await window.ethereum.request({
      method: 'eth_sendTransaction',
      params: [{
        from: currentAccount,
        to: to,
        value: value, // hexadecimal wei
        gas: '0x5208', // 21000
      }]
    })

    console.log('Transaction hash:', transactionHash)
    return transactionHash
  } catch (error) {
    console.error('Transaction failed:', error)
  }
}

Signing a Message

async function signMessage(message) {
  try {
    const signature = await window.ethereum.request({
      method: 'personal_sign',
      params: [message, currentAccount]
    })

    console.log('Signature:', signature)
    return signature
  } catch (error) {
    console.error('Signing failed:', error)
  }
}

Switching Networks

async function switchToPolygon() {
  try {
    await window.ethereum.request({
      method: 'wallet_switchEthereumChain',
      params: [{ chainId: '0x89' }], // Polygon mainnet
    })
  } catch (error) {
    if (error.code === 4902) {
      // Chain not added, need to add it first
      await window.ethereum.request({
        method: 'wallet_addEthereumChain',
        params: [{
          chainId: '0x89',
          chainName: 'Polygon',
          nativeCurrency: {
            name: 'MATIC',
            symbol: 'MATIC',
            decimals: 18
          },
          rpcUrls: ['https://polygon-rpc.com'],
          blockExplorerUrls: ['https://polygonscan.com']
        }]
      })
    }
  }
}

Relationship with Web3 Libraries

Ethers.js

import { ethers } from 'ethers'

// Using the EIP-1193 provider
const provider = new ethers.BrowserProvider(window.ethereum)
const signer = await provider.getSigner()

Web3.js

import Web3 from 'web3'

// Using the EIP-1193 provider
const web3 = new Web3(window.ethereum)

Wagmi (React)

import { WagmiConfig, createConfig } from 'wagmi'
import { mainnet } from 'wagmi/chains'
import { InjectedConnector } from 'wagmi/connectors/injected'

// Automatically uses the EIP-1193 provider
const config = createConfig({
  chains: [mainnet],
  connectors: [new InjectedConnector()],
})

Best Practices

Initialization Check

// Check if provider is available
if (typeof window.ethereum !== 'undefined') {
  console.log('Ethereum provider detected')

  // Check if it is MetaMask
  if (window.ethereum.isMetaMask) {
    console.log('This is MetaMask')
  }
} else {
  console.log('Please install MetaMask or another wallet')
}

Handling Chain Switches

// Listen for chain switch and reload
window.ethereum.on('chainChanged', (chainId) => {
  // Strongly recommended to reload the page
  window.location.reload()
})

Handling Account Changes

// Listen for account switches
window.ethereum.on('accountsChanged', (accounts) => {
  if (accounts.length === 0) {
    // User disconnected
    setCurrentAccount(null)
  } else {
    // User switched accounts
    setCurrentAccount(accounts[0])
  }
})

Error Handling

// Always handle errors
try {
  const result = await window.ethereum.request({ ... })
} catch (error) {
  // User-readable error messages
  if (error.code === 4001) {
    showMessage('You cancelled the operation')
  } else {
    showMessage(`Operation failed: ${error.message}`)
  }
}

EIP-6963 Supplement

EIP-1193 defines the provider API, but multi-wallet coexistence still has issues. EIP-6963 (Multi Injected Provider Discovery) solves this problem: - Uses an event mechanism to discover multiple wallets - Users can choose which wallet to use - Avoids window.ethereum race conditions

Ecosystem Impact

Wallet Developers - Must implement EIP-1193 to be compatible with mainstream DApps - Provides a consistent user experience - Reduces development and maintenance costs

*DApp* Developers** - Write code once, compatible with all wallets - No longer need wallet-specific adapters - More reliable API behavior

Users - Free to choose any wallet - Consistent interaction experience - Fewer compatibility issues