@usebutr/ledger
v2.0.1
Published
Ledger hardware-wallet adapter for butr (EVM + SVM + Sui + Bitcoin).
Maintainers
Readme
@usebutr/ledger
Ledger hardware-wallet adapter for butr (EVM + SVM + Sui + Bitcoin).
Part of butr, a multi-chain wallet discovery and connection-state library. Your application owns the picker UI and chain client.
Install
npm install @usebutr/core @usebutr/ledger @ledgerhq/hw-transport-webusb @ledgerhq/hw-app-eth zustandWebUSB needs a Chromium browser and a user gesture for device access. Each platform's Ledger app package is an optional peer loaded on first connect: @ledgerhq/hw-app-eth (EVM), @ledgerhq/hw-app-solana (SVM), @ledgerhq/hw-app-sui (Sui) or @ledgerhq/hw-app-btc (Bitcoin). Install the ones you use.
Usage
Factories resolve an adapter, so hand them to the manager through fromAdapters:
import { EVM_CHAINS, fromAdapters } from "@usebutr/core";
import type { WalletManagerConfig } from "@usebutr/core";
import { createLedgerAdapter } from "@usebutr/ledger";
import { autoDiscovery } from "@usebutr/wallets";
const config: WalletManagerConfig = {
sources: [
autoDiscovery(),
fromAdapters(
Promise.all([
createLedgerAdapter({ chainId: EVM_CHAINS.sepolia.id, id: "ledger-evm", platform: "evm" }),
createLedgerAdapter({ accountCount: 3, id: "ledger-svm", platform: "svm" }),
]),
),
],
};connect() opens the WebUSB prompt and reads accountCount addresses from the device, active first. chainId is a CAIP-2 id and defaults to the platform's mainnet. Ledger apps have no network switch, so there is no switchChain: build one adapter per chain.
A Ledger signs; your chain client owns RPC and broadcast. There is no sendTx, getBalance or subscribe. What each adapter defines:
- EVM:
signMessage(EIP-191r || s || v). - SVM:
signMessageandsignTransaction, which resolves the whole signed transaction (legacy or v0). - Sui:
signTransactionon BCS transaction bytes. - Bitcoin:
signMessage(BIP-137 compact signature) andsignTransactionon PSBT bytes.
Pass { account } to sign as another exposed account; an account the device did not expose rejects. For anything else, getSigner() hands back the device app:
const signer = await wallet.connector.getSigner();
if (signer.kind === "ledger-evm") {
const { r, s, v } = await signer.app.signTransaction("44'/60'/0'/0/0", unsignedTxHex);
}Documentation
License
MIT, copyright 2026 Pedro Filho.
