All the code examples are written with the assumption trezor.js is imported to the trezor
variable. So, for example
var trezor = require('trezor.js'); // if you are using the npm version
DeviceList
is class that lists devices and emits events on any change.
var list = new trezor.DeviceList();
or
var list = new trezor.DeviceList(options);
DeviceList
constructor takes care of initialization of transport layers. options
is an optional object of key/value pair for configuring. Possible values are:
configUrl
: if you want to use your own URL for config fileconfig
: if you want to use your own config file. The value should be a hex string.transport
: if you want to provide your own Transport object (fromtrezor-link
module)
The config should be available either on github, on Amazon AWS, or on wallet.trezor.io. The AWS URL is hardcoded and is used by default, so you don't have to worry about it if you don't want to.
DeviceList
emits following events:
event | parameters | description |
---|---|---|
error |
e: Error | Error on either initialization of DeviceList, or any other error. If deviceList.transport is null, it means transport layer was not initialized and none of the layers are probably installed. You should probably offer user to install either extension or bridge in that case. |
transport |
t: Transport | When transport is successfully initialized. |
connectUnacquired |
u: UnacquiredDevice | When unacquired device is connected. |
connect |
d: Device, previous: UnacquiredDevice | When device is properly acquired. If it was previously being used by someone else, previous is the unacquiredDevice , otherwise it's null. |
disconnect |
d: Device | When a device is disconnected. |
disconnectUnacquired |
u: UnacquiredDevice | When an unacquired device is disconnected. |
DeviceList
has following properties:
deviceList.devices
- plain object, with values being the devices available and keys their pathsdeviceList.unacquiredDevices
- plain object, with values being unacquired devices and keys their pathsdeviceList.transport
- object, representing transport layer. You might use it in the following way:- if it's null, it means no transport layer was yet set up -- or the setup failed (nothing installed).
- you can look at the
deviceList.transport.version
property, you can read the version as a string
DeviceList
has following methods:
method | parameters | return type | description |
---|---|---|---|
acquireFirstDevice |
rejectOnEmpty ?boolean | Promise | Easiest way of acquiring and getting the first device. First parameter is optional - if true, rejects on empty list. |
See multitasking for info on acquiring.
If you don't want to deal with acquired/unacquired/... devices, and you "just" want to use the first TREZOR, you can use the function deviceList.acquireFirstDevice
, which will take first device currently connected, acquire it and never release it, and returns the Device and the Session objects. Other applications will then have to steal it to use it.
This class represents a device that cannot be acquired; see multitasking.
UnacquiredDevice
emits following events:
event | parameters | description |
---|---|---|
connect |
d: Device | This unacquired device was finally acquired and is now a Device. |
disconnect |
This unacquired device was disconnected before it could be acquired. |
UnacquiredDevice
has following methods:
method | parameters | return type | description |
---|---|---|---|
steal |
Promise | Stops the action blocking acquiring and returns promise, resolving with the new Device. See multitasking. |
UnacquiredDevice
has following properties:
unacquiredDevice.path
- path uniquely defines device between more connected devices. It might or might not be unique over time; on some platform it changes, on others a given USB port always returns the same path.
Device represents the actual TREZOR device. The most important method is run
(or waitForSessionAndRun
), which works in a following way:
device.run(function callback(session) {
return session.signMessage([1,2], "message", "bitcoin");
});
device.run
will first do acquire
(see multitasking), then runs the callback function, and waits for the returned promise to resolve or reject. In both cases, it calls release
.
It is important that the callback returns promise and the promise doesn't "hang", so the release
is correctly called.
Note that the lenght of the action is not tied to the actual device actions. You can do more complex actions - for example, a complete account discovery - before the session is released.
Session
object itself is described in the next section.
Device
emits following events:
event | parameters | description |
---|---|---|
changedSessions |
isUsed: boolean isUsedHere: boolean |
Some change in sessions happened. Either release or acquire. isUsed is true if the device is used (meaning session was acquired), either with this application or elsewhere. isUsedHere is true if the device is used in this application. |
disconnect |
This device was disconnected. | |
error |
e :Error | Error Can be anything from bad response to user pressing Cancel on device |
send |
type: string, msg: Object | (low level) Message is being sent to Trezor with a given type |
receive |
type: string, msg: Object | (low level) Message is being received from Trezor with a given type |
button |
type: string | User needs to confirm something on the device |
pin |
type: string callback: (error: Error, pin: string)=>void |
The device asks for PIN. You can either send PIN or send Error back to the device. You need to do one for the flow to continue. PIN is randomized depending on device display; top right is "9", bottom left is "1". "123" means the bottom row on the display. |
passphrase |
callback: (error: Error, passphrase: string)=>void | The device asks for passhprase. You can either send passphrase or send Error back to the device. You need to do one for the flow to continue. |
word |
callback: (error: Error, word: string)=>void | The device asks for word (on device recovery). You can either send word or send Error back to the device. You need to do one for the flow to continue. |
Device
has following methods:
method | parameters | return type | description |
---|---|---|---|
run | callback: (Session)=>Promise<X> [options: Object] |
Promise<X> | Runs an action on acquired session. Returns promise, that resolves with the same value as the callback result. With no options, the promise is rejected when there is another action running (see multitasking). The optional option argument changes the behavior. If options.waiting is true, we wait for the current action to finish, if there is some. If options.aggressive is true, the current action is stopped. |
waitForSessionAndRun | callback: (Session)=>Promise<X> | Promise<X> | Equivalent to running run with options.waiting == true . |
steal | Promise | See multitasking. Equivalent to running run with options.aggressive == true and an empty function. |
|
isBootloader | boolean | Is device in a bootloader mode? | |
isInitialized | boolean | Is device initialized? (Meaning, does it have seed loaded) | |
getVersion | string | Get current firmware version | |
atLeast | version: string | boolean | Compares this firmware version with another version |
isUsed | boolean | Is anyone using this device? (Me or other app.) See multitasking. | |
isUsedHere | boolean | Am I anyone using this device? | |
isUsedElsewhere | boolean | Is anyone else using this device? |
Device
has following properties:
device.path
- path uniquely defines device between more connected devices. It might or might not be unique over time; on some platform it changes, on others a given USB port always returns the same path.device.features
- basic information about a devicedevice.features.label
- name of a devicedevice.features.pin_protection
- is PIN protection turned on?device.features.passphrase_protection
- is PIN protection turned on?device.features.device_id
- a unique ID (is restarted only on device wipe)
Session
object represents one running session, between acquiring and releasing. All the "actual" calls are done here.
Session
emits following events (note that they are also re-emitted by Device
class):
event | parameters | description |
---|---|---|
error |
e :Error | Error Can be anything from bad response to user pressing Cancel on device |
send |
type: string, msg: Object | (low level) Message is being sent to Trezor with a given type |
receive |
type: string, msg: Object | (low level) Message is being received from Trezor with a given type |
button |
type: string | User needs to confirm something on the device |
pin |
type: string callback: (error: Error, pin: string)=>void |
The device asks for PIN. You can either send PIN or send Error back to the device. You need to do one for the flow to continue. PIN is randomized depending on device display; top right is "9", bottom left is "1". "123" means the bottom row on the display. |
passphrase |
callback: (error: Error, passphrase: string)=>void | The device asks for passhprase. You can either send passphrase or send Error back to the device. You need to do one for the flow to continue. |
word |
callback: (error: Error, word: string)=>void | The device asks for word (on device recovery). You can either send word or send Error back to the device. You need to do one for the flow to continue. |
Session
has following methods:
method | parameters | return type | description |
---|---|---|---|
typedCall | type: string resType: string message: Object |
Promise<Response> | More low-level API. Sends message and returns message of given type. Note that while this is low-level, it still doesn't return "intermediary" messages like asking for PIN and passhprase; those are emitted as events by Device and Session. |
getEntropy | size: number | Promise<Response <{ bytes: string }>>> |
random data |
getAddress | path: Array<number> coin: string display: boolean |
Promise<Response <{ address: string }>>> |
Gets address with a given path. Coin is the name of coin ("bitcoin", "testnet", "litecoin",...) if display is true, the address is displayed on TREZOR and user has to confirm. |
ethereumGetAddress | address_n: Array<number> display: ?boolean |
Promise<Response <{ address: string, path: Array<number> }>> |
Gets Ethereum address from a given path. if display is true, the address is displayed on TREZOR and user has to confirm. |
verifyAddress | path: Array<number> refAddress: string coin: string |
Promise<boolean> | Gets address with the given path, displays it on display and compares to the refAddress .Note: promise doesn't reject on different result, but resolves with false. It rejects on user not confirming on device. |
getHDNode | path: Array<number> coin: string |
Promise<HDNode> | Returns bitcoin.js HDNode with the given path. (No confirmation needed; it's public node.) |
wipeDevice | Promise | Wipes the device (after user confirms). | |
resetDevice | settings: {strength : number,passphrase_protection :boolean, pin_protection : boolean,label : stringlanguage : stringu2f_counter : numberskip_backup : boolean } |
Promise | Sets a new device. It has to be wiped first. It resolves after user confirms all the words on display. |
loadDevice | settings: {strength : number,passphrase_protection :boolean, pin_protection : boolean,label : string,mnemonic : string,node HDNode,payload : string }network: string |
Promise | Loads a device with specific mnemonic and/or HD Node and/or xprv. Do not use this if you don't need to; use recoverDevice Either mnemonic, node or payload have to be present. HDNode is an internal TREZOR structure, not bitcoin.js HD Node. |
recoverDevice | settings: {word_count : number,passphrase_protection :boolean, pin_protection : boolean,label : string,dry_run : boolean } |
Promise | Recovers device. If dry_run is enabled performs a dry run of the recovery process and displays result without resetting the device. |
clearSession | Promise | Clears device "session". "Session" has nothing to do with Session object or multitasking sessions; clearSession just clears currently remembered PIN and passphrase from device. |
|
updateFirmware | payload: string (hex) | Promise | Erases current firmware and loads a new one. |
signMessage | path: Array<number> message: string coin: string segwit: boolean |
Promise< MessageResponse< address: string, signature: string >> |
Signs a message. Note: you have to pass the message encoded as HEX Coin can be specified as string 'Bitcoin' or 'Litecoin': source |
signEthMessage | address_n: Array<number> message: string |
Promise< MessageResponse< address: string, signature: string >> |
Signs a message using an Ethereum private key. Note: you have to pass the message encoded as HEX |
verifyMessage | address: string signature: string message: string coin: string |
Promise | Verifies a signed message. Rejects on wrong signature. |
verifyEthMessage | address: string signature: string message: string |
Promise | Verifies a signed Ethereum message. Rejects on wrong signature. |
signIdentity | identity:{ proto:?string, user:?string, host:?string, port:?string, path:?string, index:?number;} challengeHidden: string challengeVisual: string |
Promise< MessageResponse< address: string, public_key: string, signature: string>> |
Signs identity TODO: write docs here |
cipherKeyValue | path: Array<number> key: string value: string |
Buffer encrypt: boolean ask_on_encrypt: boolean ask_on_decrypt: boolean iv: [iv Buffer] |
Promise< Response< {value: string }>> |
changeLabel | label: string | Promise | Changes label. |
togglePassphrase | enable: boolean | Promise | Turns the passphrase on/off. |
changeHomescreen | picture: string (hex) | Promise | changes the homescreen. |
signBjsTx | info: TxInfo refTx: Array<Transaction> nodes: Array<HDNode> coinName: string network: bitcoin.Network |
Promise<Transaction> | Signs transaction after asking user on the device. The name is signBjsTx for backwards compatibility, meaning "sign Bitcoin.js transaction", since it accepts and returns blockchain.js data structures.The parameters are explained under this table. |
signEthTx | address_n: Array<number> nonce: string gas_price: string gas_limit: string to: string value: string data: ?string chain_id: ?number |
Promise<EthereumSignature> | Signs Ethereum transaction after asking user on the device. |
backupDevice | Promise | Initiates the display of recovery mnemonic, works only for devices with needs_backup true , sets needs_backup to false |
Response<X>
is an object with type {type: string, message: X}
.
TxInfo is an object with:
- inputs: Array<InputInfo>
- outputs: Array<OutputInfo>
InputInfo is an object with:
- hash: Buffer
- note that, as in Bitcoin.js structures, the hash is reversed from the transaction IDs!
- index: number
- path: Array | null
- present, if the input is from TREZOR
OutputInfo is an object with:
- value: number
- path: Array | null
- address: string | null
- either path or address must be present
refTxs are bitcoin.js transations, that are referenced in the inputs. You need to put all the transactions which are referenced in any of the inputs!
nodes is an array of Bitcoin.js HDNodes for addresses, with indexes being the index of the nodes. So, most often, [externalChainHDNode, changeChainHDNode]
.
coinName is "bitcoin", "litecoin", "dash", ...
network is network for Bitcoin.js. If it's not present, trezor.js tries to find it by coinName in bitcoin.js table.
More applications can, in general, work with the same TREZOR; however, they cannot perform actions with the device at the same time.
We use a "cooperative multitasking". Trezor.js does an "acquire" command to transport layer before any action, and after the action calls a "release". This is done transparently by trezor.js, so the app doesn't have to call the commands explicitly; however, it's important to keep the multitasking model in mind to understand various call results.
This image illustrates the flow. The left-right axis represents time; the two lines represent two applications, that both use trezor.js.
Connected device needs to do the acquire-release flow - before the Device
object is even created (!) - in order to read some basic information about the device. Only then, the device
event is being emitted by DeviceList
.
This image illustrates a case, when there is no conflict between applications.
The first application can be in the middle of a long action (for example, a long transaction signing, or waiting for confirmation on a device) when the second application starts. In that case, Device
object cannot be immediately created, since it cannot run acquire.
The Device
object creation has to wait until the action is finished. DeviceList
first emits unacquiredDevice
, and only after the first application action finishes, the device
event is emitted. Note that this happens automatically.
The first application does not have to finish the action (it might be stuck - the tab could be frozen, or perhaps there is a bug in the application, or perhaps user forgot about the tab). You can explicitly call steal()
on the UnacquiredDevice
object, which will stop the first application action and automatically acquire the device.
Note that you should only do steal
when user explicitly asks for it - you don't want to stop other application from finishing its task, if it's not necessary.
Often, two acquires happen at the same time.
If you have two application open at the same time and one device is connected, both will try to call acquire
at the same time.
Only one of those two acquires immediately succeed. The second application will first get unacquiredDevice
event, and very quickly afterwards device
event.
If you try to use run()
on Device
while other application has acquired the device, the call will immediately fail and the run
will return rejected promise, while the first action will continue running.
You can tell the status by calling isUsedElsewhere()
, which returns boolean, see below.
Note that not even single application can run two actions at the same time. If you try to run()
two actions at the same time, the second will fail. You can use isUsedHere()
to detect the status of your application using the device.
If you use waitForSessionAndRun()
on Device
while other application has acquired the device, the call will wait until the other application called release
and only then will happen.
Note that if the first application is frozen, the second application never gets to use the device.
Note that waitForSessionAndRun()
also works within a single application.
Instead of waiting for session, you can actively stop the other action by using steal()
.
Stealing will not actually "give you" the session; however, it will stop the first action, allowing you to use run()
immediately.
Depending on how much complex do you want to make your application, you might use some of the following strategies.
If you want a simple application, you might want to either ignore unacquiredDevice
events entirely, or, conversely, just immediately call steal()
at the UnacquiredDevice
objects. If you want a more robust application, you might want to offer the user the possibility to call steal()
if the device stays unacquired for too much of a long time.
Similarly, if you want a simple application, you might want to either just call waitForSessionAndRun
or, conversely, just call steal
whenever the device is used and you want to run some action. If you want a more robust application, you might want to detect when the device is being used elsewhere and offer the user the possibility to call steal()
.