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
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
Related Links¶
- EIP-1193 Official Specification
- MetaMask Documentation
- Ethers.js Provider Documentation
- EIP-6963 Multi-Wallet Discovery