@metamask/eth-hd-keyring 12.1.0 → 13.1.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 CHANGED
@@ -7,6 +7,44 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [13.1.0]
11
+
12
+ ### Added
13
+
14
+ - Add `HdKeyringV2` class implementing `KeyringV2` interface ([#398](https://github.com/MetaMask/accounts/pull/398)), ([#402](https://github.com/MetaMask/accounts/pull/402)), ([#404](https://github.com/MetaMask/accounts/pull/404)), ([#410](https://github.com/MetaMask/accounts/pull/410)), ([#413](https://github.com/MetaMask/accounts/pull/413)), ([#451](https://github.com/MetaMask/accounts/pull/451)), ([#453](https://github.com/MetaMask/accounts/pull/453))
15
+ - Wraps legacy `HdKeyring` to expose accounts via the unified `KeyringV2` API and the `KeyringAccount` type.
16
+ - Extends `EthKeyringWrapper` for common Ethereum logic.
17
+
18
+ ### Fixed
19
+
20
+ - Enforce mnemonics validation ([#450](https://github.com/MetaMask/accounts/pull/450))
21
+ - Validates mnemonics against BIP39 specification (word count, wordlist, checksum) before use.
22
+ - Throws for invalid mnemonics.
23
+
24
+ ## [13.0.0]
25
+
26
+ ### Added
27
+
28
+ - Export types `SerializedHDKeyringState` and `DeserializableHDKeyringState` ([#255](https://github.com/MetaMask/accounts/pull/255))
29
+
30
+ ### Changed
31
+
32
+ - **BREAKING:** The method signature for `signTypedData` has been changed ([#224](https://github.com/MetaMask/accounts/pull/224))
33
+ - The method now accepts a `TypedDataV1` object when `SignTypedDataVersion.V1` is passed in the options, and `TypedMessage<Types>` when other versions are requested.
34
+ - **BREAKING:** The `HdKeyring` class now extends `Keyring` from `@metamask/keyring-utils` ([#255](https://github.com/MetaMask/accounts/pull/255))
35
+ - The `deserialize` method does not accept `Buffer` mnemonic anymore
36
+ - The options argument to `deserialize` is no longer optional
37
+ - `getAccounts` is now `async`
38
+ - Switched to using a `Map` for wallet storage ([#374](https://github.com/metamask/accounts/pull/374))
39
+ - Improves performance of account lookup and removal to constant time
40
+ - Cache derived account addresses ([#373](https://github.com/metamask/accounts/pull/373))
41
+ - Avoids recomputing addresses from public keys on every `getAccounts()` call
42
+
43
+ ### Removed
44
+
45
+ - **BREAKING:** `HDKeyringState` type is no longer exported ([#255](https://github.com/MetaMask/accounts/pull/255))
46
+ - This has effectively been replaced with `SerializedHDKeyringState` and `DeserializableHDKeyringState`
47
+
10
48
  ## [12.1.0]
11
49
 
12
50
  ### Added
@@ -197,7 +235,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
197
235
  - Deserialize method (and `HdKeyring` constructor by extension) can no longer be passed an options object containing a value for `numberOfAccounts` if it is not also containing a value for `mnemonic`.
198
236
  - Package name changed from `eth-hd-keyring` to `@metamask/eth-hd-keyring`.
199
237
 
200
- [Unreleased]: https://github.com/MetaMask/accounts/compare/@metamask/eth-hd-keyring@12.1.0...HEAD
238
+ [Unreleased]: https://github.com/MetaMask/accounts/compare/@metamask/eth-hd-keyring@13.1.0...HEAD
239
+ [13.1.0]: https://github.com/MetaMask/accounts/compare/@metamask/eth-hd-keyring@13.0.0...@metamask/eth-hd-keyring@13.1.0
240
+ [13.0.0]: https://github.com/MetaMask/accounts/compare/@metamask/eth-hd-keyring@12.1.0...@metamask/eth-hd-keyring@13.0.0
201
241
  [12.1.0]: https://github.com/MetaMask/accounts/compare/@metamask/eth-hd-keyring@12.0.0...@metamask/eth-hd-keyring@12.1.0
202
242
  [12.0.0]: https://github.com/MetaMask/accounts/compare/@metamask/eth-hd-keyring@11.0.0...@metamask/eth-hd-keyring@12.0.0
203
243
  [11.0.0]: https://github.com/MetaMask/accounts/compare/@metamask/eth-hd-keyring@10.0.1...@metamask/eth-hd-keyring@11.0.0
@@ -0,0 +1,180 @@
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 _HdKeyringV2_instances, _HdKeyringV2_isLastAccount, _HdKeyringV2_createKeyringAccount;
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.HdKeyringV2 = void 0;
10
+ const keyring_api_1 = require("@metamask/keyring-api");
11
+ const utils_1 = require("@metamask/utils");
12
+ /**
13
+ * Methods supported by HD keyring EOA accounts.
14
+ * HD keyrings support all standard signing methods plus encryption and app keys.
15
+ */
16
+ const HD_KEYRING_METHODS = [
17
+ keyring_api_1.EthMethod.SignTransaction,
18
+ keyring_api_1.EthMethod.Sign,
19
+ keyring_api_1.EthMethod.PersonalSign,
20
+ keyring_api_1.EthMethod.SignTypedDataV1,
21
+ keyring_api_1.EthMethod.SignTypedDataV3,
22
+ keyring_api_1.EthMethod.SignTypedDataV4,
23
+ keyring_api_1.EthKeyringMethod.Decrypt,
24
+ keyring_api_1.EthKeyringMethod.GetEncryptionPublicKey,
25
+ keyring_api_1.EthKeyringMethod.GetAppKeyAddress,
26
+ keyring_api_1.EthKeyringMethod.SignEip7702Authorization,
27
+ ];
28
+ const hdKeyringV2Capabilities = {
29
+ scopes: [keyring_api_1.EthScope.Eoa],
30
+ bip44: {
31
+ deriveIndex: true,
32
+ },
33
+ privateKey: {
34
+ exportFormats: [{ encoding: keyring_api_1.PrivateKeyEncoding.Hexadecimal }],
35
+ },
36
+ };
37
+ class HdKeyringV2 extends keyring_api_1.EthKeyringWrapper {
38
+ constructor(options) {
39
+ super({
40
+ type: keyring_api_1.KeyringType.Hd,
41
+ inner: options.legacyKeyring,
42
+ capabilities: hdKeyringV2Capabilities,
43
+ });
44
+ _HdKeyringV2_instances.add(this);
45
+ this.entropySource = options.entropySource;
46
+ }
47
+ async getAccounts() {
48
+ const addresses = await this.inner.getAccounts();
49
+ return addresses.map((address, addressIndex) => {
50
+ // Check if we already have this account in the registry
51
+ const existingId = this.registry.getAccountId(address);
52
+ if (existingId) {
53
+ const cached = this.registry.get(existingId);
54
+ if (cached) {
55
+ return cached;
56
+ }
57
+ }
58
+ // Create and register the account if not already cached
59
+ return __classPrivateFieldGet(this, _HdKeyringV2_instances, "m", _HdKeyringV2_createKeyringAccount).call(this, address, addressIndex);
60
+ });
61
+ }
62
+ async createAccounts(options) {
63
+ return this.withLock(async () => {
64
+ // For HD keyring, we only support BIP-44 derive index
65
+ if (options.type !== 'bip44:derive-index') {
66
+ throw new Error(`Unsupported account creation type for HdKeyring: ${options.type}`);
67
+ }
68
+ // Validate that the entropy source matches this keyring's entropy source
69
+ if (options.entropySource !== this.entropySource) {
70
+ throw new Error(`Entropy source mismatch: expected '${this.entropySource}', got '${options.entropySource}'`);
71
+ }
72
+ // Sync with the inner keyring state in case it was modified externally
73
+ // This ensures our cache is up-to-date before we make changes
74
+ const currentAccounts = await this.getAccounts();
75
+ const currentCount = currentAccounts.length;
76
+ const targetIndex = options.groupIndex;
77
+ // Check if an account at this index already exists
78
+ // Since only the last account can be deleted, array position always equals groupIndex
79
+ const existingAccount = currentAccounts[targetIndex];
80
+ if (existingAccount) {
81
+ return [existingAccount];
82
+ }
83
+ // Only allow derivation of the next account in sequence
84
+ if (targetIndex !== currentCount) {
85
+ throw new Error(`Can only create the next account in sequence. ` +
86
+ `Expected groupIndex ${currentCount}, got ${targetIndex}.`);
87
+ }
88
+ // Add the next account
89
+ const [newAddress] = await this.inner.addAccounts(1);
90
+ if (!newAddress) {
91
+ throw new Error('Failed to create new account');
92
+ }
93
+ const newAccount = __classPrivateFieldGet(this, _HdKeyringV2_instances, "m", _HdKeyringV2_createKeyringAccount).call(this, newAddress, targetIndex);
94
+ return [newAccount];
95
+ });
96
+ }
97
+ /**
98
+ * Delete an account from the keyring.
99
+ *
100
+ * ⚠️ Warning: Only deleting the last account is possible.
101
+ *
102
+ * @param accountId - The account ID to delete.
103
+ */
104
+ async deleteAccount(accountId) {
105
+ await this.withLock(async () => {
106
+ // Get the account first, before any registry operations
107
+ const { address } = await this.getAccount(accountId);
108
+ const hexAddress = this.toHexAddress(address);
109
+ // Assert that the account to delete is the last one in the inner keyring
110
+ // We check against the inner keyring directly to avoid stale registry issues
111
+ if (!(await __classPrivateFieldGet(this, _HdKeyringV2_instances, "m", _HdKeyringV2_isLastAccount).call(this, address))) {
112
+ throw new Error('Can only delete the last account in the HD keyring due to derivation index constraints.');
113
+ }
114
+ // Remove from the legacy keyring
115
+ this.inner.removeAccount(hexAddress);
116
+ // Remove from the registry
117
+ this.registry.delete(accountId);
118
+ });
119
+ }
120
+ /**
121
+ * Export the private key for an account in hexadecimal format.
122
+ *
123
+ * @param accountId - The ID of the account to export.
124
+ * @param options - Export options (only hexadecimal encoding is supported).
125
+ * @returns The exported account with private key.
126
+ */
127
+ async exportAccount(accountId, options) {
128
+ const account = await this.getAccount(accountId);
129
+ // Validate encoding - we only support hexadecimal for Ethereum keys
130
+ const requestedEncoding = options?.encoding ?? keyring_api_1.PrivateKeyEncoding.Hexadecimal;
131
+ if (requestedEncoding !== keyring_api_1.PrivateKeyEncoding.Hexadecimal) {
132
+ throw new Error(`Unsupported encoding for Ethereum HD keyring: ${requestedEncoding}. Only '${keyring_api_1.PrivateKeyEncoding.Hexadecimal}' is supported.`);
133
+ }
134
+ // The legacy HdKeyring returns a hex string without 0x prefix.
135
+ const privateKeyWithout0x = await this.inner.exportAccount(this.toHexAddress(account.address));
136
+ const privateKey = (0, utils_1.add0x)(privateKeyWithout0x);
137
+ return {
138
+ type: 'private-key',
139
+ privateKey,
140
+ encoding: keyring_api_1.PrivateKeyEncoding.Hexadecimal,
141
+ };
142
+ }
143
+ }
144
+ exports.HdKeyringV2 = HdKeyringV2;
145
+ _HdKeyringV2_instances = new WeakSet(), _HdKeyringV2_isLastAccount =
146
+ /**
147
+ * Checks if the given address is the last account in the inner keyring.
148
+ * This compares against the actual inner keyring state, not the registry,
149
+ * to avoid issues with stale registry entries.
150
+ *
151
+ * @param address - The address to check.
152
+ * @returns True if this is the last account in the inner keyring.
153
+ */
154
+ async function _HdKeyringV2_isLastAccount(address) {
155
+ const innerAddresses = await this.inner.getAccounts();
156
+ const lastAddress = innerAddresses[innerAddresses.length - 1];
157
+ return address === lastAddress;
158
+ }, _HdKeyringV2_createKeyringAccount = function _HdKeyringV2_createKeyringAccount(address, addressIndex) {
159
+ const id = this.registry.register(address);
160
+ const account = {
161
+ id,
162
+ type: keyring_api_1.EthAccountType.Eoa,
163
+ address,
164
+ scopes: [...this.capabilities.scopes],
165
+ methods: [...HD_KEYRING_METHODS],
166
+ options: {
167
+ entropy: {
168
+ type: keyring_api_1.KeyringAccountEntropyTypeOption.Mnemonic,
169
+ id: this.entropySource,
170
+ groupIndex: addressIndex,
171
+ derivationPath: `${this.inner.hdPath}/${addressIndex}`,
172
+ },
173
+ exportable: true,
174
+ },
175
+ };
176
+ // Add the account to the registry
177
+ this.registry.set(account);
178
+ return account;
179
+ };
180
+ //# sourceMappingURL=hd-keyring-v2.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hd-keyring-v2.cjs","sourceRoot":"","sources":["../src/hd-keyring-v2.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Bip44Account } from '@metamask/account-api';\nimport {\n type CreateAccountOptions,\n EthAccountType,\n EthKeyringMethod,\n EthKeyringWrapper,\n EthMethod,\n EthScope,\n type ExportAccountOptions,\n type ExportedAccount,\n type KeyringAccount,\n KeyringAccountEntropyTypeOption,\n type KeyringCapabilities,\n type KeyringV2,\n KeyringType,\n PrivateKeyEncoding,\n type EntropySourceId,\n} from '@metamask/keyring-api';\nimport type { AccountId } from '@metamask/keyring-utils';\nimport { add0x, type Hex } from '@metamask/utils';\n\nimport type { HdKeyring } from './hd-keyring';\n\n/**\n * Methods supported by HD keyring EOA accounts.\n * HD keyrings support all standard signing methods plus encryption and app keys.\n */\nconst HD_KEYRING_METHODS = [\n EthMethod.SignTransaction,\n EthMethod.Sign,\n EthMethod.PersonalSign,\n EthMethod.SignTypedDataV1,\n EthMethod.SignTypedDataV3,\n EthMethod.SignTypedDataV4,\n EthKeyringMethod.Decrypt,\n EthKeyringMethod.GetEncryptionPublicKey,\n EthKeyringMethod.GetAppKeyAddress,\n EthKeyringMethod.SignEip7702Authorization,\n];\n\nconst hdKeyringV2Capabilities: KeyringCapabilities = {\n scopes: [EthScope.Eoa],\n bip44: {\n deriveIndex: true,\n },\n privateKey: {\n exportFormats: [{ encoding: PrivateKeyEncoding.Hexadecimal }],\n },\n};\n\n/**\n * Concrete {@link KeyringV2} adapter for {@link HdKeyring}.\n *\n * This wrapper exposes the accounts and signing capabilities of the legacy\n * HD keyring via the unified V2 interface.\n */\nexport type HdKeyringV2Options = {\n legacyKeyring: HdKeyring;\n entropySource: EntropySourceId;\n};\n\nexport class HdKeyringV2\n extends EthKeyringWrapper<HdKeyring, Bip44Account<KeyringAccount>>\n implements KeyringV2\n{\n protected readonly entropySource: EntropySourceId;\n\n constructor(options: HdKeyringV2Options) {\n super({\n type: KeyringType.Hd,\n inner: options.legacyKeyring,\n capabilities: hdKeyringV2Capabilities,\n });\n this.entropySource = options.entropySource;\n }\n\n /**\n * Checks if the given address is the last account in the inner keyring.\n * This compares against the actual inner keyring state, not the registry,\n * to avoid issues with stale registry entries.\n *\n * @param address - The address to check.\n * @returns True if this is the last account in the inner keyring.\n */\n async #isLastAccount(address: string): Promise<boolean> {\n const innerAddresses = await this.inner.getAccounts();\n const lastAddress = innerAddresses[innerAddresses.length - 1];\n return address === lastAddress;\n }\n\n /**\n * Creates a KeyringAccount object for the given address and index.\n *\n * @param address - The account address.\n * @param addressIndex - The account index in the HD path.\n * @returns The created KeyringAccount.\n */\n #createKeyringAccount(\n address: Hex,\n addressIndex: number,\n ): Bip44Account<KeyringAccount> {\n const id = this.registry.register(address);\n\n const account: Bip44Account<KeyringAccount> = {\n id,\n type: EthAccountType.Eoa,\n address,\n scopes: [...this.capabilities.scopes],\n methods: [...HD_KEYRING_METHODS],\n options: {\n entropy: {\n type: KeyringAccountEntropyTypeOption.Mnemonic,\n id: this.entropySource,\n groupIndex: addressIndex,\n derivationPath: `${this.inner.hdPath}/${addressIndex}`,\n },\n exportable: true,\n },\n };\n\n // Add the account to the registry\n this.registry.set(account);\n\n return account;\n }\n\n async getAccounts(): Promise<Bip44Account<KeyringAccount>[]> {\n const addresses = await this.inner.getAccounts();\n\n return addresses.map((address, addressIndex) => {\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 // Create and register the account if not already cached\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 // For HD keyring, we only support BIP-44 derive index\n if (options.type !== 'bip44:derive-index') {\n throw new Error(\n `Unsupported account creation type for HdKeyring: ${options.type}`,\n );\n }\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\n // Sync with the inner keyring state in case it was modified externally\n // This ensures our cache is up-to-date before we make changes\n const currentAccounts = await this.getAccounts();\n const currentCount = currentAccounts.length;\n const targetIndex = options.groupIndex;\n\n // Check if an account at this index already exists\n // Since only the last account can be deleted, array position always equals groupIndex\n const existingAccount = currentAccounts[targetIndex];\n if (existingAccount) {\n return [existingAccount];\n }\n\n // Only allow derivation of the next account in sequence\n if (targetIndex !== currentCount) {\n throw new Error(\n `Can only create the next account in sequence. ` +\n `Expected groupIndex ${currentCount}, got ${targetIndex}.`,\n );\n }\n\n // Add the next account\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 * ⚠️ Warning: Only deleting the last account is possible.\n *\n * @param accountId - The account ID to delete.\n */\n async deleteAccount(accountId: AccountId): Promise<void> {\n await this.withLock(async () => {\n // Get the account first, before any registry operations\n const { address } = await this.getAccount(accountId);\n const hexAddress = this.toHexAddress(address);\n\n // Assert that the account to delete is the last one in the inner keyring\n // We check against the inner keyring directly to avoid stale registry issues\n if (!(await this.#isLastAccount(address))) {\n throw new Error(\n 'Can only delete the last account in the HD keyring due to derivation index constraints.',\n );\n }\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 /**\n * Export the private key for an account in hexadecimal format.\n *\n * @param accountId - The ID of the account to export.\n * @param options - Export options (only hexadecimal encoding is supported).\n * @returns The exported account with private key.\n */\n async exportAccount(\n accountId: AccountId,\n options?: ExportAccountOptions,\n ): Promise<ExportedAccount> {\n const account = await this.getAccount(accountId);\n\n // Validate encoding - we only support hexadecimal for Ethereum keys\n const requestedEncoding =\n options?.encoding ?? PrivateKeyEncoding.Hexadecimal;\n\n if (requestedEncoding !== PrivateKeyEncoding.Hexadecimal) {\n throw new Error(\n `Unsupported encoding for Ethereum HD keyring: ${requestedEncoding}. Only '${PrivateKeyEncoding.Hexadecimal}' is supported.`,\n );\n }\n\n // The legacy HdKeyring returns a hex string without 0x prefix.\n const privateKeyWithout0x = await this.inner.exportAccount(\n this.toHexAddress(account.address),\n );\n const privateKey = add0x(privateKeyWithout0x);\n\n return {\n type: 'private-key',\n privateKey,\n encoding: PrivateKeyEncoding.Hexadecimal,\n };\n }\n}\n"]}
@@ -0,0 +1,38 @@
1
+ import type { Bip44Account } from "@metamask/account-api";
2
+ import { type CreateAccountOptions, EthKeyringWrapper, type ExportAccountOptions, type ExportedAccount, type KeyringAccount, type KeyringV2, type EntropySourceId } from "@metamask/keyring-api";
3
+ import type { AccountId } from "@metamask/keyring-utils";
4
+ import type { HdKeyring } from "./hd-keyring.cjs";
5
+ /**
6
+ * Concrete {@link KeyringV2} adapter for {@link HdKeyring}.
7
+ *
8
+ * This wrapper exposes the accounts and signing capabilities of the legacy
9
+ * HD keyring via the unified V2 interface.
10
+ */
11
+ export type HdKeyringV2Options = {
12
+ legacyKeyring: HdKeyring;
13
+ entropySource: EntropySourceId;
14
+ };
15
+ export declare class HdKeyringV2 extends EthKeyringWrapper<HdKeyring, Bip44Account<KeyringAccount>> implements KeyringV2 {
16
+ #private;
17
+ protected readonly entropySource: EntropySourceId;
18
+ constructor(options: HdKeyringV2Options);
19
+ getAccounts(): Promise<Bip44Account<KeyringAccount>[]>;
20
+ createAccounts(options: CreateAccountOptions): Promise<Bip44Account<KeyringAccount>[]>;
21
+ /**
22
+ * Delete an account from the keyring.
23
+ *
24
+ * ⚠️ Warning: Only deleting the last account is possible.
25
+ *
26
+ * @param accountId - The account ID to delete.
27
+ */
28
+ deleteAccount(accountId: AccountId): Promise<void>;
29
+ /**
30
+ * Export the private key for an account in hexadecimal format.
31
+ *
32
+ * @param accountId - The ID of the account to export.
33
+ * @param options - Export options (only hexadecimal encoding is supported).
34
+ * @returns The exported account with private key.
35
+ */
36
+ exportAccount(accountId: AccountId, options?: ExportAccountOptions): Promise<ExportedAccount>;
37
+ }
38
+ //# sourceMappingURL=hd-keyring-v2.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hd-keyring-v2.d.cts","sourceRoot":"","sources":["../src/hd-keyring-v2.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,8BAA8B;AAC1D,OAAO,EACL,KAAK,oBAAoB,EAGzB,iBAAiB,EAGjB,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,KAAK,cAAc,EAGnB,KAAK,SAAS,EAGd,KAAK,eAAe,EACrB,8BAA8B;AAC/B,OAAO,KAAK,EAAE,SAAS,EAAE,gCAAgC;AAGzD,OAAO,KAAK,EAAE,SAAS,EAAE,yBAAqB;AA6B9C;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,aAAa,EAAE,SAAS,CAAC;IACzB,aAAa,EAAE,eAAe,CAAC;CAChC,CAAC;AAEF,qBAAa,WACX,SAAQ,iBAAiB,CAAC,SAAS,EAAE,YAAY,CAAC,cAAc,CAAC,CACjE,YAAW,SAAS;;IAEpB,SAAS,CAAC,QAAQ,CAAC,aAAa,EAAE,eAAe,CAAC;gBAEtC,OAAO,EAAE,kBAAkB;IA2DjC,WAAW,IAAI,OAAO,CAAC,YAAY,CAAC,cAAc,CAAC,EAAE,CAAC;IAkBtD,cAAc,CAClB,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,YAAY,CAAC,cAAc,CAAC,EAAE,CAAC;IAkD1C;;;;;;OAMG;IACG,aAAa,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAsBxD;;;;;;OAMG;IACG,aAAa,CACjB,SAAS,EAAE,SAAS,EACpB,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,eAAe,CAAC;CAyB5B"}
@@ -0,0 +1,38 @@
1
+ import type { Bip44Account } from "@metamask/account-api";
2
+ import { type CreateAccountOptions, EthKeyringWrapper, type ExportAccountOptions, type ExportedAccount, type KeyringAccount, type KeyringV2, type EntropySourceId } from "@metamask/keyring-api";
3
+ import type { AccountId } from "@metamask/keyring-utils";
4
+ import type { HdKeyring } from "./hd-keyring.mjs";
5
+ /**
6
+ * Concrete {@link KeyringV2} adapter for {@link HdKeyring}.
7
+ *
8
+ * This wrapper exposes the accounts and signing capabilities of the legacy
9
+ * HD keyring via the unified V2 interface.
10
+ */
11
+ export type HdKeyringV2Options = {
12
+ legacyKeyring: HdKeyring;
13
+ entropySource: EntropySourceId;
14
+ };
15
+ export declare class HdKeyringV2 extends EthKeyringWrapper<HdKeyring, Bip44Account<KeyringAccount>> implements KeyringV2 {
16
+ #private;
17
+ protected readonly entropySource: EntropySourceId;
18
+ constructor(options: HdKeyringV2Options);
19
+ getAccounts(): Promise<Bip44Account<KeyringAccount>[]>;
20
+ createAccounts(options: CreateAccountOptions): Promise<Bip44Account<KeyringAccount>[]>;
21
+ /**
22
+ * Delete an account from the keyring.
23
+ *
24
+ * ⚠️ Warning: Only deleting the last account is possible.
25
+ *
26
+ * @param accountId - The account ID to delete.
27
+ */
28
+ deleteAccount(accountId: AccountId): Promise<void>;
29
+ /**
30
+ * Export the private key for an account in hexadecimal format.
31
+ *
32
+ * @param accountId - The ID of the account to export.
33
+ * @param options - Export options (only hexadecimal encoding is supported).
34
+ * @returns The exported account with private key.
35
+ */
36
+ exportAccount(accountId: AccountId, options?: ExportAccountOptions): Promise<ExportedAccount>;
37
+ }
38
+ //# sourceMappingURL=hd-keyring-v2.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hd-keyring-v2.d.mts","sourceRoot":"","sources":["../src/hd-keyring-v2.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,8BAA8B;AAC1D,OAAO,EACL,KAAK,oBAAoB,EAGzB,iBAAiB,EAGjB,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,KAAK,cAAc,EAGnB,KAAK,SAAS,EAGd,KAAK,eAAe,EACrB,8BAA8B;AAC/B,OAAO,KAAK,EAAE,SAAS,EAAE,gCAAgC;AAGzD,OAAO,KAAK,EAAE,SAAS,EAAE,yBAAqB;AA6B9C;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,aAAa,EAAE,SAAS,CAAC;IACzB,aAAa,EAAE,eAAe,CAAC;CAChC,CAAC;AAEF,qBAAa,WACX,SAAQ,iBAAiB,CAAC,SAAS,EAAE,YAAY,CAAC,cAAc,CAAC,CACjE,YAAW,SAAS;;IAEpB,SAAS,CAAC,QAAQ,CAAC,aAAa,EAAE,eAAe,CAAC;gBAEtC,OAAO,EAAE,kBAAkB;IA2DjC,WAAW,IAAI,OAAO,CAAC,YAAY,CAAC,cAAc,CAAC,EAAE,CAAC;IAkBtD,cAAc,CAClB,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,YAAY,CAAC,cAAc,CAAC,EAAE,CAAC;IAkD1C;;;;;;OAMG;IACG,aAAa,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAsBxD;;;;;;OAMG;IACG,aAAa,CACjB,SAAS,EAAE,SAAS,EACpB,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,eAAe,CAAC;CAyB5B"}
@@ -0,0 +1,176 @@
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 _HdKeyringV2_instances, _HdKeyringV2_isLastAccount, _HdKeyringV2_createKeyringAccount;
7
+ import { EthAccountType, EthKeyringMethod, EthKeyringWrapper, EthMethod, EthScope, KeyringAccountEntropyTypeOption, KeyringType, PrivateKeyEncoding } from "@metamask/keyring-api";
8
+ import { add0x } from "@metamask/utils";
9
+ /**
10
+ * Methods supported by HD keyring EOA accounts.
11
+ * HD keyrings support all standard signing methods plus encryption and app keys.
12
+ */
13
+ const HD_KEYRING_METHODS = [
14
+ EthMethod.SignTransaction,
15
+ EthMethod.Sign,
16
+ EthMethod.PersonalSign,
17
+ EthMethod.SignTypedDataV1,
18
+ EthMethod.SignTypedDataV3,
19
+ EthMethod.SignTypedDataV4,
20
+ EthKeyringMethod.Decrypt,
21
+ EthKeyringMethod.GetEncryptionPublicKey,
22
+ EthKeyringMethod.GetAppKeyAddress,
23
+ EthKeyringMethod.SignEip7702Authorization,
24
+ ];
25
+ const hdKeyringV2Capabilities = {
26
+ scopes: [EthScope.Eoa],
27
+ bip44: {
28
+ deriveIndex: true,
29
+ },
30
+ privateKey: {
31
+ exportFormats: [{ encoding: PrivateKeyEncoding.Hexadecimal }],
32
+ },
33
+ };
34
+ export class HdKeyringV2 extends EthKeyringWrapper {
35
+ constructor(options) {
36
+ super({
37
+ type: KeyringType.Hd,
38
+ inner: options.legacyKeyring,
39
+ capabilities: hdKeyringV2Capabilities,
40
+ });
41
+ _HdKeyringV2_instances.add(this);
42
+ this.entropySource = options.entropySource;
43
+ }
44
+ async getAccounts() {
45
+ const addresses = await this.inner.getAccounts();
46
+ return addresses.map((address, addressIndex) => {
47
+ // Check if we already have this account in the registry
48
+ const existingId = this.registry.getAccountId(address);
49
+ if (existingId) {
50
+ const cached = this.registry.get(existingId);
51
+ if (cached) {
52
+ return cached;
53
+ }
54
+ }
55
+ // Create and register the account if not already cached
56
+ return __classPrivateFieldGet(this, _HdKeyringV2_instances, "m", _HdKeyringV2_createKeyringAccount).call(this, address, addressIndex);
57
+ });
58
+ }
59
+ async createAccounts(options) {
60
+ return this.withLock(async () => {
61
+ // For HD keyring, we only support BIP-44 derive index
62
+ if (options.type !== 'bip44:derive-index') {
63
+ throw new Error(`Unsupported account creation type for HdKeyring: ${options.type}`);
64
+ }
65
+ // Validate that the entropy source matches this keyring's entropy source
66
+ if (options.entropySource !== this.entropySource) {
67
+ throw new Error(`Entropy source mismatch: expected '${this.entropySource}', got '${options.entropySource}'`);
68
+ }
69
+ // Sync with the inner keyring state in case it was modified externally
70
+ // This ensures our cache is up-to-date before we make changes
71
+ const currentAccounts = await this.getAccounts();
72
+ const currentCount = currentAccounts.length;
73
+ const targetIndex = options.groupIndex;
74
+ // Check if an account at this index already exists
75
+ // Since only the last account can be deleted, array position always equals groupIndex
76
+ const existingAccount = currentAccounts[targetIndex];
77
+ if (existingAccount) {
78
+ return [existingAccount];
79
+ }
80
+ // Only allow derivation of the next account in sequence
81
+ if (targetIndex !== currentCount) {
82
+ throw new Error(`Can only create the next account in sequence. ` +
83
+ `Expected groupIndex ${currentCount}, got ${targetIndex}.`);
84
+ }
85
+ // Add the next account
86
+ const [newAddress] = await this.inner.addAccounts(1);
87
+ if (!newAddress) {
88
+ throw new Error('Failed to create new account');
89
+ }
90
+ const newAccount = __classPrivateFieldGet(this, _HdKeyringV2_instances, "m", _HdKeyringV2_createKeyringAccount).call(this, newAddress, targetIndex);
91
+ return [newAccount];
92
+ });
93
+ }
94
+ /**
95
+ * Delete an account from the keyring.
96
+ *
97
+ * ⚠️ Warning: Only deleting the last account is possible.
98
+ *
99
+ * @param accountId - The account ID to delete.
100
+ */
101
+ async deleteAccount(accountId) {
102
+ await this.withLock(async () => {
103
+ // Get the account first, before any registry operations
104
+ const { address } = await this.getAccount(accountId);
105
+ const hexAddress = this.toHexAddress(address);
106
+ // Assert that the account to delete is the last one in the inner keyring
107
+ // We check against the inner keyring directly to avoid stale registry issues
108
+ if (!(await __classPrivateFieldGet(this, _HdKeyringV2_instances, "m", _HdKeyringV2_isLastAccount).call(this, address))) {
109
+ throw new Error('Can only delete the last account in the HD keyring due to derivation index constraints.');
110
+ }
111
+ // Remove from the legacy keyring
112
+ this.inner.removeAccount(hexAddress);
113
+ // Remove from the registry
114
+ this.registry.delete(accountId);
115
+ });
116
+ }
117
+ /**
118
+ * Export the private key for an account in hexadecimal format.
119
+ *
120
+ * @param accountId - The ID of the account to export.
121
+ * @param options - Export options (only hexadecimal encoding is supported).
122
+ * @returns The exported account with private key.
123
+ */
124
+ async exportAccount(accountId, options) {
125
+ const account = await this.getAccount(accountId);
126
+ // Validate encoding - we only support hexadecimal for Ethereum keys
127
+ const requestedEncoding = options?.encoding ?? PrivateKeyEncoding.Hexadecimal;
128
+ if (requestedEncoding !== PrivateKeyEncoding.Hexadecimal) {
129
+ throw new Error(`Unsupported encoding for Ethereum HD keyring: ${requestedEncoding}. Only '${PrivateKeyEncoding.Hexadecimal}' is supported.`);
130
+ }
131
+ // The legacy HdKeyring returns a hex string without 0x prefix.
132
+ const privateKeyWithout0x = await this.inner.exportAccount(this.toHexAddress(account.address));
133
+ const privateKey = add0x(privateKeyWithout0x);
134
+ return {
135
+ type: 'private-key',
136
+ privateKey,
137
+ encoding: PrivateKeyEncoding.Hexadecimal,
138
+ };
139
+ }
140
+ }
141
+ _HdKeyringV2_instances = new WeakSet(), _HdKeyringV2_isLastAccount =
142
+ /**
143
+ * Checks if the given address is the last account in the inner keyring.
144
+ * This compares against the actual inner keyring state, not the registry,
145
+ * to avoid issues with stale registry entries.
146
+ *
147
+ * @param address - The address to check.
148
+ * @returns True if this is the last account in the inner keyring.
149
+ */
150
+ async function _HdKeyringV2_isLastAccount(address) {
151
+ const innerAddresses = await this.inner.getAccounts();
152
+ const lastAddress = innerAddresses[innerAddresses.length - 1];
153
+ return address === lastAddress;
154
+ }, _HdKeyringV2_createKeyringAccount = function _HdKeyringV2_createKeyringAccount(address, addressIndex) {
155
+ const id = this.registry.register(address);
156
+ const account = {
157
+ id,
158
+ type: EthAccountType.Eoa,
159
+ address,
160
+ scopes: [...this.capabilities.scopes],
161
+ methods: [...HD_KEYRING_METHODS],
162
+ options: {
163
+ entropy: {
164
+ type: KeyringAccountEntropyTypeOption.Mnemonic,
165
+ id: this.entropySource,
166
+ groupIndex: addressIndex,
167
+ derivationPath: `${this.inner.hdPath}/${addressIndex}`,
168
+ },
169
+ exportable: true,
170
+ },
171
+ };
172
+ // Add the account to the registry
173
+ this.registry.set(account);
174
+ return account;
175
+ };
176
+ //# sourceMappingURL=hd-keyring-v2.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hd-keyring-v2.mjs","sourceRoot":"","sources":["../src/hd-keyring-v2.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Bip44Account } from '@metamask/account-api';\nimport {\n type CreateAccountOptions,\n EthAccountType,\n EthKeyringMethod,\n EthKeyringWrapper,\n EthMethod,\n EthScope,\n type ExportAccountOptions,\n type ExportedAccount,\n type KeyringAccount,\n KeyringAccountEntropyTypeOption,\n type KeyringCapabilities,\n type KeyringV2,\n KeyringType,\n PrivateKeyEncoding,\n type EntropySourceId,\n} from '@metamask/keyring-api';\nimport type { AccountId } from '@metamask/keyring-utils';\nimport { add0x, type Hex } from '@metamask/utils';\n\nimport type { HdKeyring } from './hd-keyring';\n\n/**\n * Methods supported by HD keyring EOA accounts.\n * HD keyrings support all standard signing methods plus encryption and app keys.\n */\nconst HD_KEYRING_METHODS = [\n EthMethod.SignTransaction,\n EthMethod.Sign,\n EthMethod.PersonalSign,\n EthMethod.SignTypedDataV1,\n EthMethod.SignTypedDataV3,\n EthMethod.SignTypedDataV4,\n EthKeyringMethod.Decrypt,\n EthKeyringMethod.GetEncryptionPublicKey,\n EthKeyringMethod.GetAppKeyAddress,\n EthKeyringMethod.SignEip7702Authorization,\n];\n\nconst hdKeyringV2Capabilities: KeyringCapabilities = {\n scopes: [EthScope.Eoa],\n bip44: {\n deriveIndex: true,\n },\n privateKey: {\n exportFormats: [{ encoding: PrivateKeyEncoding.Hexadecimal }],\n },\n};\n\n/**\n * Concrete {@link KeyringV2} adapter for {@link HdKeyring}.\n *\n * This wrapper exposes the accounts and signing capabilities of the legacy\n * HD keyring via the unified V2 interface.\n */\nexport type HdKeyringV2Options = {\n legacyKeyring: HdKeyring;\n entropySource: EntropySourceId;\n};\n\nexport class HdKeyringV2\n extends EthKeyringWrapper<HdKeyring, Bip44Account<KeyringAccount>>\n implements KeyringV2\n{\n protected readonly entropySource: EntropySourceId;\n\n constructor(options: HdKeyringV2Options) {\n super({\n type: KeyringType.Hd,\n inner: options.legacyKeyring,\n capabilities: hdKeyringV2Capabilities,\n });\n this.entropySource = options.entropySource;\n }\n\n /**\n * Checks if the given address is the last account in the inner keyring.\n * This compares against the actual inner keyring state, not the registry,\n * to avoid issues with stale registry entries.\n *\n * @param address - The address to check.\n * @returns True if this is the last account in the inner keyring.\n */\n async #isLastAccount(address: string): Promise<boolean> {\n const innerAddresses = await this.inner.getAccounts();\n const lastAddress = innerAddresses[innerAddresses.length - 1];\n return address === lastAddress;\n }\n\n /**\n * Creates a KeyringAccount object for the given address and index.\n *\n * @param address - The account address.\n * @param addressIndex - The account index in the HD path.\n * @returns The created KeyringAccount.\n */\n #createKeyringAccount(\n address: Hex,\n addressIndex: number,\n ): Bip44Account<KeyringAccount> {\n const id = this.registry.register(address);\n\n const account: Bip44Account<KeyringAccount> = {\n id,\n type: EthAccountType.Eoa,\n address,\n scopes: [...this.capabilities.scopes],\n methods: [...HD_KEYRING_METHODS],\n options: {\n entropy: {\n type: KeyringAccountEntropyTypeOption.Mnemonic,\n id: this.entropySource,\n groupIndex: addressIndex,\n derivationPath: `${this.inner.hdPath}/${addressIndex}`,\n },\n exportable: true,\n },\n };\n\n // Add the account to the registry\n this.registry.set(account);\n\n return account;\n }\n\n async getAccounts(): Promise<Bip44Account<KeyringAccount>[]> {\n const addresses = await this.inner.getAccounts();\n\n return addresses.map((address, addressIndex) => {\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 // Create and register the account if not already cached\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 // For HD keyring, we only support BIP-44 derive index\n if (options.type !== 'bip44:derive-index') {\n throw new Error(\n `Unsupported account creation type for HdKeyring: ${options.type}`,\n );\n }\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\n // Sync with the inner keyring state in case it was modified externally\n // This ensures our cache is up-to-date before we make changes\n const currentAccounts = await this.getAccounts();\n const currentCount = currentAccounts.length;\n const targetIndex = options.groupIndex;\n\n // Check if an account at this index already exists\n // Since only the last account can be deleted, array position always equals groupIndex\n const existingAccount = currentAccounts[targetIndex];\n if (existingAccount) {\n return [existingAccount];\n }\n\n // Only allow derivation of the next account in sequence\n if (targetIndex !== currentCount) {\n throw new Error(\n `Can only create the next account in sequence. ` +\n `Expected groupIndex ${currentCount}, got ${targetIndex}.`,\n );\n }\n\n // Add the next account\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 * ⚠️ Warning: Only deleting the last account is possible.\n *\n * @param accountId - The account ID to delete.\n */\n async deleteAccount(accountId: AccountId): Promise<void> {\n await this.withLock(async () => {\n // Get the account first, before any registry operations\n const { address } = await this.getAccount(accountId);\n const hexAddress = this.toHexAddress(address);\n\n // Assert that the account to delete is the last one in the inner keyring\n // We check against the inner keyring directly to avoid stale registry issues\n if (!(await this.#isLastAccount(address))) {\n throw new Error(\n 'Can only delete the last account in the HD keyring due to derivation index constraints.',\n );\n }\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 /**\n * Export the private key for an account in hexadecimal format.\n *\n * @param accountId - The ID of the account to export.\n * @param options - Export options (only hexadecimal encoding is supported).\n * @returns The exported account with private key.\n */\n async exportAccount(\n accountId: AccountId,\n options?: ExportAccountOptions,\n ): Promise<ExportedAccount> {\n const account = await this.getAccount(accountId);\n\n // Validate encoding - we only support hexadecimal for Ethereum keys\n const requestedEncoding =\n options?.encoding ?? PrivateKeyEncoding.Hexadecimal;\n\n if (requestedEncoding !== PrivateKeyEncoding.Hexadecimal) {\n throw new Error(\n `Unsupported encoding for Ethereum HD keyring: ${requestedEncoding}. Only '${PrivateKeyEncoding.Hexadecimal}' is supported.`,\n );\n }\n\n // The legacy HdKeyring returns a hex string without 0x prefix.\n const privateKeyWithout0x = await this.inner.exportAccount(\n this.toHexAddress(account.address),\n );\n const privateKey = add0x(privateKeyWithout0x);\n\n return {\n type: 'private-key',\n privateKey,\n encoding: PrivateKeyEncoding.Hexadecimal,\n };\n }\n}\n"]}