Skip to main content
The hardware wallet is connected in the Trustodian app, never through the SDK. The account holder sets it up as the account root in the web app, the extension or the mobile app. Your integration only works with an account that already has it. Requires SDK v3.3.0 or newer.

What a hardware wallet account is

An account root is either a recovery phrase or a hardware wallet (Ledger or Trezor) that holds the seed. With a hardware root, every wallet derives on the device and the private key never leaves it. The SDK holds no seed, asks for no wallet password, and writes nothing to your shareStore for these wallets. A hardware root is for individual accounts only. Organization vaults take no hardware wallet owners. A hardware account has no recovery phrase, so hasRecoveryPhrase and recoveryPhraseVerified stay false.

Detecting it

Each wallet carries seedRootType: 'phrase' | 'hardware' | null. 'hardware' means the key lives on the device and is used only through the prepareHardware… methods below.

How it works

The SDK never talks to the device. It plans the action, your frontend runs it on the device, and the SDK verifies the answer and finishes the job.
1

Prepare

Call a prepareHardware… method. It returns a HardwareRequest, plain JSON.
2

Store and forward

Keep the whole request on your server, for example by request.id. Send your frontend only request.calls and request.display.
3

Run on the device

The frontend runs the calls in order in one device session and posts back the raw answer of each call, keyed by call id, or an error.
4

Submit

Call sdk.submitHardwareStep(request, result). You get { status: 'next', request } (repeat from step 2 with the new request) or { status: 'done', result }.
Before it broadcasts or posts anything, the SDK checks every device answer against the account’s own key and against exactly the transaction it prepared. A device with a different seed or passphrase ends in HardwareSignatureMismatchError and nothing is sent. The request carries an integrity code (mac) tied to your API key, so a request changed anywhere is refused. Each step has an expiresAt; after it submitHardwareStep throws HardwareRequestExpiredError and you prepare again. Adding an account and reading Vault keys expire 15 minutes after prepare.

Who does what

Example

Backend, a send:
Frontend, a sketch for Trezor (a Ledger version runs call.method through the Ledger signers instead):
HardwareRequest, HardwareDeviceCall, HardwareDeviceResult and HardwareDeviceErrorCode are exported from the package. HardwareDeviceResult.step must be the step of the request you are answering. HardwareActionResult depends on the action:

Showing the request to the user

Show display.summary and display.details next to the device prompt, for example “Send 0.1 ETH to 0xab…cd”.
  • confirmOnDevice is true on a call when the user must check and approve on the device screen. For the second step of adding an account, the user compares the address in display.summary with the one on the device.
  • blindSigning is present when the device cannot show the request in full. Show warning, and hint (for example “turn on Blind signing in the Ethereum app”), before the user approves.

Method map

Calling the password method on a hardware wallet throws HardwareSignerRequiredError before anything is authorized, fetched or signed. Its prepareMethod field names the method to use. Every prepareHardware… input takes an optional vendor. The default is the vendor that last connected the root, and a device of the other vendor holding the same seed also works. Solana Vault activation also takes userRef and walletPassword, because the Vault activation key is under the wallet password. Under strict authentication, executing or activating a Vault needs walletPassword on the prepare and again on submitHardwareStep(request, result, { walletPassword }). Nothing else in the hardware flow needs it. establishRecoveryPhrase and import for the personal phrase throw HardwareAccountRefusedError (SEED_ROOT_IS_HARDWARE) on a hardware root.

Ledger and Trezor

Browser. WebHID, so Chrome or Edge on desktop. The page must be a secure context: HTTPS, or localhost.Calls. method names a call such as eth.signTransaction, btc.signPsbt or ada.signTransaction, and app is the Ledger app to open first. Bytes are lowercase hex both ways. Paths are m/… strings: drop the m/ for the Device Management Kit signers, and convert to number[] for the Cardano library. Some calls name a contextModule (LedgerContextModule: evm-token-only, evm-basic-typed-data, sol-offline, tron-none).The LedgerMethod type exported from the package is the catalog: every method with its params and expected response.Libraries (tested versions):Apps on the device. Bitcoin (Bitcoin Test on testnet), Ethereum, Solana, Tron, XRP and Cardano ADA. The first account on any network also needs the Bitcoin app, because the root is read there.Opening an app asks the user to confirm on the device. Refusing there is app-not-open, not rejected. Ledger runs every call of a step with one click.See the Ledger device interaction docs.

Device errors

When the device fails, your frontend sends { requestId, step, error: { code, message } } instead of responses. The SDK throws HardwareDeviceError with that deviceCode. The request stays valid, so the user can retry the same step until it expires.

Vendor limits

  • Trezor cannot sign Vault transactions on TRON or XRP. The SDK throws HardwareVendorUnsupportedError before the device is asked. Creating a Vault with such an owner is refused by the server as VAULT_SIGNER_HARDWARE_UNSUPPORTED.
  • Trezor signs only plain XRP payments: no issued tokens and no trust lines.
  • Some requests cannot be shown in full on the device, so the SDK sets display.blindSigning. On Ledger these are EVM token transfers, Safe/Vault typed data and contract calls, and SOL/TRON token transfers and contract calls. On Trezor they are contract calls. BTC, ADA and XRP are always shown in full.
Hardware wallets are available per vendor on each platform. If getAppConfig() reports a vendor as disabled, adding accounts with it throws HardwareAccountRefusedError (HARDWARE_WALLET_DISABLED).

Platform availability

config.hardwareAvailability is the platform-wide switch for hardware wallets. Requires SDK v3.3.1 or newer. getAppConfig() keeps its result for 60 seconds, so a change made by the platform reaches a running TrustodianSDK instance within a minute. A failed request is never kept: the last good result is returned, or the defaults (hardwareAvailability: 'closed') when there is none, and the next call asks again. A missing or unknown value is read as closed, which blocks nothing. submit is not checked by the SDK. For Vault actions and adding accounts the server refuses a submitted request with the same error code; personal sends are refused only by prepareHardware…. The per-vendor flags above are separate. A switched-off vendor still refuses adding accounts with HARDWARE_WALLET_DISABLED.

Errors

All of them are listed with the rest in Hardware wallet errors.
Last modified on October 27, 2026