Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions packages/extension/src/libs/keyring/public-keyring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,20 @@ class PublicKeyRing {
isHardware: false,
isTestWallet: true,
};
allKeys[
'bc1qwgpekhhfekclmp58kyzmlsdzey948d4395nvs8'
] = {
address:
'bc1qwgpekhhfekclmp58kyzmlsdzey948d4395nvs8',
basePath: "m/49'/2'/0'/1",
name: 'fake btc account #1',
pathIndex: 0,
publicKey: '0x0',
signerType: SignerType.secp256k1btc,
walletType: WalletType.mnemonic,
isHardware: false,
isTestWallet: true,
};
Comment thread
gamalielhere marked this conversation as resolved.
allKeys['77hREDDaAiimedtD9bR1JDMgYLW3AA5yPvD91pvrueRp'] = {
address: '77hREDDaAiimedtD9bR1JDMgYLW3AA5yPvD91pvrueRp',
basePath: "m/44'/501'/0'/1",
Expand Down
81 changes: 81 additions & 0 deletions packages/hw-wallets/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,84 @@
# @enkryptcom/hw-wallets

## v0.0.12

## Hardware wallet manager for enkrypt

### Getting started

#### Minimum Node version

`node v20`

#### Installation

NPM: `npm install @enkryptcom/hw-wallets @enkryptcom/types`
Yarn: `yarn add @enkryptcom/hw-wallets @enkryptcom/types`
PNPM `pnpm add @enkryptcom/hw-wallets @enkryptcom/types`

### How to use

1. Create an instance of `HWwalletManager`

```
import HWwalletManager from @enkryptcom/hw-wallets

const hwManager = new HWwalletManager()
```

2. Call methods within class, passing network names and providers. Class automatically handles which wallet to use.

### API

#### `getAddress(options: getAddressRequest): Promise<AddressResponse>`

Returns wallet address based off of the path provided in the `getAddressRequest`.

#### `signPersonalMessage(options: SignMessageRequest): Promise<string>`

Signs personal message.

#### `signTransaction(options: SignTransactionRequest): Promise<string>`

Returns an RPC sign you can then add to a transaction object.

#### `getSupportedPaths(options: isConnectedRequest): Promise<PathType[]>`

Returns supported paths based on options provided.

#### `isNetworkSupported(networkName: NetworkNames): boolean`

Checks network name support.

#### `isConnected(options: isConnectedRequest): Promise<boolean>`

Checks connection status.

#### `close(): Promise<void>`

Closes all HW wallet connections

### Types

`NetworkNames`: https://github.com/enkryptcom/enKrypt/blob/main/packages/types/src/networks.ts#L1
`getAddressRequest`: https://github.com/enkryptcom/enKrypt/blob/main/packages/hw-wallets/src/types.ts#L74
`AddressResponse`: https://github.com/enkryptcom/enKrypt/blob/main/packages/hw-wallets/src/types.ts#L31
`SignMessageRequest`: https://github.com/enkryptcom/enKrypt/blob/main/packages/hw-wallets/src/types.ts#L54
`SignTransactionRequest`: https://github.com/enkryptcom/enKrypt/blob/main/packages/hw-wallets/src/types.ts#L65
`isConnectedRequest`: https://github.com/enkryptcom/enKrypt/blob/main/packages/hw-wallets/src/types.ts#L78

### Adding more paths

Navigate to `src/configs.ts` and add your new derivation path at the bottom.
Make sure to follow the configuration in that file.
Import path in the corresponding `Trezor` provider `config.ts`.
See `src/trezor/ethereum/configs.ts` for example.

### Notes

Connection request to hardware wallet actually happens on `getAddress()` request.
`Ledger` can't have any paths as each paths are defined by the corresponding app.

#### For Vue devs

`ref/reactive` will mess with how Vue compiles these classes because of how Vue utilizes proxies. If you want to store an instance into a ref or reactive, use [`makeRaw`](https://vuejs.org/api/reactivity-advanced#markraw).
152 changes: 152 additions & 0 deletions packages/hw-wallets/src/configs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const ledgerAppNames = {
[NetworkNames.Polkadot]: "Polkadot",
[NetworkNames.Karura]: "Karura",
[NetworkNames.Bitcoin]: "Bitcoin",
[NetworkNames.BitcoinTest]: "Bitcoin Test",
[NetworkNames.Litecoin]: "Litecoin",
[NetworkNames.Dogecoin]: "Dogecoin",
[NetworkNames.Solana]: "Solana",
Expand Down Expand Up @@ -60,6 +61,11 @@ const bip44Paths = {
basePath: "m/44'/137'/0'/0",
label: "Rootstock",
},
rootstockTestnet: {
path: "m/44'/37310'/0'/0/{index}",
basePath: "m/44'/37310'/0'/0",
label: 'Rootstock Testnet'
},
ethereumClassic: {
path: "m/44'/61'/0'/0/{index}",
basePath: "m/44'/61'/0'/0",
Expand All @@ -80,6 +86,11 @@ const bip44Paths = {
basePath: "m/84'/0'",
label: "Bitcoin",
},
bitcoinTestSegwitLedger: {
path: "m/84'/1'/{index}'/0/0",
basePath: "m/84'/1'",
label: "Bitcoin Test",
},
litecoinSegwitLedger: {
path: "m/84'/2'/{index}'/0/0",
basePath: "m/84'/2'",
Expand All @@ -105,6 +116,11 @@ const bip44Paths = {
basePath: "m/84'/0'/0'/0",
label: "Bitcoin",
},
bitcoinTestSegwitTrezor: {
path: "m/84'/1'/0'/0/{index}",
basePath: "m/84'/1'/0'/0",
label: "Bitcoin Test",
},
litecoinSegwitTrezor: {
path: "m/84'/2'/0'/0/{index}",
basePath: "m/84'/2'/0'/0",
Expand All @@ -115,5 +131,141 @@ const bip44Paths = {
basePath: "m/44'/3'/0'/0",
label: "Dogecoin",
},
// Additional paths from MyEtherWallet
poaNetwork: {
path: "m/44'/60'/0'/0/{index}",
basePath: "m/44'/60'/0'/0",
label: 'POA network'
},
expanse: {
path: "m/44'/40'/0'/0/{index}",
basePath: "m/44'/40'/0'/0",
label: 'Expanse'
},
ubiq: {
path: "m/44'/108'/0'/0/{index}",
basePath: "m/44'/108'/0'/0",
label: 'Ubiq'
},
ellaism: {
path: "m/44'/163'/0'/0/{index}",
basePath: "m/44'/163'/0'/0",
label: 'Ellaism'
},
etherGem: {
path: "m/44'/1987'/0'/0/{index}",
basePath: "m/44'/1987'/0'/0",
label: 'EtherGem'
},
callisto: {
path: "m/44'/820'/0'/0/{index}",
basePath: "m/44'/820'/0'/0",
label: 'Callisto'
},
ethereumSocial: {
path: "m/44'/1128'/0'/0/{index}",
basePath: "m/44'/1128'/0'/0",
label: 'Ethereum Social'
},
musicoin: {
path: "m/44'/184'/0'/0/{index}",
basePath: "m/44'/184'/0'/0",
label: 'Musicoin'
},
goChain: {
path: "m/44'/6060'/0'/0/{index}",
basePath: "m/44'/6060'/0'/0",
label: 'GoChain'
},
eosClassic: {
path: "m/44'/2018'/0'/0/{index}",
basePath: "m/44'/2018'/0'/0",
label: 'EOS Classic'
},
akroma: {
path: "m/44'/200625'/0'/0/{index}",
basePath: "m/44'/200625'/0'/0",
label: 'Akroma'
},
etherSocialNetwork: {
path: "m/44'/31102'/0'/0/{index}",
basePath: "m/44'/31102'/0'/0",
label: 'EtherSocial Network'
},
pirl: {
path: "m/44'/164'/0'/0/{index}",
basePath: "m/44'/164'/0'/0",
label: 'PIRL'
},
ether1: {
path: "m/44'/1313114'/0'/0/{index}",
basePath: "m/44'/1313114'/0'/0",
label: 'Ether-1'
},
atheios: {
path: "m/44'/1620'/0'/0/{index}",
basePath: "m/44'/1620'/0'/0",
label: 'Atheios'
},
tomoChain: {
path: "m/44'/889'/0'/0/{index}",
basePath: "m/44'/889'/0'/0",
label: 'TomoChain'
},
mixBlockchain: {
path: "m/44'/76'/0'/0/{index}",
basePath: "m/44'/76'/0'/0",
label: 'Mix Blockchain'
},
iolite: {
path: "m/44'/1171337'/0'/0/{index}",
basePath: "m/44'/1171337'/0'/0",
label: 'Iolite'
},
thundercore: {
path: "m/44'/1001'/0'/0/{index}",
basePath: "m/44'/1001'/0'/0",
label: 'ThunderCore'
},
solidum: {
path: "m/44'/997'/0'/0/{index}",
basePath: "m/44'/997'/0'/0",
label: 'Solidum'
},
metadium: {
path: "m/44'/916'/0'/0/{index}",
basePath: "m/44'/916'/0'/0",
label: 'Metadium'
},
reoscChain: {
path: "m/44'/2894'/0'/0/{index}",
basePath: "m/44'/2894'/0'/0",
label: 'REOSC'
},
dexon: {
path: "m/44'/237'/0'/0/{index}",
basePath: "m/44'/237'/0'/0",
label: 'DEXON Network'
},
lightstreamsNetwork: {
path: "m/44'/60'/0'/{index}",
basePath: "m/44'/60'/0'",
label: 'Lightstreams Network'
},
mintmeComCoin: {
path: "m/44'/227'/0'/0/{index}",
basePath: "m/44'/227'/0'/0",
label: 'MintMe.com Coin'
},
ethercore: {
path: "m/44'/466'/0'/0/{index}",
basePath: "m/44'/466'/0'/0",
label: 'EtherCore'
},
binanceChain: {
path: "m/44'/714'/{index}",
basePath: "m/44'/714'",
label: 'Binance Chain'
},
Comment thread
gamalielhere marked this conversation as resolved.
};
export { walletConfigs, MessengerName, ledgerAppNames, bip44Paths };
2 changes: 1 addition & 1 deletion packages/hw-wallets/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class HWwalletManager {
LedgerEthereum,
LedgerSubstrate,
LedgerBitcoin,
LedgerSolana,
LedgerSolana
],
[HWwalletType.trezor]: [TrezorEthereum, TrezorBitcoin, TrezorSolana],
};
Expand Down
1 change: 1 addition & 0 deletions packages/hw-wallets/src/ledger/bitcoin/configs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { bip44Paths } from "../../configs";

const supportedPaths = {
[NetworkNames.Bitcoin]: [bip44Paths.bitcoinSegwitLedger],
[NetworkNames.BitcoinTest]: [bip44Paths.bitcoinTestSegwitLedger],
[NetworkNames.Litecoin]: [bip44Paths.litecoinSegwitLedger],
[NetworkNames.Dogecoin]: [bip44Paths.dogecoinLedger],
};
Expand Down
8 changes: 4 additions & 4 deletions packages/hw-wallets/src/ledger/bitcoin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,8 @@ class LedgerBitcoin implements HWWalletProvider {
transactionOptions.psbtTx.txInputs[idx].index,
transactionOptions.psbtTx.data.inputs[idx].witnessScript
? transactionOptions.psbtTx.data.inputs[idx].witnessScript.toString(
"hex",
)
"hex",
)
: undefined,
undefined,
]),
Expand Down Expand Up @@ -218,7 +218,7 @@ class LedgerBitcoin implements HWWalletProvider {

close(): Promise<void> {
// eslint-disable-next-line @typescript-eslint/no-empty-function
return this.transport.close().catch(() => {});
return this.transport.close().catch(() => { });
}
Comment thread
gamalielhere marked this conversation as resolved.

isConnected(networkName: NetworkNames): Promise<boolean> {
Expand All @@ -234,4 +234,4 @@ class LedgerBitcoin implements HWWalletProvider {
}
}

export default LedgerBitcoin;
export default LedgerBitcoin;
4 changes: 2 additions & 2 deletions packages/hw-wallets/src/ledger/ethereum/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ class LedgerEthereum implements HWWalletProvider {

close(): Promise<void> {
// eslint-disable-next-line @typescript-eslint/no-empty-function
return this.transport.close().catch(() => {});
return this.transport.close().catch(() => { });
}
Comment on lines 171 to 174
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Prevent close() from crashing after failed init

If transport setup fails and this.transport stays null, calling close() triggers a TypeError before the catch runs. That makes cleanup impossible after an init failure.

Guard the call (same pattern as other Ledger classes):

   close(): Promise<void> {
-    // eslint-disable-next-line @typescript-eslint/no-empty-function
-    return this.transport.close().catch(() => { });
+    if (!this.transport) return Promise.resolve();
+    return this.transport.close().catch(() => undefined);
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
close(): Promise<void> {
// eslint-disable-next-line @typescript-eslint/no-empty-function
return this.transport.close().catch(() => {});
return this.transport.close().catch(() => { });
}
close(): Promise<void> {
if (!this.transport) return Promise.resolve();
return this.transport.close().catch(() => undefined);
}
🤖 Prompt for AI Agents
In packages/hw-wallets/src/ledger/ethereum/index.ts around lines 155 to 158,
calling this.transport.close() can throw a TypeError when this.transport is null
after a failed init; update close() to first check if this.transport is non-null
and only call close().catch(...) when present, otherwise return a resolved
Promise<void> (matching the guard pattern used in other Ledger classes) so
cleanup never crashes when transport was not created.


isConnected(networkName: NetworkNames): Promise<boolean> {
Expand All @@ -191,4 +191,4 @@ class LedgerEthereum implements HWWalletProvider {
}
}

export default LedgerEthereum;
export default LedgerEthereum;
4 changes: 2 additions & 2 deletions packages/hw-wallets/src/ledger/solana/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ class LedgerSolana implements HWWalletProvider {

close(): Promise<void> {
// eslint-disable-next-line @typescript-eslint/no-empty-function
return this.transport.close().catch(() => {});
return this.transport.close().catch(() => { });
}
Comment on lines 95 to 98
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Handle the no-transport case before calling close()

When initialization never bound a transport (e.g., both BLE and WebUSB unsupported), this.transport is still null. Invoking .close() then throws immediately, so callers can’t safely clean up.

Add a simple guard:

   close(): Promise<void> {
-    // eslint-disable-next-line @typescript-eslint/no-empty-function
-    return this.transport.close().catch(() => { });
+    if (!this.transport) return Promise.resolve();
+    return this.transport.close().catch(() => undefined);
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
close(): Promise<void> {
// eslint-disable-next-line @typescript-eslint/no-empty-function
return this.transport.close().catch(() => {});
return this.transport.close().catch(() => { });
}
close(): Promise<void> {
if (!this.transport) return Promise.resolve();
return this.transport.close().catch(() => undefined);
}
🤖 Prompt for AI Agents
In packages/hw-wallets/src/ledger/solana/index.ts around lines 77 to 80, the
close() method calls this.transport.close() without checking whether
this.transport is null, causing an immediate throw when no transport was
initialized; update close() to guard for a missing transport by returning a
resolved Promise if this.transport is null/undefined, otherwise call
this.transport.close() and propagate/catch its Promise as before so callers can
safely clean up even when no transport was created.


isConnected(networkName: NetworkNames): Promise<boolean> {
Expand All @@ -110,4 +110,4 @@ class LedgerSolana implements HWWalletProvider {
}
}

export default LedgerSolana;
export default LedgerSolana;
4 changes: 2 additions & 2 deletions packages/hw-wallets/src/ledger/substrate/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class LedgerSubstrate implements HWWalletProvider {

close(): Promise<void> {
// eslint-disable-next-line @typescript-eslint/no-empty-function
return this.transport.close().catch(() => {});
return this.transport.close().catch(() => { });
}
Comment on lines 81 to 84
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Guard close() when no transport exists

this.transport stays null whenever initialization fails or was never run. Calling this.transport.close() in that state throws before the catch executes, so consumers hit a runtime error when attempting to clean up after a failed init.

Use a guard (or optional chaining) so close() resolves immediately if there is no transport:

   close(): Promise<void> {
-    // eslint-disable-next-line @typescript-eslint/no-empty-function
-    return this.transport.close().catch(() => { });
+    if (!this.transport) return Promise.resolve();
+    return this.transport.close().catch(() => undefined);
   }
🤖 Prompt for AI Agents
In packages/hw-wallets/src/ledger/substrate/index.ts around lines 66 to 69,
close() currently calls this.transport.close() unguarded which throws when
this.transport is null; change it to first check for a transport (e.g., if
(!this.transport) return Promise.resolve(); or use optional chaining like return
this.transport?.close().catch(() => {});) so the method resolves immediately
when no transport exists and still catches errors from close().


isConnected(networkName: NetworkNames): Promise<boolean> {
Expand Down Expand Up @@ -128,4 +128,4 @@ class LedgerSubstrate implements HWWalletProvider {
}
}

export default LedgerSubstrate;
export default LedgerSubstrate;
Loading
Loading