Get started with Ledger Wallet Provider
This guide shows you how to add Ledger Wallet Provider to a web application and request accounts through the EIP-6963 provider discovery protocol.
Partner program: Ledger Wallet Provider requires enrollment in Ledger’s partner program to obtain API keys. Contact our team to discuss integration.
Before you start, check the Requirements to confirm your target browsers support Web HID and Web Bluetooth.
Install the SDK
npm install @ledgerhq/ledger-wallet-providerYou can also install with yarn or pnpm.
Initialize the provider
import { initializeLedgerProvider } from '@ledgerhq/ledger-wallet-provider'
import '@ledgerhq/ledger-wallet-provider/styles.css'
const cleanup = initializeLedgerProvider({
dAppIdentifier: 'my-dapp', // Your dApp identifier
apiKey: 'your-api-key', // Your Ledger API key
loggerLevel: 'info', // Log level: 'fatal', 'error', 'warn', 'info', 'debug'
target: document.body, // Where to mount the UI (default: document.body)
dmkConfig: undefined, // Device Management Kit configuration (optional)
devConfig: {
stub: {
base: false, // Set to true to enable base stub mode
account: false, // Set to true to stub account operations
device: false, // Set to true to stub signer interactions
web3Provider: false, // Set to true to stub Web3 provider responses
dAppConfig: false, // Set to true to stub dApp configuration
},
},
})initializeLedgerProvider mounts the Ledger UI and registers the provider with the EIP-6963 discovery protocol. It returns a cleanup function — call it when your application unmounts to remove the UI and deregister event listeners.
See Configuration for the full list of available options.
Discover the provider (EIP-6963)
Listen for provider announcements before dispatching the request event:
window.addEventListener('eip6963:announceProvider', (event) => {
const { provider, info } = (event as CustomEvent).detail
console.log('Ledger provider discovered', info.name)
// Store provider for later use
})
window.dispatchEvent(new Event('eip6963:requestProvider'))Request accounts
const accounts = await provider.request({
method: 'eth_requestAccounts',
params: [],
})Once users connect their signer through the Ledger Wallet Provider flow, you receive accounts and can start signing transactions or messages.
Complete React example
Here’s a full working example that demonstrates provider discovery, account connection, and transaction signing:
import { useEffect, useState, useCallback } from 'react'
import type { EIP6963ProviderDetail } from '@ledgerhq/ledger-wallet-provider'
function useProviders() {
const [providers, setProviders] = useState<EIP6963ProviderDetail[]>([])
const [selectedProvider, setSelectedProvider] = useState<EIP6963ProviderDetail | null>(null)
const handleAnnounceProvider = useCallback((e: CustomEvent<EIP6963ProviderDetail>) => {
setProviders(prev => {
const found = prev.find(p => p.info.uuid === e.detail.info.uuid)
if (found) return prev
return [...prev, e.detail]
})
}, [])
useEffect(() => {
// Dynamic import is required because the library uses browser APIs
// and won't work with Server-Side Rendering (SSR)
const initializeProvider = async () => {
await import('@ledgerhq/ledger-wallet-provider/styles.css')
const { initializeLedgerProvider } = await import('@ledgerhq/ledger-wallet-provider')
const cleanup = initializeLedgerProvider({
dAppIdentifier: 'my-dapp',
apiKey: 'your-api-key',
loggerLevel: 'info',
})
window.addEventListener('eip6963:announceProvider', handleAnnounceProvider)
return cleanup
}
let cleanup: (() => void) | undefined
initializeProvider().then(cleanupFn => {
cleanup = cleanupFn
})
return () => {
cleanup?.()
window.removeEventListener('eip6963:announceProvider', handleAnnounceProvider)
}
}, [handleAnnounceProvider])
return { providers, selectedProvider, setSelectedProvider }
}
function App() {
const { providers, selectedProvider, setSelectedProvider } = useProviders()
const [account, setAccount] = useState<string | null>(null)
const connectWallet = async () => {
if (!selectedProvider) return
try {
const accounts = await selectedProvider.provider.request({
method: 'eth_requestAccounts',
params: []
})
setAccount(accounts[0])
} catch (error) {
console.error('Failed to connect:', error)
}
}
const signTransaction = async (transaction: any) => {
if (!selectedProvider) return
try {
const result = await selectedProvider.provider.request({
method: 'eth_signTransaction',
params: [transaction]
})
return result
} catch (error) {
console.error('Failed to sign transaction:', error)
}
}
return (
<div>
{providers.map(provider => (
<button
key={provider.info.uuid}
onClick={() => setSelectedProvider(provider)}
>
Connect {provider.info.name}
</button>
))}
{selectedProvider && (
<button onClick={connectWallet}>
Request accounts
</button>
)}
{account && <p>Connected: {account}</p>}
</div>
)
}Why dynamic imports are necessary:
- The library uses Web APIs (
window,document,CustomEvent) that do not exist in Node.js - Direct imports cause build errors in SSR frameworks (Next.js, Nuxt, SvelteKit, etc.)
- Dynamic imports ensure the code only runs in the browser environment
Troubleshooting
The eip6963:announceProvider event never fires.
Make sure initializeLedgerProvider has been called before dispatching eip6963:requestProvider. The provider announces itself immediately on initialization and again in response to any subsequent eip6963:requestProvider event. If the browser does not support Web HID or Web Bluetooth, the provider is not registered and no announcement is dispatched. Check the Requirements page and confirm the browser console shows no errors from the SDK.
request() rejects with error code -32603 and message “Ledger Provider is busy”.
A blocking request (signing or account selection) is already in progress. The provider processes one blocking request at a time. Wait for the current request to complete or be rejected before sending another.
request() rejects with error code 4100 (“Unauthorized”).
eth_requestAccounts has not been called yet, or the user disconnected. Call eth_requestAccounts first to prompt the user to select an account.
Next steps
Customize your integration
Explore Configuration options to set up chain support, floating button position, and wallet action buttons.
Review the API
Read the full list of provider methods, events, and error codes in the API reference.