//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com>
//! SPDX-License-Identifier: GPL-3.0-or-later
-import { APDU_CODES, DERIVATION_PATH, LedgerAccountResponse, LedgerTransport, STATUS_CODES, listenTimeout, openTimeout } from '.'
+import { APDU_CODES, DERIVATION_PATH, LedgerAccountResponse, LedgerTransport, STATUS_CODES, LISTEN_TIMEOUT, OPEN_TIMEOUT } from '.'
import { HARDENED_OFFSET } from '../constants'
import { bytes, dec } from '../convert'
const account = dec.toBytes(index + HARDENED_OFFSET, 4)
const data = new Uint8Array([...DERIVATION_PATH, ...account])
- const t = await transport.create(openTimeout, listenTimeout)
+ const t = await transport.create(OPEN_TIMEOUT, LISTEN_TIMEOUT)
const response = await t
.send(APDU_CODES.class, APDU_CODES.account, show ? 1 : 0, APDU_CODES.paramUnused, data as Buffer)
.catch((err: any) => dec.toBytes(err.statusCode))
//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com>
//! SPDX-License-Identifier: GPL-3.0-or-later
-import { APDU_CODES, LedgerResponse, LedgerTransport, STATUS_CODES, listenTimeout, openTimeout } from '.'
+import { APDU_CODES, LedgerResponse, LedgerTransport, STATUS_CODES, LISTEN_TIMEOUT, OPEN_TIMEOUT } from '.'
import { Account } from '../account'
import { Block } from '../block'
import { BIP44_COIN_NANO, BIP44_PURPOSE, HARDENED_OFFSET } from '../constants'
const signature = hex.toBytes(block.signature, 64)
const data = new Uint8Array([APDU_CODES.bip32DerivationLevel, ...purpose, ...coin, ...account, ...previous, ...link, ...representative, ...balance, ...signature])
- const t = await transport.create(openTimeout, listenTimeout)
+ const t = await transport.create(OPEN_TIMEOUT, LISTEN_TIMEOUT)
const response = await t
.send(APDU_CODES.class, APDU_CODES.cacheBlock, APDU_CODES.paramUnused, APDU_CODES.paramUnused, data as Buffer)
.then((res: Buffer) => bytes.toDec(res))
//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com>
//! SPDX-License-Identifier: GPL-3.0-or-later
-import { LedgerResponse, LedgerTransport, openTimeout, listenTimeout, APDU_CODES, STATUS_CODES } from '.'
+import { LedgerResponse, LedgerTransport, OPEN_TIMEOUT, LISTEN_TIMEOUT, APDU_CODES, STATUS_CODES } from '.'
import { bytes } from '../convert'
export async function _close (transport: LedgerTransport): Promise<LedgerResponse> {
- const t = await transport.create(openTimeout, listenTimeout)
+ const t = await transport.create(OPEN_TIMEOUT, LISTEN_TIMEOUT)
const response = await t
.send(0xb0, 0xa7, APDU_CODES.paramUnused, APDU_CODES.paramUnused)
.then((res: Buffer) => bytes.toDec(res))
//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com>
//! SPDX-License-Identifier: GPL-3.0-or-later
-import { ledgerVendorId } from '.'
+import { LEDGER_VENDOR_ID } from '.'
export async function _disconnect (): Promise<void> {
try {
const hidDevices = await navigator?.hid?.getDevices?.() ?? []
const usbDevices = await navigator?.usb?.getDevices?.() ?? []
const devices = [...hidDevices, ...usbDevices]
- .filter(device => device.vendorId === ledgerVendorId)
+ .filter(device => device.vendorId === LEDGER_VENDOR_ID)
await Promise.allSettled(devices.map(device => device.forget?.() ?? Promise.resolve()))
} catch (err) {
console.warn('Ledger.disconnect()', err)
* Vendor ID assigned to Ledger for HID and USB interfaces.
* https://github.com/LedgerHQ/ledger-live/blob/develop/libs/ledgerjs/packages/devices/src/index.ts#L164
*/
-export const ledgerVendorId: 0x2c97 = 0x2c97
+export const LEDGER_VENDOR_ID: 0x2c97 = 0x2c97
+
+export const LISTEN_TIMEOUT: 30000 = 30000
+
+export const OPEN_TIMEOUT: 3000 = 3000
export const STATUS_CODES: Readonly<Record<number, string>> = Object.freeze({
...Object.fromEntries(Object.entries(StatusCodes).map(([k, v]) => [+v, k])),
0x6a82: 'CACHE_MISS'
})
-export const listenTimeout: 30000 = 30000
-
-export const openTimeout: 3000 = 3000
-
/**
* Ledger hardware wallet created by communicating with a Ledger device via ADPU
* calls. This wallet does not feature any seed nor mnemonic phrase as all
static async #poll (): Promise<void> {
try {
const isHidPaired = (await navigator.hid?.getDevices?.() ?? [])
- .some(device => device.vendorId === ledgerVendorId)
+ .some(device => device.vendorId === LEDGER_VENDOR_ID)
const isUsbPaired = (await navigator.usb?.getDevices?.() ?? [])
- .some(device => device.vendorId === ledgerVendorId)
+ .some(device => device.vendorId === LEDGER_VENDOR_ID)
if ((this.#transport === TransportHID && isHidPaired)
|| (this.#transport === TransportUSB && isUsbPaired)) {
await this.connect()
//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com>
//! SPDX-License-Identifier: GPL-3.0-or-later
-import { LedgerResponse, LedgerTransport, openTimeout, listenTimeout, APDU_CODES, STATUS_CODES } from '.'
+import { LedgerResponse, LedgerTransport, OPEN_TIMEOUT, LISTEN_TIMEOUT, APDU_CODES, STATUS_CODES } from '.'
import { bytes } from '../convert'
export async function _open (transport: LedgerTransport): Promise<LedgerResponse> {
const name = new TextEncoder().encode('Nano')
- const t = await transport.create(openTimeout, listenTimeout)
+ const t = await transport.create(OPEN_TIMEOUT, LISTEN_TIMEOUT)
const response = await t
.send(0xe0, 0xd8, APDU_CODES.paramUnused, APDU_CODES.paramUnused, name as Buffer)
.then((res: Buffer) => bytes.toDec(res))
//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com>
//! SPDX-License-Identifier: GPL-3.0-or-later
-import { APDU_CODES, DERIVATION_PATH, LedgerSignResponse, LedgerTransport, STATUS_CODES, listenTimeout, openTimeout } from '.'
+import { APDU_CODES, DERIVATION_PATH, LedgerSignResponse, LedgerTransport, STATUS_CODES, LISTEN_TIMEOUT, OPEN_TIMEOUT } from '.'
import { Account } from '../account'
import { Block } from '../block'
import { HARDENED_OFFSET } from '../constants'
const balance = hex.toBytes(BigInt(block.balance).toString(16), 16)
const data = new Uint8Array([...DERIVATION_PATH, ...account, ...previous, ...link, ...representative, ...balance])
- const t = await transport.create(openTimeout, listenTimeout)
+ const t = await transport.create(OPEN_TIMEOUT, LISTEN_TIMEOUT)
const response = await t
.send(APDU_CODES.class, APDU_CODES.signBlock, APDU_CODES.paramUnused, APDU_CODES.paramUnused, data as Buffer)
.catch((err: any) => dec.toBytes(err.statusCode))
const derivationAccount = dec.toBytes(index + HARDENED_OFFSET, 4)
const data = new Uint8Array([...DERIVATION_PATH, ...derivationAccount, ...nonce])
- const t = await transport.create(openTimeout, listenTimeout)
+ const t = await transport.create(OPEN_TIMEOUT, LISTEN_TIMEOUT)
const response = await t
.send(APDU_CODES.class, APDU_CODES.signNonce, APDU_CODES.paramUnused, APDU_CODES.paramUnused, data as Buffer)
.catch((err: any) => dec.toBytes(err.statusCode))
//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com>
//! SPDX-License-Identifier: GPL-3.0-or-later
-import { APDU_CODES, LedgerResponse, LedgerTransport, STATUS_CODES, listenTimeout, openTimeout } from '.'
+import { APDU_CODES, LedgerResponse, LedgerTransport, STATUS_CODES, LISTEN_TIMEOUT, OPEN_TIMEOUT } from '.'
import { bytes, dec } from '../convert'
import { queue } from './queue'
export async function version (transport: LedgerTransport): Promise<LedgerVersionResponse> {
return queue<LedgerVersionResponse>(async () => {
try {
- const t = await transport.create(openTimeout, listenTimeout)
+ const t = await transport.create(OPEN_TIMEOUT, LISTEN_TIMEOUT)
const response = await t
.send(0xb0, APDU_CODES.version, APDU_CODES.paramUnused, APDU_CODES.paramUnused)
.catch((err: any) => dec.toBytes(err.statusCode))