//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
//! SPDX-License-Identifier: GPL-3.0-or-later
+//@ts-expect-error
import { default as TransportBLE } from '@ledgerhq/hw-transport-web-ble'
+//@ts-expect-error
import { default as TransportHID } from '@ledgerhq/hw-transport-webhid'
+//@ts-expect-error
import { default as TransportUSB } from '@ledgerhq/hw-transport-webusb'
import { LedgerStatus, LedgerAccountResponse, LedgerResponse, LedgerSignResponse, LedgerVersionResponse } from '#types'
import { Account } from './account'
paramUnused: 0x00
})
static #DERIVATION_PATH: Uint8Array = new Uint8Array([
- Ledger.#ADPU_CODES.bip32DerivationLevel,
+ this.#ADPU_CODES.bip32DerivationLevel,
...dec.toBytes(BIP44_PURPOSE + HARDENED_OFFSET, 4),
...dec.toBytes(BIP44_COIN_NANO + HARDENED_OFFSET, 4)
])
throw new TypeError('Invalid account index')
}
const account = dec.toBytes(index + HARDENED_OFFSET, 4)
- const data = new Uint8Array([...Ledger.#DERIVATION_PATH, ...account])
+ const data = new Uint8Array([...this.#DERIVATION_PATH, ...account])
- const transport = await Ledger.#transport.create(Ledger.#openTimeout, Ledger.#listenTimeout)
+ const transport = await this.#transport.create(this.#openTimeout, this.#listenTimeout)
const response = await transport
- .send(Ledger.#ADPU_CODES.class, Ledger.#ADPU_CODES.account, show ? 1 : 0, Ledger.#ADPU_CODES.paramUnused, data as Buffer)
- .catch(err => dec.toBytes(err.statusCode)) as Uint8Array
+ .send(this.#ADPU_CODES.class, this.#ADPU_CODES.account, show ? 1 : 0, this.#ADPU_CODES.paramUnused, data as Buffer)
+ .catch((err: any) => dec.toBytes(err.statusCode)) as Uint8Array
await transport.close()
const statusCode = bytes.toDec(response.slice(-2)) as number
- const status = Ledger.#STATUS_CODES[statusCode] ?? 'UNKNOWN_ERROR'
+ const status = this.#STATUS_CODES[statusCode] ?? 'UNKNOWN_ERROR'
if (status !== 'OK') {
return { status, publicKey: null, address: null }
}
*/
static async connect (api?: 'hid' | 'ble' | 'usb'): Promise<LedgerStatus> {
if (api !== undefined) {
- if (api === 'hid' && Ledger.#transport !== TransportHID) {
- Ledger.#transport = TransportHID
+ if (api === 'hid' && this.#transport !== TransportHID) {
+ this.#transport = TransportHID
}
- if (api === 'ble' && Ledger.#transport !== TransportBLE) {
- Ledger.#transport = TransportBLE
+ if (api === 'ble' && this.#transport !== TransportBLE) {
+ this.#transport = TransportBLE
}
- if (api === 'usb' && Ledger.#transport !== TransportUSB) {
- Ledger.#transport = TransportUSB
+ if (api === 'usb' && this.#transport !== TransportUSB) {
+ this.#transport = TransportUSB
}
}
try {
throw new RangeError(`Index outside allowed range 0-${HARDENED_OFFSET}`, { cause: index })
}
if (frontier != null) {
- const { status } = await Ledger.#cacheBlock(index, frontier)
+ const { status } = await this.#cacheBlock(index, frontier)
if (status !== 'OK') {
throw new Error('Failed to cache frontier block in ledger', { cause: status })
}
}
console.log('Waiting for signature confirmation on Ledger device...')
- const { status, signature, hash } = await Ledger.#signBlock(index, block)
+ const { status, signature, hash } = await this.#signBlock(index, block)
if (status !== 'OK') {
throw new Error('Signing with ledger failed', { cause: status })
}
.sign(testWallet, 0)
const testSignature = testSendBlock.signature
try {
- const ledgerSignature = await Ledger.sign(0, testSendBlock, testOpenBlock)
+ const ledgerSignature = await this.sign(0, testSendBlock, testOpenBlock)
return ledgerSignature === testSignature
} catch (err) {
throw new Error('Failed to verify wallet', { cause: err })
const representative = hex.toBytes(block.representative.publicKey, 32)
const balance = hex.toBytes(block.balance.toString(16), 16)
const signature = hex.toBytes(block.signature, 64)
- const data = new Uint8Array([Ledger.#ADPU_CODES.bip32DerivationLevel, ...purpose, ...coin, ...account, ...previous, ...link, ...representative, ...balance, ...signature])
+ const data = new Uint8Array([this.#ADPU_CODES.bip32DerivationLevel, ...purpose, ...coin, ...account, ...previous, ...link, ...representative, ...balance, ...signature])
- const transport = await Ledger.#transport.create(Ledger.#openTimeout, Ledger.#listenTimeout)
+ const transport = await this.#transport.create(this.#openTimeout, this.#listenTimeout)
const response = await transport
- .send(Ledger.#ADPU_CODES.class, Ledger.#ADPU_CODES.cacheBlock, Ledger.#ADPU_CODES.paramUnused, Ledger.#ADPU_CODES.paramUnused, data as Buffer)
- .then(res => bytes.toDec(res))
- .catch(err => err.statusCode) as number
+ .send(this.#ADPU_CODES.class, this.#ADPU_CODES.cacheBlock, this.#ADPU_CODES.paramUnused, this.#ADPU_CODES.paramUnused, data as Buffer)
+ .then((res: Buffer) => bytes.toDec(res))
+ .catch((err: any) => err.statusCode) as number
await transport.close()
- return { status: Ledger.#STATUS_CODES[response] }
+ return { status: this.#STATUS_CODES[response] }
}
/**
* @returns Status of command
*/
static async #close (): Promise<LedgerResponse> {
- const transport = await Ledger.#transport.create(Ledger.#openTimeout, Ledger.#listenTimeout)
+ const transport = await this.#transport.create(this.#openTimeout, this.#listenTimeout)
const response = await transport
- .send(0xb0, 0xa7, Ledger.#ADPU_CODES.paramUnused, Ledger.#ADPU_CODES.paramUnused)
- .then(res => bytes.toDec(res))
- .catch(err => err.statusCode) as number
- return new Promise(r => setTimeout(r, 1000, { status: Ledger.#STATUS_CODES[response] }))
+ .send(0xb0, 0xa7, this.#ADPU_CODES.paramUnused, this.#ADPU_CODES.paramUnused)
+ .then((res: Buffer) => bytes.toDec(res))
+ .catch((err: any) => err.statusCode) as number
+ return new Promise(r => setTimeout(r, 1000, { status: this.#STATUS_CODES[response] }))
}
static #onConnectHid = async (e: HIDConnectionEvent): Promise<void> => {
console.log(e)
- if (e.device?.vendorId === ledgerUSBVendorId) {
+ if (e.device?.vendorId === this.UsbVendorId) {
console.log('Ledger connected via HID')
await this.connect()
this.#polling = setInterval(this.connect, 1000)
static #onDisconnectHid = async (e: HIDConnectionEvent): Promise<void> => {
console.log(e)
- if (e.device?.vendorId === ledgerUSBVendorId) {
+ if (e.device?.vendorId === this.UsbVendorId) {
console.log('Ledger disconnected via HID')
clearInterval(this.#polling)
await this.connect()
static #onConnectUsb = async (e: USBConnectionEvent): Promise<void> => {
console.log(e)
- if (e.device?.vendorId === ledgerUSBVendorId) {
+ if (e.device?.vendorId === this.UsbVendorId) {
console.log('Ledger connected via USB')
await this.connect()
this.#polling = setInterval(this.connect, 1000)
static #onDisconnectUsb = async (e: USBConnectionEvent): Promise<void> => {
console.log(e)
- if (e.device?.vendorId === ledgerUSBVendorId) {
+ if (e.device?.vendorId === this.UsbVendorId) {
console.log('Ledger disconnected via USB')
clearInterval(this.#polling)
await this.connect()
*/
static async #open (): Promise<LedgerResponse> {
const name = new TextEncoder().encode('Nano')
- const transport = await Ledger.#transport.create(Ledger.#openTimeout, Ledger.#listenTimeout)
+ const transport = await this.#transport.create(this.#openTimeout, this.#listenTimeout)
const response = await transport
- .send(0xe0, 0xd8, Ledger.#ADPU_CODES.paramUnused, Ledger.#ADPU_CODES.paramUnused, name as Buffer)
- .then(res => bytes.toDec(res))
- .catch(err => err.statusCode) as number
- return new Promise(r => setTimeout(r, 1000, { status: Ledger.#STATUS_CODES[response] }))
+ .send(0xe0, 0xd8, this.#ADPU_CODES.paramUnused, this.#ADPU_CODES.paramUnused, name as Buffer)
+ .then((res: Buffer) => bytes.toDec(res))
+ .catch((err: any) => err.statusCode) as number
+ return new Promise(r => setTimeout(r, 1000, { status: this.#STATUS_CODES[response] }))
}
/**
const link = block.link
const representative = hex.toBytes(block.representative.publicKey, 32)
const balance = hex.toBytes(BigInt(block.balance).toString(16), 16)
- const data = new Uint8Array([...Ledger.#DERIVATION_PATH, ...account, ...previous, ...link, ...representative, ...balance])
+ const data = new Uint8Array([...this.#DERIVATION_PATH, ...account, ...previous, ...link, ...representative, ...balance])
- const transport = await Ledger.#transport.create(Ledger.#openTimeout, Ledger.#listenTimeout)
+ const transport = await this.#transport.create(this.#openTimeout, this.#listenTimeout)
const response = await transport
- .send(Ledger.#ADPU_CODES.class, Ledger.#ADPU_CODES.signBlock, Ledger.#ADPU_CODES.paramUnused, Ledger.#ADPU_CODES.paramUnused, data as Buffer)
- .catch(err => dec.toBytes(err.statusCode)) as Uint8Array
+ .send(this.#ADPU_CODES.class, this.#ADPU_CODES.signBlock, this.#ADPU_CODES.paramUnused, this.#ADPU_CODES.paramUnused, data as Buffer)
+ .catch((err: any) => dec.toBytes(err.statusCode)) as Uint8Array
await transport.close()
const statusCode = bytes.toDec(response.slice(-2)) as number
- const status = Ledger.#STATUS_CODES[statusCode] ?? 'UNKNOWN_ERROR'
+ const status = this.#STATUS_CODES[statusCode] ?? 'UNKNOWN_ERROR'
if (response.byteLength === 2) {
return { status, signature: null }
}
const derivationAccount = dec.toBytes(index + HARDENED_OFFSET, 4)
- const data = new Uint8Array([...Ledger.#DERIVATION_PATH, ...derivationAccount, ...nonce])
+ const data = new Uint8Array([...this.#DERIVATION_PATH, ...derivationAccount, ...nonce])
- const transport = await Ledger.#transport.create(Ledger.#openTimeout, Ledger.#listenTimeout)
+ const transport = await this.#transport.create(this.#openTimeout, this.#listenTimeout)
const response = await transport
- .send(Ledger.#ADPU_CODES.class, Ledger.#ADPU_CODES.signNonce, Ledger.#ADPU_CODES.paramUnused, Ledger.#ADPU_CODES.paramUnused, data as Buffer)
- .catch(err => dec.toBytes(err.statusCode)) as Uint8Array
+ .send(this.#ADPU_CODES.class, this.#ADPU_CODES.signNonce, this.#ADPU_CODES.paramUnused, this.#ADPU_CODES.paramUnused, data as Buffer)
+ .catch((err: any) => dec.toBytes(err.statusCode)) as Uint8Array
await transport.close()
const statusCode = bytes.toDec(response.slice(-2)) as number
- const status = Ledger.#STATUS_CODES[statusCode] ?? 'UNKNOWN_ERROR'
+ const status = this.#STATUS_CODES[statusCode] ?? 'UNKNOWN_ERROR'
if (response.byteLength === 2) {
return { status, signature: null }
* @returns Status, process name, and version
*/
static async #version (): Promise<LedgerVersionResponse> {
- const transport = await Ledger.#transport.create(Ledger.#openTimeout, Ledger.#listenTimeout)
+ const transport = await this.#transport.create(this.#openTimeout, this.#listenTimeout)
const response = await transport
- .send(0xb0, Ledger.#ADPU_CODES.version, Ledger.#ADPU_CODES.paramUnused, Ledger.#ADPU_CODES.paramUnused)
- .catch(err => dec.toBytes(err.statusCode)) as Uint8Array
+ .send(0xb0, this.#ADPU_CODES.version, this.#ADPU_CODES.paramUnused, this.#ADPU_CODES.paramUnused)
+ .catch((err: any) => dec.toBytes(err.statusCode)) as Uint8Array
await transport.close()
const statusCode = bytes.toDec(response.slice(-2)) as number
- const status = Ledger.#STATUS_CODES[statusCode] ?? 'UNKNOWN_ERROR'
+ const status = this.#STATUS_CODES[statusCode] ?? 'UNKNOWN_ERROR'
if (status !== 'OK') {
return { status, name: null, version: null }
}