Skip to Content
📢 Breaking change: Applications using LedgerJS for transport implementation should migrate to the Device Management Kit (DMK). Learn more.
DocumentationLedger Wallet ProviderBlockchainsSolana

Solana with Wallet Standard

This guide shows how to connect to Ledger Wallet Provider from a Solana dApp and sign messages or transactions through the Wallet Standard .

Ledger Wallet Provider registers a Solana Wallet Standard wallet when you call initializeLedgerProvider with the Solana factory and Solana is enabled in your partner dApp configuration (dAppIdentifier).

Prerequisites

npm install @ledgerhq/ledger-wallet-provider @ledgerhq/ledger-wallet-provider-solana

Initialize once at application startup (see Get started and Configuration):

import { initializeLedgerProvider } from '@ledgerhq/ledger-wallet-provider' import { solanaBlockchainProviderFactory } from '@ledgerhq/ledger-wallet-provider-solana' import '@ledgerhq/ledger-wallet-provider/styles.css' const cleanup = initializeLedgerProvider({ dAppIdentifier: 'my-dapp', apiKey: 'your-api-key', blockchainProviderFactories: [solanaBlockchainProviderFactory], }) // Call cleanup() when your app unmounts to unregister the Solana wallet too.

Optional Solana packages for discovery and React hooks:

npm install @wallet-standard/app # React helpers (optional): npm install @solana/react @wallet-standard/react

Libraries such as @solana/wallet-adapter also discover Wallet Standard wallets with no extra Ledger wiring.

Supported features

After connection, the Ledger Solana wallet exposes:

FeaturePurpose
standard:connect / standard:disconnectConnect and disconnect accounts
standard:eventsListen for account / feature changes
solana:signMessageSign an off-chain message (ed25519)
solana:signTransactionSign a serialized transaction without broadcasting
solana:signAndSendTransactionSign and submit a transaction

Supported transaction versions: legacy and 0 (versioned).

Discover and connect

Unlike EVM, there is no discovery event to dispatch: the Ledger wallet announces itself through the Wallet Standard as soon as initializeLedgerProvider runs. You only need to list the registered wallets.

Never dispatch wallet-standard:app-ready yourself. That event carries the app’s register callback in its detail, so emitting a bare event throws inside every listening wallet. Let getWallets() or your wallet adapter emit it for you.

List Wallet Standard wallets and connect to Ledger:

import { getWallets } from '@wallet-standard/app' const { get } = getWallets() function findLedgerWallet() { return get().find((wallet) => wallet.chains.some((chain) => chain.startsWith('solana:')) && wallet.name.toLowerCase().includes('ledger'), ) } const wallet = findLedgerWallet() if (!wallet) { throw new Error('Ledger Solana wallet not found. Call initializeLedgerProvider first.') } const connect = wallet.features['standard:connect'] if (!connect) { throw new Error('Wallet does not support standard:connect') } const { accounts } = await connect.connect() const account = accounts[0]

With React, @solana/react / @wallet-standard/react helpers (useConnect, useSelectedWalletAccount, …) wrap the same Wallet Standard APIs.

Sign a message

const signMessageFeature = wallet.features['solana:signMessage'] if (!signMessageFeature) { throw new Error('Wallet does not support solana:signMessage') } const message = new TextEncoder().encode('Hello from my Solana dApp') const [result] = await signMessageFeature.signMessage({ account, message, }) // result.signedMessage — bytes that were signed // result.signature — ed25519 signature bytes

React equivalent with @solana/react:

import { useSignMessage } from '@solana/react' const signMessage = useSignMessage(account) const { signature } = await signMessage({ message: new TextEncoder().encode('Hello from my Solana dApp'), })

Signing opens the Ledger Wallet Provider UI so the user can confirm on their device.

Sign a transaction

Build and serialize a Solana transaction with your preferred toolkit (@solana/kit, @solana/web3.js, …), then pass the serialized bytes:

const signTransactionFeature = wallet.features['solana:signTransaction'] if (!signTransactionFeature) { throw new Error('Wallet does not support solana:signTransaction') } // `serializedTransaction` is a Uint8Array (wire format) const [signed] = await signTransactionFeature.signTransaction({ account, transaction: serializedTransaction, chain: 'solana:mainnet', // or solana:devnet / solana:testnet }) // signed.signedTransaction — signed wire bytes (you broadcast yourself)

Two-step signing

Reviewing a transaction on a hardware device can take longer than a Solana blockhash stays valid. Ledger Wallet Provider uses two-step signing (also called delayed signing) so that wait does not expire the transaction.

The device shows the transaction first. When the user approves, the provider fetches a fresh recentBlockhash (getLatestBlockhash, commitment finalized) and the device signs that updated message. The returned signedTransaction already contains that blockhash, and the signature matches those bytes.

Do not check that the returned blockhash equals the one you set when you built the transaction. The comparison fails whenever the hash was refreshed, even though the signature is valid.

What to do with the result:

  • Broadcast or submit the returned signedTransaction as-is. Do not rebuild the message or put the original blockhash back.
  • For solana:signAndSendTransaction, Ledger already broadcasts the refreshed transaction. Track the returned signature.
  • Confirm the transaction by signature status. Do not confirm with { blockhash, lastValidBlockHeight }: the returned transaction contains the refreshed blockhash, and Ledger does not return the lastValidBlockHeight that belongs to it. The height captured when you built the transaction can make confirmation report expiry too early.

The blockhash is left unchanged in two cases. A missing change is expected, not an error:

  • Another signature is already present (co-sign).
  • The transaction uses a durable nonce.

Sign and send a transaction

const signAndSendFeature = wallet.features['solana:signAndSendTransaction'] if (!signAndSendFeature) { throw new Error('Wallet does not support solana:signAndSendTransaction') } const [sent] = await signAndSendFeature.signAndSendTransaction({ account, transaction: serializedTransaction, chain: 'solana:mainnet', options: { skipPreflight: false }, }) // sent.signature — base58 transaction signature

Cleanup

Call the function returned by initializeLedgerProvider when your application unmounts. Cleanup removes the Ledger UI and unregisters any providers that were registered for your partner configuration.

const cleanup = initializeLedgerProvider({ /* options */ }) // Later: cleanup()

Troubleshooting

Ledger does not appear in the wallet list. Confirm initializeLedgerProvider ran in the browser before you query wallets, that you passed solanaBlockchainProviderFactory, and that Solana is enabled for your dAppIdentifier. On unsupported platforms (no Web HID / Web Bluetooth), initialization is a no-op and no Solana wallet is registered — see Requirements.

Connect returns no accounts. The user cancelled the flow, or no Solana account is available on the device for the requested cluster. Retry standard:connect after the user finishes onboarding in the Ledger UI.

Signing fails while another flow is open. Blocking requests (connect / sign) are handled one at a time. Wait for the current Ledger UI flow to finish before starting another.

See also

  • Get started — install family packages and pass blockchainProviderFactories
  • EVM — EIP-6963 discovery and EIP-1193 signing
  • Configuration — init options, including blockchainProviderFactories
  • API reference — initializeLedgerProvider and EVM EIP-1193 surface
  • Wallet Standard  — discovery protocol specification
Last updated on
Ledger
Copyright © Ledger SAS. All rights reserved. Ledger, Ledger Stax, Ledger Flex, Ledger Nano, Ledger Nano S, Ledger OS, Ledger Wallet, [LEDGER] (logo), [L] (logo) are trademarks owned by Ledger SAS.