Ledger Setup
Sign transactions with a Ledger hardware wallet. The CLI integrates via
@ledgerhq/hw-app-eth over USB-HID — every write prompts on the
device before broadcast.
{
"profiles": {
"cold": {
"chain": "ethereum",
"rpcUrl": "https://eth.llamarpc.com",
"signer": { "type": "ledger", "derivationPath": "m/44'/60'/0'/0/0" }
}
}
}
- Install the optional deps
- Add a Ledger profile
- Use it
- What you’ll see during a write
- Linux: udev rules
- Cleanup
- Testing without a device
- Related
Install the optional deps
Ledger support is opt-in — the native HID stack adds a few hundred
KB of bindings most consumers don’t want. The SDK lists them under
optionalDependencies so a stock pnpm add @koed_jang/apyx-sdk does
not pull them in:
pnpm add @ledgerhq/hw-transport-node-hid @ledgerhq/hw-app-eth
Both packages must be present at runtime, dynamically loaded by the
CLI when it sees a signer.type = "ledger" profile. If they’re missing
the CLI fails with a clear hint:
apyx: Ledger support requires optional deps. Install them with:
pnpm add @ledgerhq/hw-transport-node-hid @ledgerhq/hw-app-eth
Add a Ledger profile
Edit the config file directly (the wizard supports it
via signer type: ledger):
{
"defaultProfile": "default",
"profiles": {
"default": {
"chain": "ethereum",
"rpcUrl": "https://eth.llamarpc.com",
"signer": { "type": "key", "keyPath": "~/.apyx/keys/default" }
},
"cold": {
"chain": "ethereum",
"rpcUrl": "https://eth.llamarpc.com",
"signer": {
"type": "ledger",
"derivationPath": "m/44'/60'/0'/0/0"
}
}
}
}
The derivationPath is whatever path Ledger Live shows for the
account you want — m/44'/60'/0'/0/N for the Nth account on a Legacy
wallet, m/44'/60'/N'/0/0 for the Nth on a default 24-word setup.
Use it
-
Plug the device in.
-
Unlock it.
-
Open the Ethereum app on the device.
-
Run the CLI:
$ apyx repl --profile cold @apyx-labs/sdk profile: cold (from /path/to/cwd/apyx.config.json) chain: ethereum (1) rpc: https://eth.llamarpc.com account: 0x1234…abcd
The address is fetched from the device — no key material ever leaves the device.
What you’ll see during a write
Every approve, deposit, redeem, etc. prompts on the device’s
screen. You confirm:
- the destination contract,
- the function and decoded arguments,
- the chainId,
- the gas budget.
Only after you press Approve on the hardware does the CLI hand the tx to the RPC. There is no way to “auto-sign” — that’s the point.
apyx> await apyx.apyUSD.deposit({ assets: 1_000_000n, receiver: account.address })
[ledger]: review tx on device…
[ledger]: signed
{ hash: '0xbc9e…1d44', wait: [Function: wait] }
Reject on the device and the CLI surfaces a User refused on Ledger
error.
Linux: udev rules
Linux needs a one-time udev rule so non-root processes can talk to the Ledger HID interface. The Ledger team publishes the canonical script:
wget -q -O - https://raw.githubusercontent.com/LedgerHQ/udev-rules/master/add_udev_rules.sh \
| sudo bash
Re-plug the device after the rules land. Without this you’ll see
Cannot open device when the CLI tries to enumerate USB.
macOS and Windows do not need extra rules — the bundled drivers work out of the box.
Cleanup
The HID transport is closed when:
- The REPL exits via
.exit,Ctrl-D, orCtrl-C. - A non-interactive subcommand finishes (success or error).
If the device starts behaving oddly (DisconnectedDeviceDuringOperation,
HID handle stuck), unplug + re-plug to reset the kernel-side state.
Testing without a device
The Ledger code path has an e2e spec that exercises the production
dispatch using a mock factory backed by a real private key —
no physical hardware required. See
test/e2e/cli/ledger.e2e.spec.ts. That same factory hook
lets you swap a mock in your own test harness; pass ledgerFactory
to resolveSigner.
Related
- Configuration —
signer.type = "ledger"schema. - REPL — what the banner shows for a Ledger profile.
- Ledger signing recipe — full setup walkthrough end-to-end.