---
title: Get started with Ledger Wallet Provider
category: how-to
---

# 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.

> **Note:** **Partner program**: Ledger Wallet Provider requires enrollment in Ledger's partner program to obtain API keys. <a href="https://tally.so/r/wzaAVa" target="_blank" className="text-orange-400 hover:text-orange-300">Contact our team</a> to discuss integration.

Before you start, check the [Requirements](./requirements) to confirm your target browsers support Web HID and Web Bluetooth.

## Install the SDK

```bash
npm install @ledgerhq/ledger-wallet-provider
```

You can also install with `yarn` or `pnpm`.

## Initialize the provider

```ts
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](./configuration) for the full list of available options.

## Discover the provider (EIP-6963)

Listen for provider announcements before dispatching the request event:

```ts
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

```ts
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:

```tsx
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>
  )
}
```

> **Note:** **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](./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](./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](./api-reference).
