@metamask/eth-ledger-bridge-keyring 11.1.2 → 11.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -1
- package/dist/index.cjs +1 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +1 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -0
- package/dist/index.mjs.map +1 -1
- package/dist/ledger-keyring-v2.cjs +227 -0
- package/dist/ledger-keyring-v2.cjs.map +1 -0
- package/dist/ledger-keyring-v2.d.cts +32 -0
- package/dist/ledger-keyring-v2.d.cts.map +1 -0
- package/dist/ledger-keyring-v2.d.mts +32 -0
- package/dist/ledger-keyring-v2.d.mts.map +1 -0
- package/dist/ledger-keyring-v2.mjs +223 -0
- package/dist/ledger-keyring-v2.mjs.map +1 -0
- package/dist/ledger-keyring.cjs +8 -9
- package/dist/ledger-keyring.cjs.map +1 -1
- package/dist/ledger-keyring.d.cts.map +1 -1
- package/dist/ledger-keyring.d.mts.map +1 -1
- package/dist/ledger-keyring.mjs +8 -9
- package/dist/ledger-keyring.mjs.map +1 -1
- package/package.json +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [11.2.0]
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Add `LedgerKeyringV2` class implementing `KeyringV2` interface ([#416](https://github.com/MetaMask/accounts/pull/416)), ([#451](https://github.com/MetaMask/accounts/pull/451)), ([#453](https://github.com/MetaMask/accounts/pull/453))
|
|
15
|
+
- Wraps legacy `LedgerKeyring` to expose accounts via the unified `KeyringV2` API and the `KeyringAccount` type.
|
|
16
|
+
- Extends `EthKeyringWrapper` for common Ethereum logic.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Normalize signature `v` value from Ledger devices for proper recovery ([#449](https://github.com/MetaMask/accounts/pull/449))
|
|
21
|
+
|
|
10
22
|
## [11.1.2]
|
|
11
23
|
|
|
12
24
|
### Changed
|
|
@@ -342,7 +354,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
342
354
|
|
|
343
355
|
- Support new versions of ethereumjs/tx ([#68](https://github.com/MetaMask/eth-ledger-bridge-keyring/pull/68))
|
|
344
356
|
|
|
345
|
-
[Unreleased]: https://github.com/MetaMask/accounts/compare/@metamask/eth-ledger-bridge-keyring@11.
|
|
357
|
+
[Unreleased]: https://github.com/MetaMask/accounts/compare/@metamask/eth-ledger-bridge-keyring@11.2.0...HEAD
|
|
358
|
+
[11.2.0]: https://github.com/MetaMask/accounts/compare/@metamask/eth-ledger-bridge-keyring@11.1.2...@metamask/eth-ledger-bridge-keyring@11.2.0
|
|
346
359
|
[11.1.2]: https://github.com/MetaMask/accounts/compare/@metamask/eth-ledger-bridge-keyring@11.1.1...@metamask/eth-ledger-bridge-keyring@11.1.2
|
|
347
360
|
[11.1.1]: https://github.com/MetaMask/accounts/compare/@metamask/eth-ledger-bridge-keyring@11.1.0...@metamask/eth-ledger-bridge-keyring@11.1.1
|
|
348
361
|
[11.1.0]: https://github.com/MetaMask/accounts/compare/@metamask/eth-ledger-bridge-keyring@11.0.3...@metamask/eth-ledger-bridge-keyring@11.1.0
|
package/dist/index.cjs
CHANGED
|
@@ -15,6 +15,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./ledger-keyring.cjs"), exports);
|
|
18
|
+
__exportStar(require("./ledger-keyring-v2.cjs"), exports);
|
|
18
19
|
__exportStar(require("./ledger-iframe-bridge.cjs"), exports);
|
|
19
20
|
__exportStar(require("./ledger-mobile-bridge.cjs"), exports);
|
|
20
21
|
__exportStar(require("./ledger-transport-middleware.cjs"), exports);
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,uDAAiC;AACjC,6DAAuC;AACvC,6DAAuC;AAEvC,oEAA8C;AAE9C,sDAAgC","sourcesContent":["export * from './ledger-keyring';\nexport * from './ledger-iframe-bridge';\nexport * from './ledger-mobile-bridge';\nexport type * from './ledger-bridge';\nexport * from './ledger-transport-middleware';\nexport type * from './type';\nexport * from './ledger-hw-app';\n"]}
|
|
1
|
+
{"version":3,"file":"index.cjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,uDAAiC;AACjC,0DAAoC;AACpC,6DAAuC;AACvC,6DAAuC;AAEvC,oEAA8C;AAE9C,sDAAgC","sourcesContent":["export * from './ledger-keyring';\nexport * from './ledger-keyring-v2';\nexport * from './ledger-iframe-bridge';\nexport * from './ledger-mobile-bridge';\nexport type * from './ledger-bridge';\nexport * from './ledger-transport-middleware';\nexport type * from './type';\nexport * from './ledger-hw-app';\n"]}
|
package/dist/index.d.cts
CHANGED
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qCAAiC;AACjC,2CAAuC;AACvC,2CAAuC;AACvC,yCAAqC;AACrC,kDAA8C;AAC9C,gCAA4B;AAC5B,oCAAgC"}
|
|
1
|
+
{"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qCAAiC;AACjC,wCAAoC;AACpC,2CAAuC;AACvC,2CAAuC;AACvC,yCAAqC;AACrC,kDAA8C;AAC9C,gCAA4B;AAC5B,oCAAgC"}
|
package/dist/index.d.mts
CHANGED
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qCAAiC;AACjC,2CAAuC;AACvC,2CAAuC;AACvC,yCAAqC;AACrC,kDAA8C;AAC9C,gCAA4B;AAC5B,oCAAgC"}
|
|
1
|
+
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qCAAiC;AACjC,wCAAoC;AACpC,2CAAuC;AACvC,2CAAuC;AACvC,yCAAqC;AACrC,kDAA8C;AAC9C,gCAA4B;AAC5B,oCAAgC"}
|
package/dist/index.mjs
CHANGED
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qCAAiC;AACjC,2CAAuC;AACvC,2CAAuC;AAEvC,kDAA8C;AAE9C,oCAAgC","sourcesContent":["export * from './ledger-keyring';\nexport * from './ledger-iframe-bridge';\nexport * from './ledger-mobile-bridge';\nexport type * from './ledger-bridge';\nexport * from './ledger-transport-middleware';\nexport type * from './type';\nexport * from './ledger-hw-app';\n"]}
|
|
1
|
+
{"version":3,"file":"index.mjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qCAAiC;AACjC,wCAAoC;AACpC,2CAAuC;AACvC,2CAAuC;AAEvC,kDAA8C;AAE9C,oCAAgC","sourcesContent":["export * from './ledger-keyring';\nexport * from './ledger-keyring-v2';\nexport * from './ledger-iframe-bridge';\nexport * from './ledger-mobile-bridge';\nexport type * from './ledger-bridge';\nexport * from './ledger-transport-middleware';\nexport type * from './type';\nexport * from './ledger-hw-app';\n"]}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
4
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
5
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
6
|
+
};
|
|
7
|
+
var _LedgerKeyringV2_instances, _LedgerKeyringV2_getChecksumHexAddress, _LedgerKeyringV2_parseDerivationPath, _LedgerKeyringV2_getIndexForAddress, _LedgerKeyringV2_createKeyringAccount;
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.LedgerKeyringV2 = void 0;
|
|
10
|
+
const keyring_api_1 = require("@metamask/keyring-api");
|
|
11
|
+
const utils_1 = require("@metamask/utils");
|
|
12
|
+
/**
|
|
13
|
+
* Methods supported by Ledger keyring EOA accounts.
|
|
14
|
+
* Ledger keyrings support a subset of signing methods (no encryption, app keys, or EIP-7702).
|
|
15
|
+
*/
|
|
16
|
+
const LEDGER_KEYRING_METHODS = [
|
|
17
|
+
keyring_api_1.EthMethod.SignTransaction,
|
|
18
|
+
keyring_api_1.EthMethod.PersonalSign,
|
|
19
|
+
keyring_api_1.EthMethod.SignTypedDataV4,
|
|
20
|
+
];
|
|
21
|
+
const ledgerKeyringV2Capabilities = {
|
|
22
|
+
scopes: [keyring_api_1.EthScope.Eoa],
|
|
23
|
+
bip44: {
|
|
24
|
+
deriveIndex: true,
|
|
25
|
+
derivePath: true,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Ledger Live HD path constant.
|
|
30
|
+
*/
|
|
31
|
+
const LEDGER_LIVE_HD_PATH = `m/44'/60'/0'/0/0`;
|
|
32
|
+
/**
|
|
33
|
+
* BIP-44 standard HD path prefix constant for Ethereum.
|
|
34
|
+
*/
|
|
35
|
+
const BIP44_HD_PATH_PREFIX = `m/44'/60'/0'/0`;
|
|
36
|
+
/**
|
|
37
|
+
* Regex pattern for validating and parsing Ledger Live derivation paths.
|
|
38
|
+
* Format: m/44'/60'/{index}'/0/0
|
|
39
|
+
*/
|
|
40
|
+
const LEDGER_LIVE_PATH_PATTERN = /^m\/44'\/60'\/(\d+)'\/0\/0$/u;
|
|
41
|
+
/**
|
|
42
|
+
* Regex pattern for validating and parsing non-Ledger-Live derivation paths.
|
|
43
|
+
* Supports Legacy (m/44'/60'/0'/{index}), BIP44 (m/44'/60'/0'/0/{index}),
|
|
44
|
+
* and custom paths that follow the m/44'/60'/... pattern.
|
|
45
|
+
*/
|
|
46
|
+
const INDEX_AT_END_PATH_PATTERN = /^(m\/44'\/60'(?:\/\d+'?)*)\/(\d+)$/u;
|
|
47
|
+
class LedgerKeyringV2 extends keyring_api_1.EthKeyringWrapper {
|
|
48
|
+
constructor(options) {
|
|
49
|
+
super({
|
|
50
|
+
type: keyring_api_1.KeyringType.Ledger,
|
|
51
|
+
inner: options.legacyKeyring,
|
|
52
|
+
capabilities: ledgerKeyringV2Capabilities,
|
|
53
|
+
});
|
|
54
|
+
_LedgerKeyringV2_instances.add(this);
|
|
55
|
+
this.entropySource = options.entropySource;
|
|
56
|
+
}
|
|
57
|
+
async getAccounts() {
|
|
58
|
+
const addresses = await this.inner.getAccounts();
|
|
59
|
+
if (addresses.length === 0) {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
return addresses.map((address) => {
|
|
63
|
+
// Check if we already have this account in the registry
|
|
64
|
+
const existingId = this.registry.getAccountId(address);
|
|
65
|
+
if (existingId) {
|
|
66
|
+
const cached = this.registry.get(existingId);
|
|
67
|
+
if (cached) {
|
|
68
|
+
return cached;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const addressIndex = __classPrivateFieldGet(this, _LedgerKeyringV2_instances, "m", _LedgerKeyringV2_getIndexForAddress).call(this, address);
|
|
72
|
+
return __classPrivateFieldGet(this, _LedgerKeyringV2_instances, "m", _LedgerKeyringV2_createKeyringAccount).call(this, address, addressIndex);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
async createAccounts(options) {
|
|
76
|
+
return this.withLock(async () => {
|
|
77
|
+
if (options.type === 'bip44:derive-path' ||
|
|
78
|
+
options.type === 'bip44:derive-index') {
|
|
79
|
+
// Validate that the entropy source matches this keyring's entropy source
|
|
80
|
+
if (options.entropySource !== this.entropySource) {
|
|
81
|
+
throw new Error(`Entropy source mismatch: expected '${this.entropySource}', got '${options.entropySource}'`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
throw new Error(`Unsupported account creation type for LedgerKeyring: ${String(options.type)}`);
|
|
86
|
+
}
|
|
87
|
+
// Check if an account at this index already exists with the same derivation path
|
|
88
|
+
const currentAccounts = await this.getAccounts();
|
|
89
|
+
let targetIndex;
|
|
90
|
+
let basePath;
|
|
91
|
+
let derivationPath;
|
|
92
|
+
if (options.type === 'bip44:derive-path') {
|
|
93
|
+
// Parse the derivation path to extract base path and index
|
|
94
|
+
const parsed = __classPrivateFieldGet(this, _LedgerKeyringV2_instances, "m", _LedgerKeyringV2_parseDerivationPath).call(this, options.derivationPath);
|
|
95
|
+
targetIndex = parsed.index;
|
|
96
|
+
basePath = parsed.basePath;
|
|
97
|
+
derivationPath = options.derivationPath;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
// derive-index uses BIP-44 standard path by default
|
|
101
|
+
targetIndex = options.groupIndex;
|
|
102
|
+
basePath = BIP44_HD_PATH_PREFIX;
|
|
103
|
+
derivationPath = `${basePath}/${targetIndex}`;
|
|
104
|
+
}
|
|
105
|
+
const existingAccount = currentAccounts.find((account) => {
|
|
106
|
+
return (account.options.entropy.groupIndex === targetIndex &&
|
|
107
|
+
account.options.entropy.derivationPath === derivationPath);
|
|
108
|
+
});
|
|
109
|
+
if (existingAccount) {
|
|
110
|
+
return [existingAccount];
|
|
111
|
+
}
|
|
112
|
+
// Derive the account at the specified index
|
|
113
|
+
this.inner.setHdPath(basePath);
|
|
114
|
+
this.inner.setAccountToUnlock(targetIndex);
|
|
115
|
+
const [newAddress] = await this.inner.addAccounts(1);
|
|
116
|
+
if (!newAddress) {
|
|
117
|
+
throw new Error('Failed to create new account');
|
|
118
|
+
}
|
|
119
|
+
const newAccount = __classPrivateFieldGet(this, _LedgerKeyringV2_instances, "m", _LedgerKeyringV2_createKeyringAccount).call(this, newAddress, targetIndex);
|
|
120
|
+
return [newAccount];
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Delete an account from the keyring.
|
|
125
|
+
*
|
|
126
|
+
* @param accountId - The account ID to delete.
|
|
127
|
+
*/
|
|
128
|
+
async deleteAccount(accountId) {
|
|
129
|
+
await this.withLock(async () => {
|
|
130
|
+
const { address } = await this.getAccount(accountId);
|
|
131
|
+
const hexAddress = this.toHexAddress(address);
|
|
132
|
+
// Remove from the legacy keyring
|
|
133
|
+
this.inner.removeAccount(hexAddress);
|
|
134
|
+
// Remove from the registry
|
|
135
|
+
this.registry.delete(accountId);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
exports.LedgerKeyringV2 = LedgerKeyringV2;
|
|
140
|
+
_LedgerKeyringV2_instances = new WeakSet(), _LedgerKeyringV2_getChecksumHexAddress = function _LedgerKeyringV2_getChecksumHexAddress(address) {
|
|
141
|
+
return (0, utils_1.getChecksumAddress)((0, utils_1.add0x)(address));
|
|
142
|
+
}, _LedgerKeyringV2_parseDerivationPath = function _LedgerKeyringV2_parseDerivationPath(derivationPath) {
|
|
143
|
+
// Try Ledger Live format first: m/44'/60'/{index}'/0/0
|
|
144
|
+
const ledgerLiveMatch = derivationPath.match(LEDGER_LIVE_PATH_PATTERN);
|
|
145
|
+
if (ledgerLiveMatch?.[1]) {
|
|
146
|
+
return {
|
|
147
|
+
// This constant is used by `inner.setHdPath` to determine which derivation
|
|
148
|
+
// mode we should use (Ledger Live derivation mode here).
|
|
149
|
+
basePath: LEDGER_LIVE_HD_PATH,
|
|
150
|
+
index: parseInt(ledgerLiveMatch[1], 10),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
// Try index-at-end format: m/44'/60'/.../{index}
|
|
154
|
+
const indexAtEndMatch = derivationPath.match(INDEX_AT_END_PATH_PATTERN);
|
|
155
|
+
if (indexAtEndMatch) {
|
|
156
|
+
// If the condition is true, indexAtEndMatch[1] and indexAtEndMatch[2] are defined, so
|
|
157
|
+
// we can safely cast them to string.
|
|
158
|
+
// This is necessary to get 100% code coverage.
|
|
159
|
+
return {
|
|
160
|
+
// Here, we use a derivation path prefix for `inner.setHdPath`
|
|
161
|
+
// (prefix + index derivation mode).
|
|
162
|
+
basePath: indexAtEndMatch[1],
|
|
163
|
+
index: parseInt(indexAtEndMatch[2], 10),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
throw new Error(`Invalid derivation path format: ${derivationPath}. ` +
|
|
167
|
+
`Expected Ledger Live (m/44'/60'/{index}'/0/0) or index-at-end (m/44'/60'/.../{index}) format.`);
|
|
168
|
+
}, _LedgerKeyringV2_getIndexForAddress = function _LedgerKeyringV2_getIndexForAddress(address) {
|
|
169
|
+
const checksummedAddress = __classPrivateFieldGet(this, _LedgerKeyringV2_instances, "m", _LedgerKeyringV2_getChecksumHexAddress).call(this, address);
|
|
170
|
+
const details = this.inner.accountDetails[checksummedAddress];
|
|
171
|
+
if (!details) {
|
|
172
|
+
throw new Error(`Address ${checksummedAddress} not found in account details`);
|
|
173
|
+
}
|
|
174
|
+
// Extract index from hdPath
|
|
175
|
+
const { hdPath } = details;
|
|
176
|
+
if (!hdPath) {
|
|
177
|
+
throw new Error(`No HD path found for address ${checksummedAddress}`);
|
|
178
|
+
}
|
|
179
|
+
// Ledger supports multiple derivation path formats:
|
|
180
|
+
// - Ledger Live (bip44: true): m/44'/60'/{index}'/0/0 - index at position 3
|
|
181
|
+
// - Other paths (bip44: false): {hdPath}/{index} - index at end
|
|
182
|
+
// - BIP44: m/44'/60'/0'/0/{index}
|
|
183
|
+
// - Legacy: m/44'/60'/0'/{index}
|
|
184
|
+
// - Custom paths via setHdPath
|
|
185
|
+
//
|
|
186
|
+
// We use the `bip44` flag to determine which extraction pattern to use.
|
|
187
|
+
if (details.bip44) {
|
|
188
|
+
// Ledger Live format: m/44'/60'/{index}'/0/0
|
|
189
|
+
const match = hdPath.match(LEDGER_LIVE_PATH_PATTERN);
|
|
190
|
+
if (match?.[1]) {
|
|
191
|
+
return parseInt(match[1], 10);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
// Index-at-end format: m/44'/60'/.../{index}
|
|
196
|
+
const match = hdPath.match(INDEX_AT_END_PATH_PATTERN);
|
|
197
|
+
if (match?.[2]) {
|
|
198
|
+
return parseInt(match[2], 10);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
throw new Error(`Could not extract index from HD path: ${hdPath}`);
|
|
202
|
+
}, _LedgerKeyringV2_createKeyringAccount = function _LedgerKeyringV2_createKeyringAccount(address, addressIndex) {
|
|
203
|
+
const id = this.registry.register(address);
|
|
204
|
+
const checksummedAddress = __classPrivateFieldGet(this, _LedgerKeyringV2_instances, "m", _LedgerKeyringV2_getChecksumHexAddress).call(this, address);
|
|
205
|
+
const details = this.inner.accountDetails[checksummedAddress];
|
|
206
|
+
if (!details?.hdPath) {
|
|
207
|
+
throw new Error(`No HD path found for address ${checksummedAddress}. Cannot create account.`);
|
|
208
|
+
}
|
|
209
|
+
const account = {
|
|
210
|
+
id,
|
|
211
|
+
type: keyring_api_1.EthAccountType.Eoa,
|
|
212
|
+
address,
|
|
213
|
+
scopes: [...this.capabilities.scopes],
|
|
214
|
+
methods: [...LEDGER_KEYRING_METHODS],
|
|
215
|
+
options: {
|
|
216
|
+
entropy: {
|
|
217
|
+
type: keyring_api_1.KeyringAccountEntropyTypeOption.Mnemonic,
|
|
218
|
+
id: this.entropySource,
|
|
219
|
+
groupIndex: addressIndex,
|
|
220
|
+
derivationPath: details.hdPath,
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
this.registry.set(account);
|
|
225
|
+
return account;
|
|
226
|
+
};
|
|
227
|
+
//# sourceMappingURL=ledger-keyring-v2.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ledger-keyring-v2.cjs","sourceRoot":"","sources":["../src/ledger-keyring-v2.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Bip44Account } from '@metamask/account-api';\nimport {\n type CreateAccountOptions,\n EthAccountType,\n EthKeyringWrapper,\n EthMethod,\n EthScope,\n type KeyringAccount,\n KeyringAccountEntropyTypeOption,\n type KeyringCapabilities,\n type KeyringV2,\n KeyringType,\n type EntropySourceId,\n} from '@metamask/keyring-api';\nimport type { AccountId, EthKeyring } from '@metamask/keyring-utils';\nimport { add0x, getChecksumAddress, type Hex } from '@metamask/utils';\n\nimport type { LedgerKeyring } from './ledger-keyring';\n\n/**\n * Methods supported by Ledger keyring EOA accounts.\n * Ledger keyrings support a subset of signing methods (no encryption, app keys, or EIP-7702).\n */\nconst LEDGER_KEYRING_METHODS = [\n EthMethod.SignTransaction,\n EthMethod.PersonalSign,\n EthMethod.SignTypedDataV4,\n];\n\nconst ledgerKeyringV2Capabilities: KeyringCapabilities = {\n scopes: [EthScope.Eoa],\n bip44: {\n deriveIndex: true,\n derivePath: true,\n },\n};\n\n/**\n * Ledger Live HD path constant.\n */\nconst LEDGER_LIVE_HD_PATH = `m/44'/60'/0'/0/0`;\n\n/**\n * BIP-44 standard HD path prefix constant for Ethereum.\n */\nconst BIP44_HD_PATH_PREFIX = `m/44'/60'/0'/0`;\n\n/**\n * Regex pattern for validating and parsing Ledger Live derivation paths.\n * Format: m/44'/60'/{index}'/0/0\n */\nconst LEDGER_LIVE_PATH_PATTERN = /^m\\/44'\\/60'\\/(\\d+)'\\/0\\/0$/u;\n\n/**\n * Regex pattern for validating and parsing non-Ledger-Live derivation paths.\n * Supports Legacy (m/44'/60'/0'/{index}), BIP44 (m/44'/60'/0'/0/{index}),\n * and custom paths that follow the m/44'/60'/... pattern.\n */\nconst INDEX_AT_END_PATH_PATTERN = /^(m\\/44'\\/60'(?:\\/\\d+'?)*)\\/(\\d+)$/u;\n\n/**\n * Concrete {@link KeyringV2} adapter for {@link LedgerKeyring}.\n *\n * This wrapper exposes the accounts and signing capabilities of the legacy\n * Ledger keyring via the unified V2 interface.\n *\n * All Ledger keyring accounts are BIP-44 derived from the device.\n */\nexport type LedgerKeyringV2Options = {\n legacyKeyring: LedgerKeyring;\n entropySource: EntropySourceId;\n};\n\n// LedgerKeyring.signTransaction returns `TypedTransaction | OldEthJsTransaction` for\n// backwards compatibility with old ethereumjs-tx, but EthKeyring expects `TypedTxData`.\n// The runtime behavior is correct - we cast the type to satisfy the constraint.\ntype LedgerKeyringAsEthKeyring = LedgerKeyring & EthKeyring;\n\nexport class LedgerKeyringV2\n extends EthKeyringWrapper<\n LedgerKeyringAsEthKeyring,\n Bip44Account<KeyringAccount>\n >\n implements KeyringV2\n{\n readonly entropySource: EntropySourceId;\n\n constructor(options: LedgerKeyringV2Options) {\n super({\n type: KeyringType.Ledger,\n inner: options.legacyKeyring as LedgerKeyringAsEthKeyring,\n capabilities: ledgerKeyringV2Capabilities,\n });\n this.entropySource = options.entropySource;\n }\n\n /**\n * Normalizes an address to a checksummed hex address.\n *\n * @param address - The address to normalize.\n * @returns The checksummed hex address.\n */\n #getChecksumHexAddress(address: string): Hex {\n return getChecksumAddress(add0x(address));\n }\n\n /**\n * Parses a derivation path to extract the base HD path and account index.\n *\n * Supports two path formats:\n * - Ledger Live: m/44'/60'/{index}'/0/0 → base: m/44'/60'/0'/0/0, index from position 3\n * - Index at end: m/44'/60'/.../{index} → base: m/44'/60'/..., index from last segment\n *\n * @param derivationPath - The full derivation path.\n * @returns The base HD path and account index.\n * @throws If the path format is invalid.\n */\n #parseDerivationPath(derivationPath: string): {\n basePath: string;\n index: number;\n } {\n // Try Ledger Live format first: m/44'/60'/{index}'/0/0\n const ledgerLiveMatch = derivationPath.match(LEDGER_LIVE_PATH_PATTERN);\n if (ledgerLiveMatch?.[1]) {\n return {\n // This constant is used by `inner.setHdPath` to determine which derivation\n // mode we should use (Ledger Live derivation mode here).\n basePath: LEDGER_LIVE_HD_PATH,\n index: parseInt(ledgerLiveMatch[1], 10),\n };\n }\n\n // Try index-at-end format: m/44'/60'/.../{index}\n const indexAtEndMatch = derivationPath.match(INDEX_AT_END_PATH_PATTERN);\n if (indexAtEndMatch) {\n // If the condition is true, indexAtEndMatch[1] and indexAtEndMatch[2] are defined, so\n // we can safely cast them to string.\n // This is necessary to get 100% code coverage.\n return {\n // Here, we use a derivation path prefix for `inner.setHdPath`\n // (prefix + index derivation mode).\n basePath: indexAtEndMatch[1] as string,\n index: parseInt(indexAtEndMatch[2] as string, 10),\n };\n }\n\n throw new Error(\n `Invalid derivation path format: ${derivationPath}. ` +\n `Expected Ledger Live (m/44'/60'/{index}'/0/0) or index-at-end (m/44'/60'/.../{index}) format.`,\n );\n }\n\n /**\n * Gets the index for an address from the account details.\n *\n * @param address - The address to get the index for.\n * @returns The index for the address.\n * @throws If the address is not found in account details.\n */\n #getIndexForAddress(address: Hex): number {\n const checksummedAddress = this.#getChecksumHexAddress(address);\n const details = this.inner.accountDetails[checksummedAddress];\n\n if (!details) {\n throw new Error(\n `Address ${checksummedAddress} not found in account details`,\n );\n }\n\n // Extract index from hdPath\n const { hdPath } = details;\n if (!hdPath) {\n throw new Error(`No HD path found for address ${checksummedAddress}`);\n }\n\n // Ledger supports multiple derivation path formats:\n // - Ledger Live (bip44: true): m/44'/60'/{index}'/0/0 - index at position 3\n // - Other paths (bip44: false): {hdPath}/{index} - index at end\n // - BIP44: m/44'/60'/0'/0/{index}\n // - Legacy: m/44'/60'/0'/{index}\n // - Custom paths via setHdPath\n //\n // We use the `bip44` flag to determine which extraction pattern to use.\n if (details.bip44) {\n // Ledger Live format: m/44'/60'/{index}'/0/0\n const match = hdPath.match(LEDGER_LIVE_PATH_PATTERN);\n if (match?.[1]) {\n return parseInt(match[1], 10);\n }\n } else {\n // Index-at-end format: m/44'/60'/.../{index}\n const match = hdPath.match(INDEX_AT_END_PATH_PATTERN);\n if (match?.[2]) {\n return parseInt(match[2], 10);\n }\n }\n\n throw new Error(`Could not extract index from HD path: ${hdPath}`);\n }\n\n /**\n * Creates a Bip44Account object for the given address.\n *\n * @param address - The account address.\n * @param addressIndex - The account index in the derivation path.\n * @returns The created Bip44Account.\n */\n #createKeyringAccount(\n address: Hex,\n addressIndex: number,\n ): Bip44Account<KeyringAccount> {\n const id = this.registry.register(address);\n\n const checksummedAddress = this.#getChecksumHexAddress(address);\n const details = this.inner.accountDetails[checksummedAddress];\n\n if (!details?.hdPath) {\n throw new Error(\n `No HD path found for address ${checksummedAddress}. Cannot create account.`,\n );\n }\n\n const account: Bip44Account<KeyringAccount> = {\n id,\n type: EthAccountType.Eoa,\n address,\n scopes: [...this.capabilities.scopes],\n methods: [...LEDGER_KEYRING_METHODS],\n options: {\n entropy: {\n type: KeyringAccountEntropyTypeOption.Mnemonic,\n id: this.entropySource,\n groupIndex: addressIndex,\n derivationPath: details.hdPath,\n },\n },\n };\n\n this.registry.set(account);\n return account;\n }\n\n async getAccounts(): Promise<Bip44Account<KeyringAccount>[]> {\n const addresses = await this.inner.getAccounts();\n\n if (addresses.length === 0) {\n return [];\n }\n\n return addresses.map((address) => {\n // Check if we already have this account in the registry\n const existingId = this.registry.getAccountId(address);\n if (existingId) {\n const cached = this.registry.get(existingId);\n if (cached) {\n return cached;\n }\n }\n\n const addressIndex = this.#getIndexForAddress(address);\n return this.#createKeyringAccount(address, addressIndex);\n });\n }\n\n async createAccounts(\n options: CreateAccountOptions,\n ): Promise<Bip44Account<KeyringAccount>[]> {\n return this.withLock(async () => {\n if (\n options.type === 'bip44:derive-path' ||\n options.type === 'bip44:derive-index'\n ) {\n // Validate that the entropy source matches this keyring's entropy source\n if (options.entropySource !== this.entropySource) {\n throw new Error(\n `Entropy source mismatch: expected '${this.entropySource}', got '${options.entropySource}'`,\n );\n }\n } else {\n throw new Error(\n `Unsupported account creation type for LedgerKeyring: ${String(\n options.type,\n )}`,\n );\n }\n\n // Check if an account at this index already exists with the same derivation path\n const currentAccounts = await this.getAccounts();\n\n let targetIndex: number;\n let basePath: string;\n let derivationPath: string;\n\n if (options.type === 'bip44:derive-path') {\n // Parse the derivation path to extract base path and index\n const parsed = this.#parseDerivationPath(options.derivationPath);\n targetIndex = parsed.index;\n basePath = parsed.basePath;\n\n derivationPath = options.derivationPath;\n } else {\n // derive-index uses BIP-44 standard path by default\n targetIndex = options.groupIndex;\n basePath = BIP44_HD_PATH_PREFIX;\n derivationPath = `${basePath}/${targetIndex}`;\n }\n\n const existingAccount = currentAccounts.find((account) => {\n return (\n account.options.entropy.groupIndex === targetIndex &&\n account.options.entropy.derivationPath === derivationPath\n );\n });\n\n if (existingAccount) {\n return [existingAccount];\n }\n\n // Derive the account at the specified index\n this.inner.setHdPath(basePath);\n this.inner.setAccountToUnlock(targetIndex);\n const [newAddress] = await this.inner.addAccounts(1);\n\n if (!newAddress) {\n throw new Error('Failed to create new account');\n }\n\n const newAccount = this.#createKeyringAccount(newAddress, targetIndex);\n\n return [newAccount];\n });\n }\n\n /**\n * Delete an account from the keyring.\n *\n * @param accountId - The account ID to delete.\n */\n async deleteAccount(accountId: AccountId): Promise<void> {\n await this.withLock(async () => {\n const { address } = await this.getAccount(accountId);\n const hexAddress = this.toHexAddress(address);\n\n // Remove from the legacy keyring\n this.inner.removeAccount(hexAddress);\n\n // Remove from the registry\n this.registry.delete(accountId);\n });\n }\n}\n"]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Bip44Account } from "@metamask/account-api";
|
|
2
|
+
import { type CreateAccountOptions, EthKeyringWrapper, type KeyringAccount, type KeyringV2, type EntropySourceId } from "@metamask/keyring-api";
|
|
3
|
+
import type { AccountId, EthKeyring } from "@metamask/keyring-utils";
|
|
4
|
+
import type { LedgerKeyring } from "./ledger-keyring.cjs";
|
|
5
|
+
/**
|
|
6
|
+
* Concrete {@link KeyringV2} adapter for {@link LedgerKeyring}.
|
|
7
|
+
*
|
|
8
|
+
* This wrapper exposes the accounts and signing capabilities of the legacy
|
|
9
|
+
* Ledger keyring via the unified V2 interface.
|
|
10
|
+
*
|
|
11
|
+
* All Ledger keyring accounts are BIP-44 derived from the device.
|
|
12
|
+
*/
|
|
13
|
+
export type LedgerKeyringV2Options = {
|
|
14
|
+
legacyKeyring: LedgerKeyring;
|
|
15
|
+
entropySource: EntropySourceId;
|
|
16
|
+
};
|
|
17
|
+
type LedgerKeyringAsEthKeyring = LedgerKeyring & EthKeyring;
|
|
18
|
+
export declare class LedgerKeyringV2 extends EthKeyringWrapper<LedgerKeyringAsEthKeyring, Bip44Account<KeyringAccount>> implements KeyringV2 {
|
|
19
|
+
#private;
|
|
20
|
+
readonly entropySource: EntropySourceId;
|
|
21
|
+
constructor(options: LedgerKeyringV2Options);
|
|
22
|
+
getAccounts(): Promise<Bip44Account<KeyringAccount>[]>;
|
|
23
|
+
createAccounts(options: CreateAccountOptions): Promise<Bip44Account<KeyringAccount>[]>;
|
|
24
|
+
/**
|
|
25
|
+
* Delete an account from the keyring.
|
|
26
|
+
*
|
|
27
|
+
* @param accountId - The account ID to delete.
|
|
28
|
+
*/
|
|
29
|
+
deleteAccount(accountId: AccountId): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
export {};
|
|
32
|
+
//# sourceMappingURL=ledger-keyring-v2.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ledger-keyring-v2.d.cts","sourceRoot":"","sources":["../src/ledger-keyring-v2.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,8BAA8B;AAC1D,OAAO,EACL,KAAK,oBAAoB,EAEzB,iBAAiB,EAGjB,KAAK,cAAc,EAGnB,KAAK,SAAS,EAEd,KAAK,eAAe,EACrB,8BAA8B;AAC/B,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,gCAAgC;AAGrE,OAAO,KAAK,EAAE,aAAa,EAAE,6BAAyB;AA2CtD;;;;;;;GAOG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC,aAAa,EAAE,aAAa,CAAC;IAC7B,aAAa,EAAE,eAAe,CAAC;CAChC,CAAC;AAKF,KAAK,yBAAyB,GAAG,aAAa,GAAG,UAAU,CAAC;AAE5D,qBAAa,eACX,SAAQ,iBAAiB,CACvB,yBAAyB,EACzB,YAAY,CAAC,cAAc,CAAC,CAE9B,YAAW,SAAS;;IAEpB,QAAQ,CAAC,aAAa,EAAE,eAAe,CAAC;gBAE5B,OAAO,EAAE,sBAAsB;IA2JrC,WAAW,IAAI,OAAO,CAAC,YAAY,CAAC,cAAc,CAAC,EAAE,CAAC;IAsBtD,cAAc,CAClB,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,YAAY,CAAC,cAAc,CAAC,EAAE,CAAC;IAmE1C;;;;OAIG;IACG,aAAa,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;CAYzD"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Bip44Account } from "@metamask/account-api";
|
|
2
|
+
import { type CreateAccountOptions, EthKeyringWrapper, type KeyringAccount, type KeyringV2, type EntropySourceId } from "@metamask/keyring-api";
|
|
3
|
+
import type { AccountId, EthKeyring } from "@metamask/keyring-utils";
|
|
4
|
+
import type { LedgerKeyring } from "./ledger-keyring.mjs";
|
|
5
|
+
/**
|
|
6
|
+
* Concrete {@link KeyringV2} adapter for {@link LedgerKeyring}.
|
|
7
|
+
*
|
|
8
|
+
* This wrapper exposes the accounts and signing capabilities of the legacy
|
|
9
|
+
* Ledger keyring via the unified V2 interface.
|
|
10
|
+
*
|
|
11
|
+
* All Ledger keyring accounts are BIP-44 derived from the device.
|
|
12
|
+
*/
|
|
13
|
+
export type LedgerKeyringV2Options = {
|
|
14
|
+
legacyKeyring: LedgerKeyring;
|
|
15
|
+
entropySource: EntropySourceId;
|
|
16
|
+
};
|
|
17
|
+
type LedgerKeyringAsEthKeyring = LedgerKeyring & EthKeyring;
|
|
18
|
+
export declare class LedgerKeyringV2 extends EthKeyringWrapper<LedgerKeyringAsEthKeyring, Bip44Account<KeyringAccount>> implements KeyringV2 {
|
|
19
|
+
#private;
|
|
20
|
+
readonly entropySource: EntropySourceId;
|
|
21
|
+
constructor(options: LedgerKeyringV2Options);
|
|
22
|
+
getAccounts(): Promise<Bip44Account<KeyringAccount>[]>;
|
|
23
|
+
createAccounts(options: CreateAccountOptions): Promise<Bip44Account<KeyringAccount>[]>;
|
|
24
|
+
/**
|
|
25
|
+
* Delete an account from the keyring.
|
|
26
|
+
*
|
|
27
|
+
* @param accountId - The account ID to delete.
|
|
28
|
+
*/
|
|
29
|
+
deleteAccount(accountId: AccountId): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
export {};
|
|
32
|
+
//# sourceMappingURL=ledger-keyring-v2.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ledger-keyring-v2.d.mts","sourceRoot":"","sources":["../src/ledger-keyring-v2.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,8BAA8B;AAC1D,OAAO,EACL,KAAK,oBAAoB,EAEzB,iBAAiB,EAGjB,KAAK,cAAc,EAGnB,KAAK,SAAS,EAEd,KAAK,eAAe,EACrB,8BAA8B;AAC/B,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,gCAAgC;AAGrE,OAAO,KAAK,EAAE,aAAa,EAAE,6BAAyB;AA2CtD;;;;;;;GAOG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC,aAAa,EAAE,aAAa,CAAC;IAC7B,aAAa,EAAE,eAAe,CAAC;CAChC,CAAC;AAKF,KAAK,yBAAyB,GAAG,aAAa,GAAG,UAAU,CAAC;AAE5D,qBAAa,eACX,SAAQ,iBAAiB,CACvB,yBAAyB,EACzB,YAAY,CAAC,cAAc,CAAC,CAE9B,YAAW,SAAS;;IAEpB,QAAQ,CAAC,aAAa,EAAE,eAAe,CAAC;gBAE5B,OAAO,EAAE,sBAAsB;IA2JrC,WAAW,IAAI,OAAO,CAAC,YAAY,CAAC,cAAc,CAAC,EAAE,CAAC;IAsBtD,cAAc,CAClB,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,YAAY,CAAC,cAAc,CAAC,EAAE,CAAC;IAmE1C;;;;OAIG;IACG,aAAa,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;CAYzD"}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
2
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
3
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
4
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
5
|
+
};
|
|
6
|
+
var _LedgerKeyringV2_instances, _LedgerKeyringV2_getChecksumHexAddress, _LedgerKeyringV2_parseDerivationPath, _LedgerKeyringV2_getIndexForAddress, _LedgerKeyringV2_createKeyringAccount;
|
|
7
|
+
import { EthAccountType, EthKeyringWrapper, EthMethod, EthScope, KeyringAccountEntropyTypeOption, KeyringType } from "@metamask/keyring-api";
|
|
8
|
+
import { add0x, getChecksumAddress } from "@metamask/utils";
|
|
9
|
+
/**
|
|
10
|
+
* Methods supported by Ledger keyring EOA accounts.
|
|
11
|
+
* Ledger keyrings support a subset of signing methods (no encryption, app keys, or EIP-7702).
|
|
12
|
+
*/
|
|
13
|
+
const LEDGER_KEYRING_METHODS = [
|
|
14
|
+
EthMethod.SignTransaction,
|
|
15
|
+
EthMethod.PersonalSign,
|
|
16
|
+
EthMethod.SignTypedDataV4,
|
|
17
|
+
];
|
|
18
|
+
const ledgerKeyringV2Capabilities = {
|
|
19
|
+
scopes: [EthScope.Eoa],
|
|
20
|
+
bip44: {
|
|
21
|
+
deriveIndex: true,
|
|
22
|
+
derivePath: true,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Ledger Live HD path constant.
|
|
27
|
+
*/
|
|
28
|
+
const LEDGER_LIVE_HD_PATH = `m/44'/60'/0'/0/0`;
|
|
29
|
+
/**
|
|
30
|
+
* BIP-44 standard HD path prefix constant for Ethereum.
|
|
31
|
+
*/
|
|
32
|
+
const BIP44_HD_PATH_PREFIX = `m/44'/60'/0'/0`;
|
|
33
|
+
/**
|
|
34
|
+
* Regex pattern for validating and parsing Ledger Live derivation paths.
|
|
35
|
+
* Format: m/44'/60'/{index}'/0/0
|
|
36
|
+
*/
|
|
37
|
+
const LEDGER_LIVE_PATH_PATTERN = /^m\/44'\/60'\/(\d+)'\/0\/0$/u;
|
|
38
|
+
/**
|
|
39
|
+
* Regex pattern for validating and parsing non-Ledger-Live derivation paths.
|
|
40
|
+
* Supports Legacy (m/44'/60'/0'/{index}), BIP44 (m/44'/60'/0'/0/{index}),
|
|
41
|
+
* and custom paths that follow the m/44'/60'/... pattern.
|
|
42
|
+
*/
|
|
43
|
+
const INDEX_AT_END_PATH_PATTERN = /^(m\/44'\/60'(?:\/\d+'?)*)\/(\d+)$/u;
|
|
44
|
+
export class LedgerKeyringV2 extends EthKeyringWrapper {
|
|
45
|
+
constructor(options) {
|
|
46
|
+
super({
|
|
47
|
+
type: KeyringType.Ledger,
|
|
48
|
+
inner: options.legacyKeyring,
|
|
49
|
+
capabilities: ledgerKeyringV2Capabilities,
|
|
50
|
+
});
|
|
51
|
+
_LedgerKeyringV2_instances.add(this);
|
|
52
|
+
this.entropySource = options.entropySource;
|
|
53
|
+
}
|
|
54
|
+
async getAccounts() {
|
|
55
|
+
const addresses = await this.inner.getAccounts();
|
|
56
|
+
if (addresses.length === 0) {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
return addresses.map((address) => {
|
|
60
|
+
// Check if we already have this account in the registry
|
|
61
|
+
const existingId = this.registry.getAccountId(address);
|
|
62
|
+
if (existingId) {
|
|
63
|
+
const cached = this.registry.get(existingId);
|
|
64
|
+
if (cached) {
|
|
65
|
+
return cached;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const addressIndex = __classPrivateFieldGet(this, _LedgerKeyringV2_instances, "m", _LedgerKeyringV2_getIndexForAddress).call(this, address);
|
|
69
|
+
return __classPrivateFieldGet(this, _LedgerKeyringV2_instances, "m", _LedgerKeyringV2_createKeyringAccount).call(this, address, addressIndex);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async createAccounts(options) {
|
|
73
|
+
return this.withLock(async () => {
|
|
74
|
+
if (options.type === 'bip44:derive-path' ||
|
|
75
|
+
options.type === 'bip44:derive-index') {
|
|
76
|
+
// Validate that the entropy source matches this keyring's entropy source
|
|
77
|
+
if (options.entropySource !== this.entropySource) {
|
|
78
|
+
throw new Error(`Entropy source mismatch: expected '${this.entropySource}', got '${options.entropySource}'`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
throw new Error(`Unsupported account creation type for LedgerKeyring: ${String(options.type)}`);
|
|
83
|
+
}
|
|
84
|
+
// Check if an account at this index already exists with the same derivation path
|
|
85
|
+
const currentAccounts = await this.getAccounts();
|
|
86
|
+
let targetIndex;
|
|
87
|
+
let basePath;
|
|
88
|
+
let derivationPath;
|
|
89
|
+
if (options.type === 'bip44:derive-path') {
|
|
90
|
+
// Parse the derivation path to extract base path and index
|
|
91
|
+
const parsed = __classPrivateFieldGet(this, _LedgerKeyringV2_instances, "m", _LedgerKeyringV2_parseDerivationPath).call(this, options.derivationPath);
|
|
92
|
+
targetIndex = parsed.index;
|
|
93
|
+
basePath = parsed.basePath;
|
|
94
|
+
derivationPath = options.derivationPath;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
// derive-index uses BIP-44 standard path by default
|
|
98
|
+
targetIndex = options.groupIndex;
|
|
99
|
+
basePath = BIP44_HD_PATH_PREFIX;
|
|
100
|
+
derivationPath = `${basePath}/${targetIndex}`;
|
|
101
|
+
}
|
|
102
|
+
const existingAccount = currentAccounts.find((account) => {
|
|
103
|
+
return (account.options.entropy.groupIndex === targetIndex &&
|
|
104
|
+
account.options.entropy.derivationPath === derivationPath);
|
|
105
|
+
});
|
|
106
|
+
if (existingAccount) {
|
|
107
|
+
return [existingAccount];
|
|
108
|
+
}
|
|
109
|
+
// Derive the account at the specified index
|
|
110
|
+
this.inner.setHdPath(basePath);
|
|
111
|
+
this.inner.setAccountToUnlock(targetIndex);
|
|
112
|
+
const [newAddress] = await this.inner.addAccounts(1);
|
|
113
|
+
if (!newAddress) {
|
|
114
|
+
throw new Error('Failed to create new account');
|
|
115
|
+
}
|
|
116
|
+
const newAccount = __classPrivateFieldGet(this, _LedgerKeyringV2_instances, "m", _LedgerKeyringV2_createKeyringAccount).call(this, newAddress, targetIndex);
|
|
117
|
+
return [newAccount];
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Delete an account from the keyring.
|
|
122
|
+
*
|
|
123
|
+
* @param accountId - The account ID to delete.
|
|
124
|
+
*/
|
|
125
|
+
async deleteAccount(accountId) {
|
|
126
|
+
await this.withLock(async () => {
|
|
127
|
+
const { address } = await this.getAccount(accountId);
|
|
128
|
+
const hexAddress = this.toHexAddress(address);
|
|
129
|
+
// Remove from the legacy keyring
|
|
130
|
+
this.inner.removeAccount(hexAddress);
|
|
131
|
+
// Remove from the registry
|
|
132
|
+
this.registry.delete(accountId);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
_LedgerKeyringV2_instances = new WeakSet(), _LedgerKeyringV2_getChecksumHexAddress = function _LedgerKeyringV2_getChecksumHexAddress(address) {
|
|
137
|
+
return getChecksumAddress(add0x(address));
|
|
138
|
+
}, _LedgerKeyringV2_parseDerivationPath = function _LedgerKeyringV2_parseDerivationPath(derivationPath) {
|
|
139
|
+
// Try Ledger Live format first: m/44'/60'/{index}'/0/0
|
|
140
|
+
const ledgerLiveMatch = derivationPath.match(LEDGER_LIVE_PATH_PATTERN);
|
|
141
|
+
if (ledgerLiveMatch?.[1]) {
|
|
142
|
+
return {
|
|
143
|
+
// This constant is used by `inner.setHdPath` to determine which derivation
|
|
144
|
+
// mode we should use (Ledger Live derivation mode here).
|
|
145
|
+
basePath: LEDGER_LIVE_HD_PATH,
|
|
146
|
+
index: parseInt(ledgerLiveMatch[1], 10),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
// Try index-at-end format: m/44'/60'/.../{index}
|
|
150
|
+
const indexAtEndMatch = derivationPath.match(INDEX_AT_END_PATH_PATTERN);
|
|
151
|
+
if (indexAtEndMatch) {
|
|
152
|
+
// If the condition is true, indexAtEndMatch[1] and indexAtEndMatch[2] are defined, so
|
|
153
|
+
// we can safely cast them to string.
|
|
154
|
+
// This is necessary to get 100% code coverage.
|
|
155
|
+
return {
|
|
156
|
+
// Here, we use a derivation path prefix for `inner.setHdPath`
|
|
157
|
+
// (prefix + index derivation mode).
|
|
158
|
+
basePath: indexAtEndMatch[1],
|
|
159
|
+
index: parseInt(indexAtEndMatch[2], 10),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
throw new Error(`Invalid derivation path format: ${derivationPath}. ` +
|
|
163
|
+
`Expected Ledger Live (m/44'/60'/{index}'/0/0) or index-at-end (m/44'/60'/.../{index}) format.`);
|
|
164
|
+
}, _LedgerKeyringV2_getIndexForAddress = function _LedgerKeyringV2_getIndexForAddress(address) {
|
|
165
|
+
const checksummedAddress = __classPrivateFieldGet(this, _LedgerKeyringV2_instances, "m", _LedgerKeyringV2_getChecksumHexAddress).call(this, address);
|
|
166
|
+
const details = this.inner.accountDetails[checksummedAddress];
|
|
167
|
+
if (!details) {
|
|
168
|
+
throw new Error(`Address ${checksummedAddress} not found in account details`);
|
|
169
|
+
}
|
|
170
|
+
// Extract index from hdPath
|
|
171
|
+
const { hdPath } = details;
|
|
172
|
+
if (!hdPath) {
|
|
173
|
+
throw new Error(`No HD path found for address ${checksummedAddress}`);
|
|
174
|
+
}
|
|
175
|
+
// Ledger supports multiple derivation path formats:
|
|
176
|
+
// - Ledger Live (bip44: true): m/44'/60'/{index}'/0/0 - index at position 3
|
|
177
|
+
// - Other paths (bip44: false): {hdPath}/{index} - index at end
|
|
178
|
+
// - BIP44: m/44'/60'/0'/0/{index}
|
|
179
|
+
// - Legacy: m/44'/60'/0'/{index}
|
|
180
|
+
// - Custom paths via setHdPath
|
|
181
|
+
//
|
|
182
|
+
// We use the `bip44` flag to determine which extraction pattern to use.
|
|
183
|
+
if (details.bip44) {
|
|
184
|
+
// Ledger Live format: m/44'/60'/{index}'/0/0
|
|
185
|
+
const match = hdPath.match(LEDGER_LIVE_PATH_PATTERN);
|
|
186
|
+
if (match?.[1]) {
|
|
187
|
+
return parseInt(match[1], 10);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
// Index-at-end format: m/44'/60'/.../{index}
|
|
192
|
+
const match = hdPath.match(INDEX_AT_END_PATH_PATTERN);
|
|
193
|
+
if (match?.[2]) {
|
|
194
|
+
return parseInt(match[2], 10);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
throw new Error(`Could not extract index from HD path: ${hdPath}`);
|
|
198
|
+
}, _LedgerKeyringV2_createKeyringAccount = function _LedgerKeyringV2_createKeyringAccount(address, addressIndex) {
|
|
199
|
+
const id = this.registry.register(address);
|
|
200
|
+
const checksummedAddress = __classPrivateFieldGet(this, _LedgerKeyringV2_instances, "m", _LedgerKeyringV2_getChecksumHexAddress).call(this, address);
|
|
201
|
+
const details = this.inner.accountDetails[checksummedAddress];
|
|
202
|
+
if (!details?.hdPath) {
|
|
203
|
+
throw new Error(`No HD path found for address ${checksummedAddress}. Cannot create account.`);
|
|
204
|
+
}
|
|
205
|
+
const account = {
|
|
206
|
+
id,
|
|
207
|
+
type: EthAccountType.Eoa,
|
|
208
|
+
address,
|
|
209
|
+
scopes: [...this.capabilities.scopes],
|
|
210
|
+
methods: [...LEDGER_KEYRING_METHODS],
|
|
211
|
+
options: {
|
|
212
|
+
entropy: {
|
|
213
|
+
type: KeyringAccountEntropyTypeOption.Mnemonic,
|
|
214
|
+
id: this.entropySource,
|
|
215
|
+
groupIndex: addressIndex,
|
|
216
|
+
derivationPath: details.hdPath,
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
this.registry.set(account);
|
|
221
|
+
return account;
|
|
222
|
+
};
|
|
223
|
+
//# sourceMappingURL=ledger-keyring-v2.mjs.map
|