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 yourshareStore 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
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 }.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: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
Showdisplay.summary and display.details next to the device prompt, for example “Send 0.1 ETH to 0xab…cd”.
confirmOnDeviceis 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 indisplay.summarywith the one on the device.blindSigningis present when the device cannot show the request in full. Showwarning, andhint(for example “turn on Blind signing in the Ethereum app”), before the user approves.
Method map
Calling the password method on a hardware wallet throwsHardwareSignerRequiredError 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
- Ledger
- 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
HardwareVendorUnsupportedErrorbefore the device is asked. Creating a Vault with such an owner is refused by the server asVAULT_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.