Socket
Socket
Sign inDemoInstall

@marinade.finance/ledger-utils

Package Overview
Dependencies
Maintainers
1
Versions
30
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@marinade.finance/ledger-utils - npm Package Compare versions

Comparing version 2.0.22 to 2.1.0

6

package.json
{
"name": "@marinade.finance/ledger-utils",
"version": "2.0.22",
"version": "2.1.0",
"description": "Utility functions for interacting with the Ledger from CLI",

@@ -30,3 +30,3 @@ "repository": {

"@solana/web3.js": "^1.78.4",
"@marinade.finance/ts-common": "^2.0.22"
"@marinade.finance/ts-common": "^2.1.0"
},

@@ -38,3 +38,3 @@ "peerDependencies": {

"@solana/web3.js": "^1.78.4",
"@marinade.finance/ts-common": "^2.0.22"
"@marinade.finance/ts-common": "^2.1.0"
},

@@ -41,0 +41,0 @@ "scripts": {

@@ -24,3 +24,4 @@ "use strict";

if (e.statusCode === 0x6d02) {
(0, ts_common_1.logError)(logger, 'Ledger device Solana application is not activated. ' +
(0, ts_common_1.logError)(logger, `Ledger device [${pathOrUrl}] ` +
': Solana application is not opened. ' +
'Please, enter the Solana app on your ledger device first.');

@@ -32,17 +33,23 @@ }

}
else if (e.statusCode === 0x5515) {
(0, ts_common_1.logError)(logger, `Ledger device [${pathOrUrl}] is locked. ` +
'Please, unlock it first.');
}
}
else if (e instanceof errors_1.TransportError &&
e.message.includes('Invalid channel')) {
(0, ts_common_1.logError)(logger, 'Ledger device seems not being acknowledged to open the ledger manager. ' +
(0, ts_common_1.logError)(logger, `Ledger device [${pathOrUrl}] seems not being acknowledged to have opened the manager. ` +
'Please, open ledger manager first on your device.');
}
else if (e instanceof errors_1.LockedDeviceError) {
(0, ts_common_1.logError)(logger, 'Ledger device is locked. ' + 'Please, unlock it first.');
(0, ts_common_1.logError)(logger, `Ledger device [${pathOrUrl}] is locked. ` +
'Please, unlock it first.');
}
else if (e instanceof Error &&
e.message.includes('read from a closed HID')) {
(0, ts_common_1.logError)(logger, 'Ledger cannot be open, it seems to be closed. Ensure no other program uses it.');
(0, ts_common_1.logError)(logger, `Ledger device [${pathOrUrl}] ` +
'cannot be open, it seems to be closed. Ensure no other program uses it.');
}
else {
(0, ts_common_1.logError)(logger, `Failed to connect to Ledger device of key ${pathOrUrl}`);
(0, ts_common_1.logError)(logger, `Failed to connect to Ledger device [${pathOrUrl}]`);
}

@@ -49,0 +56,0 @@ throw e;

import Solana from '@ledgerhq/hw-app-solana';
import TransportNodeHid from '@ledgerhq/hw-transport-node-hid-noevents';
import { PublicKey, Transaction, VersionedTransaction } from '@solana/web3.js';

@@ -25,4 +26,5 @@ import { LoggerPlaceholder } from '@marinade.finance/ts-common';

readonly publicKey: PublicKey;
readonly logger: LoggerPlaceholder | undefined;
/**
* "Constructor" of SolanaLedger class.
* "Constructor" of Solana Ledger to be opened and worked as a Wallet.
* From ledger url in format of usb://ledger[/<pubkey>[?key=<number>]

@@ -35,3 +37,2 @@ * creates wrapper class around Solana ledger device from '@ledgerhq/hw-app-solana' package.

signAllTransactions<T extends Transaction | VersionedTransaction>(txs: T[]): Promise<T[]>;
private static getPublicKey;
/**

@@ -48,3 +49,2 @@ * Based on the provided pubkey and derived path

private static getSolanaApi;
private static scheduleOnExitClose;
/**

@@ -63,2 +63,7 @@ * Signing versioned transaction message with ledger

/**
* From provided Solana API and derived path
* it returns the public key of the derived path.
*/
export declare function getPublicKey(solanaApi: Solana, derivedPath: string): Promise<PublicKey>;
/**
* Parsing string as ledger url that could be in format of url or derivation path.

@@ -74,4 +79,20 @@ * Some of the examples (trying to be compatible with solana cli https://github.com/solana-labs/solana/blob/v1.14.19/clap-utils/src/keypair.rs#L613)

export declare function parseLedgerUrl(ledgerUrl: string): {
pubkey: PublicKey | undefined;
parsedPubkey: PublicKey | undefined;
parsedDerivedPath: string;
};
export declare function searchDerivedPathFromPubkey(pubkey: PublicKey, logger?: LoggerPlaceholder | undefined, heuristicDepth?: number | undefined, heuristicWide?: number | undefined): Promise<{
derivedPath: string;
solanaApi: Solana;
transport: TransportNodeHid;
} | null>;
/**
*
* Parsing the derived path string to check heuristic depth and wide.
*
* When the derived path is e.g., 44'/501'/0/0/5 then
* the wide will be 3, depth will be max of the provided numbers as it's 5.
*/
export declare function getHeuristicDepthAndWide(derivedPath: string, defaultDepth?: number, defaultWide?: number): {
depth: number;
wide: number;
};

@@ -29,3 +29,3 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.parseLedgerUrl = exports.LedgerWallet = exports.DEFAULT_DERIVATION_PATH = exports.SOLANA_LEDGER_BIP44_BASE_REGEXP = exports.SOLANA_LEDGER_BIP44_BASE_PATH = exports.CLI_LEDGER_URL_PREFIX = void 0;
exports.getHeuristicDepthAndWide = exports.searchDerivedPathFromPubkey = exports.parseLedgerUrl = exports.getPublicKey = exports.LedgerWallet = exports.DEFAULT_DERIVATION_PATH = exports.SOLANA_LEDGER_BIP44_BASE_REGEXP = exports.SOLANA_LEDGER_BIP44_BASE_PATH = exports.CLI_LEDGER_URL_PREFIX = void 0;
const hw_app_solana_1 = __importDefault(require("@ledgerhq/hw-app-solana"));

@@ -36,3 +36,2 @@ const hw_transport_node_hid_noevents_1 = __importStar(require("@ledgerhq/hw-transport-node-hid-noevents"));

const ts_common_1 = require("@marinade.finance/ts-common");
const process_1 = require("process");
exports.CLI_LEDGER_URL_PREFIX = 'usb://ledger';

@@ -42,5 +41,6 @@ exports.SOLANA_LEDGER_BIP44_BASE_PATH = "44'/501'";

exports.DEFAULT_DERIVATION_PATH = exports.SOLANA_LEDGER_BIP44_BASE_PATH;
const IN_LIB_TRANSPORT_CACHE = new Map();
class LedgerWallet {
/**
* "Constructor" of SolanaLedger class.
* "Constructor" of Solana Ledger to be opened and worked as a Wallet.
* From ledger url in format of usb://ledger[/<pubkey>[?key=<number>]

@@ -50,12 +50,12 @@ * creates wrapper class around Solana ledger device from '@ledgerhq/hw-app-solana' package.

static async instance(ledgerUrl = '0', logger = undefined) {
const { pubkey, derivedPath: parsedDerivedPath } = parseLedgerUrl(ledgerUrl);
// getting
const { api, derivedPath } = await LedgerWallet.getSolanaApi(pubkey, parsedDerivedPath, logger);
const publicKey = await LedgerWallet.getPublicKey(api, derivedPath);
return new LedgerWallet(api, derivedPath, publicKey);
// parsedPubkey could be undefined when not provided in url string
const { parsedPubkey, parsedDerivedPath } = parseLedgerUrl(ledgerUrl);
const { api, pubkey, derivedPath } = await LedgerWallet.getSolanaApi(parsedPubkey, parsedDerivedPath, logger);
return new LedgerWallet(api, derivedPath, pubkey, logger);
}
constructor(solanaApi, derivedPath, publicKey) {
constructor(solanaApi, derivedPath, publicKey, logger = undefined) {
this.solanaApi = solanaApi;
this.derivedPath = derivedPath;
this.publicKey = publicKey;
this.logger = logger;
}

@@ -81,6 +81,2 @@ async signTransaction(tx) {

}
static async getPublicKey(solanaApi, derivedPath) {
const { address: bufAddress } = await solanaApi.getAddress(derivedPath);
return new web3_js_1.PublicKey(bufAddress);
}
/**

@@ -103,16 +99,13 @@ * Based on the provided pubkey and derived path

if (pubkey === undefined) {
// taking first device
transport = await hw_transport_node_hid_noevents_1.default.open('');
LedgerWallet.scheduleOnExitClose(transport);
// we don't know where to search for the derived path and thus taking first device
// we will search for the provided derived path at this first device when signing message
// when pubkey is defined we search all available devices to find a match of pubkey and derived path
transport = (await openTransports(ledgerDevices[0]))[0];
}
else {
const openedTransports = [];
for (const device of ledgerDevices) {
openedTransports.push(await hw_transport_node_hid_noevents_1.default.open(device.path));
}
LedgerWallet.scheduleOnExitClose(...openedTransports);
const openedTransports = await openTransports(...ledgerDevices);
// if derived path is provided let's check if matches the pubkey
for (const openedTransport of openedTransports) {
const solanaApi = new hw_app_solana_1.default(openedTransport);
const ledgerPubkey = await LedgerWallet.getPublicKey(solanaApi, derivedPath);
const ledgerPubkey = await getPublicKey(solanaApi, derivedPath);
if (ledgerPubkey.equals(pubkey)) {

@@ -124,61 +117,20 @@ transport = openedTransport;

if (transport === undefined) {
(0, ts_common_1.logInfo)(logger, `Ledger device does not provide pubkey ${pubkey.toBase58()} ` +
`at defined derivation path ${derivedPath}, searching...`);
// parsing the derived path to check heuristic depth and wide
// when the derived path is 44'/501'/0/0/5
// then the wide will be 3, depth will be max of numbers as it's 5
let splitDerivedPath = derivedPath.split('/');
if (splitDerivedPath.length > 2) {
splitDerivedPath = splitDerivedPath.slice(2);
heuristicWide = splitDerivedPath.length;
heuristicDepth = Math.max(heuristicDepth, ...splitDerivedPath.map(v => parseFloat(v)));
(0, ts_common_1.logInfo)(logger, `Public key ${pubkey.toBase58()} has not been found at the default or provided ` +
`derivation path ${derivedPath}. Going to search, it will take a while...`);
const { depth, wide } = getHeuristicDepthAndWide(derivedPath, heuristicDepth, heuristicWide);
const searchedData = await searchDerivedPathFromPubkey(pubkey, logger, depth, wide);
if (searchedData !== null) {
transport = searchedData.transport;
derivedPath = searchedData.derivedPath;
(0, ts_common_1.logInfo)(logger, `For public key ${pubkey.toBase58()} has been found derived path ${derivedPath}`);
}
const heuristicsCombinations = (0, utils_1.generateAllCombinations)(heuristicDepth, heuristicWide);
for (const openedTransport of openedTransports) {
const solanaApi = new hw_app_solana_1.default(openedTransport);
for (const combination of heuristicsCombinations) {
const strCombination = combination.map(v => v.toString());
strCombination.unshift(exports.SOLANA_LEDGER_BIP44_BASE_PATH);
const heuristicDerivedPath = strCombination.join('/');
(0, ts_common_1.logDebug)(logger, `search loop: ${heuristicDerivedPath}`);
const ledgerPubkey = await LedgerWallet.getPublicKey(solanaApi, heuristicDerivedPath);
if (ledgerPubkey.equals(pubkey)) {
transport = openedTransport;
derivedPath = heuristicDerivedPath;
(0, ts_common_1.logInfo)(logger, `Using derived path ${derivedPath}, pubkey ${pubkey.toBase58()}`);
break; // the last found transport is the one we need
}
}
if (transport !== undefined) {
break; // the last transport found as the last one is the one we need
}
}
// let's close all the opened transports that are not the ones we need
openedTransports.filter(t => t !== transport).forEach(t => t.close());
}
if (transport === undefined) {
throw new Error('Available ledger devices does not provide pubkey ' +
pubkey.toBase58() +
' for derivation path ' +
derivedPath);
}
}
return { api: new hw_app_solana_1.default(transport), derivedPath };
}
// trying to close all provided transports in case of abrupt exit, or just exit
static scheduleOnExitClose(...transports) {
if (process) {
const signals = ['SIGINT', 'SIGTERM', 'SIGQUIT', 'exit'];
signals.forEach(signal => process.on(signal, () => {
for (const openedTransport of transports) {
try {
openedTransport.close();
}
catch (e) {
// ignore error and go to next transport
}
(0, process_1.exit)();
}
}));
if (transport === undefined) {
throw new Error('Available ledger devices does not provide pubkey ' +
`'${pubkey === null || pubkey === void 0 ? void 0 : pubkey.toBase58()}' for derivation path '${derivedPath}'`);
}
const api = new hw_app_solana_1.default(transport);
pubkey = await getPublicKey(api, derivedPath);
return { api, derivedPath, pubkey };
}

@@ -196,2 +148,5 @@ /**

async signMessage(message) {
(0, ts_common_1.logDebug)(this.logger, 'signing message with pubkey ' +
(await getPublicKey(this.solanaApi, this.derivedPath)).toBase58() +
` of derived path ${this.derivedPath}`);
const { signature } = await this.solanaApi.signTransaction(this.derivedPath, Buffer.from(message.serialize()));

@@ -203,2 +158,11 @@ return signature;

/**
* From provided Solana API and derived path
* it returns the public key of the derived path.
*/
async function getPublicKey(solanaApi, derivedPath) {
const { address: bufAddress } = await solanaApi.getAddress(derivedPath);
return new web3_js_1.PublicKey(bufAddress);
}
exports.getPublicKey = getPublicKey;
/**
* Parsing string as ledger url that could be in format of url or derivation path.

@@ -218,4 +182,4 @@ * Some of the examples (trying to be compatible with solana cli https://github.com/solana-labs/solana/blob/v1.14.19/clap-utils/src/keypair.rs#L613)

}
let pubkey;
let derivedPath;
let parsedPubkey;
let parsedDerivedPath;
// removal of the prefix + optional slash

@@ -243,16 +207,16 @@ const ledgerUrlRegexp = new RegExp(exports.CLI_LEDGER_URL_PREFIX + '/?');

//case: usb://ledger/<pubkey>
pubkey = parsePubkey(parts[0]);
derivedPath = exports.DEFAULT_DERIVATION_PATH;
parsedPubkey = parsePubkey(parts[0]);
parsedDerivedPath = exports.DEFAULT_DERIVATION_PATH;
}
else if (parts.length === 2) {
//case: usb://ledger/<pubkey>?key=<number>
pubkey = parsePubkey(parts[0]);
parsedPubkey = parsePubkey(parts[0]);
const key = parts[1];
if (key === '') {
// case: usb://ledger/<pubkey>?key=
derivedPath = exports.DEFAULT_DERIVATION_PATH;
parsedDerivedPath = exports.DEFAULT_DERIVATION_PATH;
}
else if (exports.SOLANA_LEDGER_BIP44_BASE_REGEXP.test(key)) {
// case: usb://ledger/<pubkey>?key=44'/501'/<number>
derivedPath = key;
parsedDerivedPath = key;
}

@@ -262,3 +226,3 @@ else {

const keyTrimmed = key.replace(/^\//, '');
derivedPath = exports.SOLANA_LEDGER_BIP44_BASE_PATH + '/' + keyTrimmed;
parsedDerivedPath = exports.SOLANA_LEDGER_BIP44_BASE_PATH + '/' + keyTrimmed;
}

@@ -270,5 +234,82 @@ }

}
return { pubkey, derivedPath };
return { parsedPubkey, parsedDerivedPath };
}
exports.parseLedgerUrl = parseLedgerUrl;
async function searchDerivedPathFromPubkey(pubkey, logger = undefined, heuristicDepth = 10, heuristicWide = 3) {
const ledgerDevices = (0, hw_transport_node_hid_noevents_1.getDevices)();
if (ledgerDevices.length === 0) {
throw new Error('No ledger device found');
}
const openedTransports = await openTransports(...ledgerDevices);
const heuristicsCombinations = (0, utils_1.generateAllCombinations)(heuristicDepth, heuristicWide);
for (const transport of openedTransports) {
const solanaApi = new hw_app_solana_1.default(transport);
for (const combination of heuristicsCombinations) {
const strCombination = combination.map(v => v.toString());
strCombination.unshift(exports.SOLANA_LEDGER_BIP44_BASE_PATH);
const heuristicDerivedPath = strCombination.join('/');
(0, ts_common_1.logDebug)(logger, `search loop: ${heuristicDerivedPath}`);
const ledgerPubkey = await getPublicKey(solanaApi, heuristicDerivedPath);
if (ledgerPubkey.equals(pubkey)) {
(0, ts_common_1.logDebug)(logger, `Found path ${heuristicDerivedPath}, pubkey ${pubkey.toBase58()}`);
return { derivedPath: heuristicDerivedPath, solanaApi, transport };
}
}
}
return null;
}
exports.searchDerivedPathFromPubkey = searchDerivedPathFromPubkey;
/**
*
* Parsing the derived path string to check heuristic depth and wide.
*
* When the derived path is e.g., 44'/501'/0/0/5 then
* the wide will be 3, depth will be max of the provided numbers as it's 5.
*/
function getHeuristicDepthAndWide(derivedPath, defaultDepth = 10, defaultWide = 3) {
let depth = defaultDepth;
let wide = defaultWide;
let splitDerivedPath = derivedPath.split('/');
// we expect derived path starts with solana derivation path 44'/501'
// going to check parts after first 2
if (splitDerivedPath.length > 2) {
splitDerivedPath = splitDerivedPath.slice(2);
wide = Math.max(defaultWide, splitDerivedPath.length);
depth = Math.max(defaultDepth, ...splitDerivedPath.map(v => parseFloat(v)));
}
return { depth, wide };
}
exports.getHeuristicDepthAndWide = getHeuristicDepthAndWide;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async function openTransports(...devices) {
const transports = [];
for (const device of devices) {
let transport = IN_LIB_TRANSPORT_CACHE.get(device.path);
if (transport === undefined) {
transport = await hw_transport_node_hid_noevents_1.default.open(device.path);
scheduleTransportCloseOnExit(transport);
IN_LIB_TRANSPORT_CACHE.set(device.path, transport);
}
transports.push(transport);
}
return transports;
}
/**
* Trying to close all provided transports in case of abrupt exit, or just exit
* (ignoring errors when closing the transport).
*
* @param transports set of transport to be closed on exit
*/
function scheduleTransportCloseOnExit(...transports) {
(0, ts_common_1.scheduleOnExit)(() => {
for (const openedTransport of transports) {
try {
openedTransport.close();
}
catch (e) {
// ignore error and go to next transport
}
}
});
}
//# sourceMappingURL=ledger.js.map

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc