Ledger Adapter
Ledger connects to the hardware device through WebHID or WebUSB. Transaction approval happens on the device; private keys do not leave it.
| Integration | Value |
|---|---|
| Package | @xrpl-wallet-kit/adapter-ledger |
| Adapter ID | ledger |
| Wallet type | Hardware wallet |
| Client registration | Included on XRPL; explicit opt-in on Xahau |
Installation
npm install @xrpl-wallet-kit/core @xrpl-wallet-kit/adapter-ledger xrpl@^4For the Client tab below, install @xrpl-wallet-kit/client instead. Use a browser with WebHID or WebUSB, connect/unlock the device and install the chain's device app.
Connect
import { createWalletKit } from "@xrpl-wallet-kit/client";
const kit = createWalletKit({ network: "mainnet", wallets: ["ledger"] });
const { manager } = kit;
const { account } = await kit.connect("ledger");import { WalletManager } from "@xrpl-wallet-kit/core";
import { createLedgerAdapter } from "@xrpl-wallet-kit/adapter-ledger";
const manager = new WalletManager({
network: "mainnet",
adapters: [createLedgerAdapter({ accountIndex: 0 })],
});
const { account } = await manager.connect("ledger");Start connection from a user action and select the device in the browser picker. With autoOpenApp, Kit requests the chain's app on the hardware and reads the address/public key once it is ready. It does not launch Ledger Live desktop or approve transactions for the user.
Networks
XRPL uses the XRP device app. Xahau uses the XAH device app and is opt-in: configure network: "xahau-mainnet" or "xahau-testnet", include "ledger" explicitly in wallets, and set a positive preflight.maxFeeDrops with the full client. Core/selective integrations must register the protocol and chain filter. See Xahau Configuration.
Connection success does not establish support for every transaction type or field. Keep the device app up to date and do not enable blind signing to bypass unsupported fields.
Capabilities
Operations below depend on the connected device app supporting the transaction being signed.
| Operation | Support |
|---|---|
| Connect | Yes |
| Disconnect | Yes; closes device/session resources |
| Session restore | No; fresh device connection required |
signTransaction() | Yes |
signAndSubmit() | Yes |
signMessage() | Not exposed |
signIn() | Not exposed by the direct Ledger adapter |
switchNetwork() | Not exposed; reconnect for the selected network |
| Payment / NFT transactions | Generic txJson; device app/version dependent |
| QR / deep link | No |
Options
These options apply to createLedgerAdapter(). Pass a configured adapter through the client's adapters option for custom device/account handling.
| Option | Type | Default | Description |
|---|---|---|---|
accountIndex | number | 0 | Account in 44'/144'/<index>'/0/0 |
derivationPath | string | 44'/144'/0'/0/0 | Full path; overrides accountIndex |
preferWebHID | boolean | true | Prefer WebHID, fall back to WebUSB |
timeout | number | 60000 | Device operation timeout in milliseconds |
autoOpenApp | boolean | true | Request the selected chain's app on the device |
deviceApp | "XRP" or "XAH" | Selected chain's app | Explicit device app override; must be compatible with the selected chain |
onDeviceState | (state: LedgerDeviceState) => void | Not set | Observe device states such as OPENING_APP and READY |
connectLedger | () => Promise<LedgerSession> | Built-in connection | Custom device/account connection flow |
supportedChains | WalletChainFilter | XRPL integration | Restrict chains in a protocol-aware integration |
icon | string | Embedded Ledger icon | Custom icon URL or data URI |
Transactions
These XRPL examples use manager and account from either Connect tab. Run each request separately from a user action. Replace recipient addresses and 64-character NFT/offer IDs before signing. Amounts are in drops: "1000000" is 1 XRP. Include DestinationTag when the recipient requires it.
maxFeeDrops is an application-selected ceiling, not the expected fee. Adjust the example limit for your application.
A returned hash or submitted status is not validated ledger confirmation. Check the transaction result before treating a payment or NFT transfer as complete. See WalletManager Events.
Payment
const result = await manager.signAndSubmit({
txJson: {
TransactionType: "Payment",
Account: account.address,
Destination: "<recipient_address>",
Amount: "1000000",
},
maxFeeDrops: "50000",
});
console.log(result.hash);const result = await manager.signTransaction({
txJson: {
TransactionType: "Payment",
Account: account.address,
Destination: "<recipient_address>",
Amount: "1000000",
},
maxFeeDrops: "50000",
});
console.log(result.txBlob); // Signed, not broadcastThe default path fills required transaction fields using the selected network, requests device approval and verifies the returned signature. XRPL single-sign requests include the canonical-signature flag for compatibility with the XRP app.
NFT Transactions
Each tab signs and submits one transaction. Wallet display and supported transaction types depend on the wallet/provider; generic signing is not a promise of support for every amendment.
await manager.signAndSubmit({
txJson: {
TransactionType: "NFTokenMint",
Account: account.address,
NFTokenTaxon: 0,
Flags: 8, // tfTransferable
},
maxFeeDrops: "50000",
});await manager.signAndSubmit({
txJson: {
TransactionType: "NFTokenCreateOffer",
Account: account.address,
NFTokenID: "<nft_id>",
Amount: "1000000",
Flags: 1, // tfSellNFToken
},
maxFeeDrops: "50000",
});await manager.signAndSubmit({
txJson: {
TransactionType: "NFTokenAcceptOffer",
Account: account.address, // Buyer's account
NFTokenSellOffer: "<sell_offer_id>",
},
maxFeeDrops: "50000",
});await manager.signAndSubmit({
txJson: {
TransactionType: "NFTokenCancelOffer",
Account: account.address, // Offer creator's account
NFTokenOffers: ["<offer_id>"],
},
maxFeeDrops: "50000",
});await manager.signAndSubmit({
txJson: {
TransactionType: "NFTokenBurn",
Account: account.address,
NFTokenID: "<nft_id>",
},
maxFeeDrops: "50000",
});Burning permanently destroys the NFT. For buy offers, brokered acceptance and additional fields, see NFT Marketplace. Xahau uses different transaction examples; see the Xahau guide.
Use a current device app that can display the requested NFT transaction. For sign-only NFT requests, pass the same txJson to manager.signTransaction().
Authentication
The direct Ledger adapter does not expose arbitrary signMessage() or native signIn(). Joey Wallet's Ledger-backed sign-in challenge belongs to the Joey Wallet adapter, not this adapter. Do not submit a Payment as a substitute login proof.
Sessions and Disconnect
Passive restore returns no session. After reload, reconnect and select the device again. manager.disconnect() closes the active transport and clears local session state.
Integration Notes
Device App Selection
autoOpenApp: false keeps manual app selection. If switching apps resets USB or requires browser permission again, open the requested XRP/XAH app on the hardware and select the device again. A custom connectLedger implementation owns its device-opening behavior. Only one transport should use the device at a time.
Multiple Accounts
import { LedgerAdapter } from "@xrpl-wallet-kit/adapter-ledger";
const ledger = new LedgerAdapter();
const accounts = await ledger.getAccounts(5);
console.log(accounts); // address, publicKey, path, indexUse accountIndex or derivationPath when creating the adapter for the selected account.
Multisigning
Sign-only multisign requests require a fully prepared source Account, Sequence/ticket, Fee, LastLedgerSequence and network fields, with SigningPubKey: "" and no Signers/TxnSignature. The default multisign path preserves those exact fields without autofill. Custom sessions must implement signMultisignTransaction. Use Core Multisign Coordination for submission; the adapter does not expose multisign submission. Generic signing does not imply Batch compatibility.
Troubleshooting
| Symptom | Action |
|---|---|
| Device is not detected | Check USB connection, unlock the device and use a browser with WebHID/WebUSB |
| Requested app does not open | Open XRP for XRPL or XAH for Xahau on the device, then reconnect |
| Device is busy | Close other applications or transports using it |
| Transaction fields/type are unsupported | Update the device app and confirm support; do not bypass with blind signing |
| Device approval times out | Approve on the device or configure a suitable timeout |