@metamask-previews/multichain-account-service 0.9.0-preview-3facabc → 0.9.0-preview-988ea6d

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.
@@ -10,13 +10,15 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
10
10
  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");
11
11
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
12
  };
13
- var _EvmAccountProvider_instances, _EvmAccountProvider_config, _EvmAccountProvider_createAccount, _EvmAccountProvider_getTransactionCount;
13
+ var _EvmAccountProvider_instances, _EvmAccountProvider_config, _EvmAccountProvider_createAccount, _EvmAccountProvider_getTransactionCount, _EvmAccountProvider_getAddressFromGroupIndex;
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.EvmAccountProvider = exports.EVM_ACCOUNT_PROVIDER_NAME = void 0;
16
+ const util_1 = require("@ethereumjs/util");
16
17
  const keyring_api_1 = require("@metamask/keyring-api");
17
18
  const keyring_controller_1 = require("@metamask/keyring-controller");
19
+ const utils_1 = require("@metamask/utils");
18
20
  const BaseBip44AccountProvider_1 = require("./BaseBip44AccountProvider.cjs");
19
- const utils_1 = require("./utils.cjs");
21
+ const utils_2 = require("./utils.cjs");
20
22
  const ETH_MAINNET_CHAIN_ID = '0x1';
21
23
  /**
22
24
  * Asserts an internal account exists.
@@ -84,31 +86,20 @@ class EvmAccountProvider extends BaseBip44AccountProvider_1.BaseBip44AccountProv
84
86
  async discoverAccounts(opts) {
85
87
  const provider = this.getEvmProvider();
86
88
  const { entropySource, groupIndex } = opts;
87
- const [address, didCreate] = await __classPrivateFieldGet(this, _EvmAccountProvider_instances, "m", _EvmAccountProvider_createAccount).call(this, {
89
+ const addressFromGroupIndex = await __classPrivateFieldGet(this, _EvmAccountProvider_instances, "m", _EvmAccountProvider_getAddressFromGroupIndex).call(this, {
88
90
  entropySource,
89
91
  groupIndex,
90
92
  });
91
- // We don't want to remove the account if it's the first one.
92
- const shouldCleanup = didCreate && groupIndex !== 0;
93
- try {
94
- const count = await __classPrivateFieldGet(this, _EvmAccountProvider_instances, "m", _EvmAccountProvider_getTransactionCount).call(this, provider, address);
95
- if (count === 0 && shouldCleanup) {
96
- await this.withKeyring({ id: entropySource }, async ({ keyring }) => {
97
- keyring.removeAccount?.(address);
98
- });
99
- return [];
100
- }
101
- }
102
- catch (error) {
103
- // If the RPC request fails and we just created this account for discovery,
104
- // remove it to avoid leaving a dangling account.
105
- if (shouldCleanup) {
106
- await this.withKeyring({ id: entropySource }, async ({ keyring }) => {
107
- keyring.removeAccount?.(address);
108
- });
109
- }
110
- throw error;
93
+ const count = await __classPrivateFieldGet(this, _EvmAccountProvider_instances, "m", _EvmAccountProvider_getTransactionCount).call(this, provider, addressFromGroupIndex);
94
+ if (count === 0) {
95
+ return [];
111
96
  }
97
+ // We have some activity on this address, we try to create the account.
98
+ const [address] = await __classPrivateFieldGet(this, _EvmAccountProvider_instances, "m", _EvmAccountProvider_createAccount).call(this, {
99
+ entropySource,
100
+ groupIndex,
101
+ });
102
+ (0, utils_1.assert)(addressFromGroupIndex === address, 'Created account does not match address from group index.');
112
103
  const account = this.messenger.call('AccountsController:getAccountByAddress', address);
113
104
  assertInternalAccountExists(account);
114
105
  (0, BaseBip44AccountProvider_1.assertIsBip44Account)(account);
@@ -131,7 +122,7 @@ _EvmAccountProvider_config = new WeakMap(), _EvmAccountProvider_instances = new
131
122
  });
132
123
  return result;
133
124
  }, _EvmAccountProvider_getTransactionCount = async function _EvmAccountProvider_getTransactionCount(provider, address) {
134
- const countHex = await (0, utils_1.withRetry)(() => (0, utils_1.withTimeout)(provider.request({
125
+ const countHex = await (0, utils_2.withRetry)(() => (0, utils_2.withTimeout)(provider.request({
135
126
  method: 'eth_getTransactionCount',
136
127
  params: [address, 'latest'],
137
128
  }), __classPrivateFieldGet(this, _EvmAccountProvider_config, "f").discovery.timeoutMs), {
@@ -139,6 +130,21 @@ _EvmAccountProvider_config = new WeakMap(), _EvmAccountProvider_instances = new
139
130
  backOffMs: __classPrivateFieldGet(this, _EvmAccountProvider_config, "f").discovery.backOffMs,
140
131
  });
141
132
  return parseInt(countHex, 16);
133
+ }, _EvmAccountProvider_getAddressFromGroupIndex = async function _EvmAccountProvider_getAddressFromGroupIndex({ entropySource, groupIndex, }) {
134
+ // NOTE: To avoid exposing this function at keyring level, we just re-use its internal state
135
+ // and compute the derivation here.
136
+ return await this.withKeyring({ id: entropySource }, async ({ keyring }) => {
137
+ // If the account already exist, do not re-derive and just re-use that account.
138
+ const existing = await keyring.getAccounts();
139
+ if (groupIndex < existing.length) {
140
+ return existing[groupIndex];
141
+ }
142
+ // If not, then we just "peek" the next address to avoid creating the account.
143
+ (0, utils_1.assert)(keyring.root, 'Expected HD keyring.root to be set');
144
+ const hdKey = keyring.root.deriveChild(groupIndex);
145
+ (0, utils_1.assert)(hdKey.publicKey, 'Expected public key to be set');
146
+ return (0, utils_1.add0x)((0, utils_1.bytesToHex)((0, util_1.publicToAddress)(hdKey.publicKey, true)).toLowerCase());
147
+ });
142
148
  };
143
149
  EvmAccountProvider.NAME = exports.EVM_ACCOUNT_PROVIDER_NAME;
144
150
  //# sourceMappingURL=EvmAccountProvider.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"EvmAccountProvider.cjs","sourceRoot":"","sources":["../../src/providers/EvmAccountProvider.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Bip44Account } from '@metamask/account-api';\nimport type { EntropySourceId, KeyringAccount } from '@metamask/keyring-api';\nimport { EthAccountType } from '@metamask/keyring-api';\nimport { KeyringTypes } from '@metamask/keyring-controller';\nimport type {\n EthKeyring,\n InternalAccount,\n} from '@metamask/keyring-internal-api';\nimport type { Provider } from '@metamask/network-controller';\nimport type { Hex } from '@metamask/utils';\nimport type { MultichainAccountServiceMessenger } from 'src/types';\n\nimport {\n assertAreBip44Accounts,\n assertIsBip44Account,\n BaseBip44AccountProvider,\n} from './BaseBip44AccountProvider';\nimport { withRetry, withTimeout } from './utils';\n\nconst ETH_MAINNET_CHAIN_ID = '0x1';\n\n/**\n * Asserts an internal account exists.\n *\n * @param account - The internal account to check.\n * @throws An error if the internal account does not exist.\n */\nfunction assertInternalAccountExists(\n account: InternalAccount | undefined,\n): asserts account is InternalAccount {\n if (!account) {\n throw new Error('Internal account does not exist');\n }\n}\n\nexport type EvmAccountProviderConfig = {\n discovery: {\n maxAttempts: number;\n timeoutMs: number;\n backOffMs: number;\n };\n};\n\nexport const EVM_ACCOUNT_PROVIDER_NAME = 'EVM' as const;\n\nexport class EvmAccountProvider extends BaseBip44AccountProvider {\n static NAME = EVM_ACCOUNT_PROVIDER_NAME;\n\n readonly #config: EvmAccountProviderConfig;\n\n constructor(\n messenger: MultichainAccountServiceMessenger,\n config: EvmAccountProviderConfig = {\n discovery: {\n maxAttempts: 3,\n timeoutMs: 500,\n backOffMs: 500,\n },\n },\n ) {\n super(messenger);\n this.#config = config;\n }\n\n isAccountCompatible(account: Bip44Account<InternalAccount>): boolean {\n return (\n account.type === EthAccountType.Eoa &&\n account.metadata.keyring.type === (KeyringTypes.hd as string)\n );\n }\n\n getName(): string {\n return EvmAccountProvider.NAME;\n }\n\n /**\n * Get the EVM provider.\n *\n * @returns The EVM provider.\n */\n getEvmProvider(): Provider {\n const networkClientId = this.messenger.call(\n 'NetworkController:findNetworkClientIdByChainId',\n ETH_MAINNET_CHAIN_ID,\n );\n const { provider } = this.messenger.call(\n 'NetworkController:getNetworkClientById',\n networkClientId,\n );\n return provider;\n }\n\n async #createAccount({\n entropySource,\n groupIndex,\n throwOnGap = false,\n }: {\n entropySource: EntropySourceId;\n groupIndex: number;\n throwOnGap?: boolean;\n }): Promise<[Hex, boolean]> {\n const result = await this.withKeyring<EthKeyring, [Hex, boolean]>(\n { id: entropySource },\n async ({ keyring }) => {\n const existing = await keyring.getAccounts();\n if (groupIndex < existing.length) {\n return [existing[groupIndex], false];\n }\n\n // If the throwOnGap flag is set, we throw an error to prevent index gaps.\n if (throwOnGap && groupIndex !== existing.length) {\n throw new Error('Trying to create too many accounts');\n }\n\n const [added] = await keyring.addAccounts(1);\n return [added, true];\n },\n );\n\n return result;\n }\n\n async createAccounts({\n entropySource,\n groupIndex,\n }: {\n entropySource: EntropySourceId;\n groupIndex: number;\n }): Promise<Bip44Account<KeyringAccount>[]> {\n const [address] = await this.#createAccount({\n entropySource,\n groupIndex,\n throwOnGap: true,\n });\n\n const account = this.messenger.call(\n 'AccountsController:getAccountByAddress',\n address,\n );\n\n // We MUST have the associated internal account.\n assertInternalAccountExists(account);\n\n const accountsArray = [account];\n assertAreBip44Accounts(accountsArray);\n\n return accountsArray;\n }\n\n async #getTransactionCount(\n provider: Provider,\n address: Hex,\n ): Promise<number> {\n const countHex = await withRetry<Hex>(\n () =>\n withTimeout(\n provider.request({\n method: 'eth_getTransactionCount',\n params: [address, 'latest'],\n }),\n this.#config.discovery.timeoutMs,\n ),\n {\n maxAttempts: this.#config.discovery.maxAttempts,\n backOffMs: this.#config.discovery.backOffMs,\n },\n );\n\n return parseInt(countHex, 16);\n }\n\n /**\n * Discover and create accounts for the EVM provider.\n *\n * @param opts - The options for the discovery and creation of accounts.\n * @param opts.entropySource - The entropy source to use for the discovery and creation of accounts.\n * @param opts.groupIndex - The index of the group to create the accounts for.\n * @returns The accounts for the EVM provider.\n */\n async discoverAccounts(opts: {\n entropySource: EntropySourceId;\n groupIndex: number;\n }): Promise<Bip44Account<KeyringAccount>[]> {\n const provider = this.getEvmProvider();\n const { entropySource, groupIndex } = opts;\n\n const [address, didCreate] = await this.#createAccount({\n entropySource,\n groupIndex,\n });\n\n // We don't want to remove the account if it's the first one.\n const shouldCleanup = didCreate && groupIndex !== 0;\n try {\n const count = await this.#getTransactionCount(provider, address);\n\n if (count === 0 && shouldCleanup) {\n await this.withKeyring<EthKeyring>(\n { id: entropySource },\n async ({ keyring }) => {\n keyring.removeAccount?.(address);\n },\n );\n return [];\n }\n } catch (error) {\n // If the RPC request fails and we just created this account for discovery,\n // remove it to avoid leaving a dangling account.\n if (shouldCleanup) {\n await this.withKeyring<EthKeyring>(\n { id: entropySource },\n async ({ keyring }) => {\n keyring.removeAccount?.(address);\n },\n );\n }\n throw error;\n }\n\n const account = this.messenger.call(\n 'AccountsController:getAccountByAddress',\n address,\n );\n assertInternalAccountExists(account);\n assertIsBip44Account(account);\n return [account];\n }\n}\n"]}
1
+ {"version":3,"file":"EvmAccountProvider.cjs","sourceRoot":"","sources":["../../src/providers/EvmAccountProvider.ts"],"names":[],"mappings":"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","sourcesContent":["import { publicToAddress } from '@ethereumjs/util';\nimport type { Bip44Account } from '@metamask/account-api';\nimport type { HdKeyring } from '@metamask/eth-hd-keyring';\nimport type { EntropySourceId, KeyringAccount } from '@metamask/keyring-api';\nimport { EthAccountType } from '@metamask/keyring-api';\nimport { KeyringTypes } from '@metamask/keyring-controller';\nimport type {\n EthKeyring,\n InternalAccount,\n} from '@metamask/keyring-internal-api';\nimport type { Provider } from '@metamask/network-controller';\nimport { add0x, assert, bytesToHex, type Hex } from '@metamask/utils';\nimport type { MultichainAccountServiceMessenger } from 'src/types';\n\nimport {\n assertAreBip44Accounts,\n assertIsBip44Account,\n BaseBip44AccountProvider,\n} from './BaseBip44AccountProvider';\nimport { withRetry, withTimeout } from './utils';\n\nconst ETH_MAINNET_CHAIN_ID = '0x1';\n\n/**\n * Asserts an internal account exists.\n *\n * @param account - The internal account to check.\n * @throws An error if the internal account does not exist.\n */\nfunction assertInternalAccountExists(\n account: InternalAccount | undefined,\n): asserts account is InternalAccount {\n if (!account) {\n throw new Error('Internal account does not exist');\n }\n}\n\nexport type EvmAccountProviderConfig = {\n discovery: {\n maxAttempts: number;\n timeoutMs: number;\n backOffMs: number;\n };\n};\n\nexport const EVM_ACCOUNT_PROVIDER_NAME = 'EVM' as const;\n\nexport class EvmAccountProvider extends BaseBip44AccountProvider {\n static NAME = EVM_ACCOUNT_PROVIDER_NAME;\n\n readonly #config: EvmAccountProviderConfig;\n\n constructor(\n messenger: MultichainAccountServiceMessenger,\n config: EvmAccountProviderConfig = {\n discovery: {\n maxAttempts: 3,\n timeoutMs: 500,\n backOffMs: 500,\n },\n },\n ) {\n super(messenger);\n this.#config = config;\n }\n\n isAccountCompatible(account: Bip44Account<InternalAccount>): boolean {\n return (\n account.type === EthAccountType.Eoa &&\n account.metadata.keyring.type === (KeyringTypes.hd as string)\n );\n }\n\n getName(): string {\n return EvmAccountProvider.NAME;\n }\n\n /**\n * Get the EVM provider.\n *\n * @returns The EVM provider.\n */\n getEvmProvider(): Provider {\n const networkClientId = this.messenger.call(\n 'NetworkController:findNetworkClientIdByChainId',\n ETH_MAINNET_CHAIN_ID,\n );\n const { provider } = this.messenger.call(\n 'NetworkController:getNetworkClientById',\n networkClientId,\n );\n return provider;\n }\n\n async #createAccount({\n entropySource,\n groupIndex,\n throwOnGap = false,\n }: {\n entropySource: EntropySourceId;\n groupIndex: number;\n throwOnGap?: boolean;\n }): Promise<[Hex, boolean]> {\n const result = await this.withKeyring<EthKeyring, [Hex, boolean]>(\n { id: entropySource },\n async ({ keyring }) => {\n const existing = await keyring.getAccounts();\n if (groupIndex < existing.length) {\n return [existing[groupIndex], false];\n }\n\n // If the throwOnGap flag is set, we throw an error to prevent index gaps.\n if (throwOnGap && groupIndex !== existing.length) {\n throw new Error('Trying to create too many accounts');\n }\n\n const [added] = await keyring.addAccounts(1);\n return [added, true];\n },\n );\n\n return result;\n }\n\n async createAccounts({\n entropySource,\n groupIndex,\n }: {\n entropySource: EntropySourceId;\n groupIndex: number;\n }): Promise<Bip44Account<KeyringAccount>[]> {\n const [address] = await this.#createAccount({\n entropySource,\n groupIndex,\n throwOnGap: true,\n });\n\n const account = this.messenger.call(\n 'AccountsController:getAccountByAddress',\n address,\n );\n\n // We MUST have the associated internal account.\n assertInternalAccountExists(account);\n\n const accountsArray = [account];\n assertAreBip44Accounts(accountsArray);\n\n return accountsArray;\n }\n\n async #getTransactionCount(\n provider: Provider,\n address: Hex,\n ): Promise<number> {\n const countHex = await withRetry<Hex>(\n () =>\n withTimeout(\n provider.request({\n method: 'eth_getTransactionCount',\n params: [address, 'latest'],\n }),\n this.#config.discovery.timeoutMs,\n ),\n {\n maxAttempts: this.#config.discovery.maxAttempts,\n backOffMs: this.#config.discovery.backOffMs,\n },\n );\n\n return parseInt(countHex, 16);\n }\n\n async #getAddressFromGroupIndex({\n entropySource,\n groupIndex,\n }: {\n entropySource: EntropySourceId,\n groupIndex: number,\n }): Promise<Hex> {\n // NOTE: To avoid exposing this function at keyring level, we just re-use its internal state\n // and compute the derivation here.\n return await this.withKeyring<HdKeyring, Hex>(\n { id: entropySource },\n async ({ keyring }) => {\n // If the account already exist, do not re-derive and just re-use that account.\n const existing = await keyring.getAccounts();\n if (groupIndex < existing.length) {\n return existing[groupIndex];\n }\n\n // If not, then we just \"peek\" the next address to avoid creating the account.\n assert(keyring.root, 'Expected HD keyring.root to be set');\n const hdKey = keyring.root.deriveChild(groupIndex);\n assert(hdKey.publicKey, 'Expected public key to be set');\n\n return add0x(\n bytesToHex(publicToAddress(hdKey.publicKey, true)).toLowerCase(),\n );\n },\n );\n }\n\n /**\n * Discover and create accounts for the EVM provider.\n *\n * @param opts - The options for the discovery and creation of accounts.\n * @param opts.entropySource - The entropy source to use for the discovery and creation of accounts.\n * @param opts.groupIndex - The index of the group to create the accounts for.\n * @returns The accounts for the EVM provider.\n */\n async discoverAccounts(opts: {\n entropySource: EntropySourceId;\n groupIndex: number;\n }): Promise<Bip44Account<KeyringAccount>[]> {\n const provider = this.getEvmProvider();\n const { entropySource, groupIndex } = opts;\n\n const addressFromGroupIndex = await this.#getAddressFromGroupIndex({\n entropySource,\n groupIndex,\n });\n\n const count = await this.#getTransactionCount(\n provider,\n addressFromGroupIndex,\n );\n if (count === 0) {\n return [];\n }\n\n // We have some activity on this address, we try to create the account.\n const [address] = await this.#createAccount({\n entropySource,\n groupIndex,\n });\n assert(\n addressFromGroupIndex === address,\n 'Created account does not match address from group index.',\n );\n\n const account = this.messenger.call(\n 'AccountsController:getAccountByAddress',\n address,\n );\n assertInternalAccountExists(account);\n assertIsBip44Account(account);\n return [account];\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"EvmAccountProvider.d.cts","sourceRoot":"","sources":["../../src/providers/EvmAccountProvider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,8BAA8B;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,8BAA8B;AAG7E,OAAO,KAAK,EAEV,eAAe,EAChB,uCAAuC;AACxC,OAAO,KAAK,EAAE,QAAQ,EAAE,qCAAqC;AAE7D,OAAO,KAAK,EAAE,iCAAiC,EAAE,kBAAkB;AAEnE,OAAO,EAGL,wBAAwB,EACzB,uCAAmC;AAmBpC,MAAM,MAAM,wBAAwB,GAAG;IACrC,SAAS,EAAE;QACT,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH,CAAC;AAEF,eAAO,MAAM,yBAAyB,OAAiB,CAAC;AAExD,qBAAa,kBAAmB,SAAQ,wBAAwB;;IAC9D,MAAM,CAAC,IAAI,QAA6B;gBAKtC,SAAS,EAAE,iCAAiC,EAC5C,MAAM,GAAE,wBAMP;IAMH,mBAAmB,CAAC,OAAO,EAAE,YAAY,CAAC,eAAe,CAAC,GAAG,OAAO;IAOpE,OAAO,IAAI,MAAM;IAIjB;;;;OAIG;IACH,cAAc,IAAI,QAAQ;IA0CpB,cAAc,CAAC,EACnB,aAAa,EACb,UAAU,GACX,EAAE;QACD,aAAa,EAAE,eAAe,CAAC;QAC/B,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,YAAY,CAAC,cAAc,CAAC,EAAE,CAAC;IA2C3C;;;;;;;OAOG;IACG,gBAAgB,CAAC,IAAI,EAAE;QAC3B,aAAa,EAAE,eAAe,CAAC;QAC/B,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,YAAY,CAAC,cAAc,CAAC,EAAE,CAAC;CA6C5C"}
1
+ {"version":3,"file":"EvmAccountProvider.d.cts","sourceRoot":"","sources":["../../src/providers/EvmAccountProvider.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,8BAA8B;AAE1D,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,8BAA8B;AAG7E,OAAO,KAAK,EAEV,eAAe,EAChB,uCAAuC;AACxC,OAAO,KAAK,EAAE,QAAQ,EAAE,qCAAqC;AAE7D,OAAO,KAAK,EAAE,iCAAiC,EAAE,kBAAkB;AAEnE,OAAO,EAGL,wBAAwB,EACzB,uCAAmC;AAmBpC,MAAM,MAAM,wBAAwB,GAAG;IACrC,SAAS,EAAE;QACT,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH,CAAC;AAEF,eAAO,MAAM,yBAAyB,OAAiB,CAAC;AAExD,qBAAa,kBAAmB,SAAQ,wBAAwB;;IAC9D,MAAM,CAAC,IAAI,QAA6B;gBAKtC,SAAS,EAAE,iCAAiC,EAC5C,MAAM,GAAE,wBAMP;IAMH,mBAAmB,CAAC,OAAO,EAAE,YAAY,CAAC,eAAe,CAAC,GAAG,OAAO;IAOpE,OAAO,IAAI,MAAM;IAIjB;;;;OAIG;IACH,cAAc,IAAI,QAAQ;IA0CpB,cAAc,CAAC,EACnB,aAAa,EACb,UAAU,GACX,EAAE;QACD,aAAa,EAAE,eAAe,CAAC;QAC/B,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,YAAY,CAAC,cAAc,CAAC,EAAE,CAAC;IAyE3C;;;;;;;OAOG;IACG,gBAAgB,CAAC,IAAI,EAAE;QAC3B,aAAa,EAAE,eAAe,CAAC;QAC/B,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,YAAY,CAAC,cAAc,CAAC,EAAE,CAAC;CAmC5C"}
@@ -1 +1 @@
1
- {"version":3,"file":"EvmAccountProvider.d.mts","sourceRoot":"","sources":["../../src/providers/EvmAccountProvider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,8BAA8B;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,8BAA8B;AAG7E,OAAO,KAAK,EAEV,eAAe,EAChB,uCAAuC;AACxC,OAAO,KAAK,EAAE,QAAQ,EAAE,qCAAqC;AAE7D,OAAO,KAAK,EAAE,iCAAiC,EAAE,kBAAkB;AAEnE,OAAO,EAGL,wBAAwB,EACzB,uCAAmC;AAmBpC,MAAM,MAAM,wBAAwB,GAAG;IACrC,SAAS,EAAE;QACT,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH,CAAC;AAEF,eAAO,MAAM,yBAAyB,OAAiB,CAAC;AAExD,qBAAa,kBAAmB,SAAQ,wBAAwB;;IAC9D,MAAM,CAAC,IAAI,QAA6B;gBAKtC,SAAS,EAAE,iCAAiC,EAC5C,MAAM,GAAE,wBAMP;IAMH,mBAAmB,CAAC,OAAO,EAAE,YAAY,CAAC,eAAe,CAAC,GAAG,OAAO;IAOpE,OAAO,IAAI,MAAM;IAIjB;;;;OAIG;IACH,cAAc,IAAI,QAAQ;IA0CpB,cAAc,CAAC,EACnB,aAAa,EACb,UAAU,GACX,EAAE;QACD,aAAa,EAAE,eAAe,CAAC;QAC/B,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,YAAY,CAAC,cAAc,CAAC,EAAE,CAAC;IA2C3C;;;;;;;OAOG;IACG,gBAAgB,CAAC,IAAI,EAAE;QAC3B,aAAa,EAAE,eAAe,CAAC;QAC/B,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,YAAY,CAAC,cAAc,CAAC,EAAE,CAAC;CA6C5C"}
1
+ {"version":3,"file":"EvmAccountProvider.d.mts","sourceRoot":"","sources":["../../src/providers/EvmAccountProvider.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,8BAA8B;AAE1D,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,8BAA8B;AAG7E,OAAO,KAAK,EAEV,eAAe,EAChB,uCAAuC;AACxC,OAAO,KAAK,EAAE,QAAQ,EAAE,qCAAqC;AAE7D,OAAO,KAAK,EAAE,iCAAiC,EAAE,kBAAkB;AAEnE,OAAO,EAGL,wBAAwB,EACzB,uCAAmC;AAmBpC,MAAM,MAAM,wBAAwB,GAAG;IACrC,SAAS,EAAE;QACT,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH,CAAC;AAEF,eAAO,MAAM,yBAAyB,OAAiB,CAAC;AAExD,qBAAa,kBAAmB,SAAQ,wBAAwB;;IAC9D,MAAM,CAAC,IAAI,QAA6B;gBAKtC,SAAS,EAAE,iCAAiC,EAC5C,MAAM,GAAE,wBAMP;IAMH,mBAAmB,CAAC,OAAO,EAAE,YAAY,CAAC,eAAe,CAAC,GAAG,OAAO;IAOpE,OAAO,IAAI,MAAM;IAIjB;;;;OAIG;IACH,cAAc,IAAI,QAAQ;IA0CpB,cAAc,CAAC,EACnB,aAAa,EACb,UAAU,GACX,EAAE;QACD,aAAa,EAAE,eAAe,CAAC;QAC/B,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,YAAY,CAAC,cAAc,CAAC,EAAE,CAAC;IAyE3C;;;;;;;OAOG;IACG,gBAAgB,CAAC,IAAI,EAAE;QAC3B,aAAa,EAAE,eAAe,CAAC;QAC/B,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,YAAY,CAAC,cAAc,CAAC,EAAE,CAAC;CAmC5C"}
@@ -9,9 +9,11 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
9
9
  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");
10
10
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
11
  };
12
- var _EvmAccountProvider_instances, _EvmAccountProvider_config, _EvmAccountProvider_createAccount, _EvmAccountProvider_getTransactionCount;
12
+ var _EvmAccountProvider_instances, _EvmAccountProvider_config, _EvmAccountProvider_createAccount, _EvmAccountProvider_getTransactionCount, _EvmAccountProvider_getAddressFromGroupIndex;
13
+ import { publicToAddress } from "@ethereumjs/util";
13
14
  import { EthAccountType } from "@metamask/keyring-api";
14
15
  import { KeyringTypes } from "@metamask/keyring-controller";
16
+ import { add0x, assert, bytesToHex } from "@metamask/utils";
15
17
  import { assertAreBip44Accounts, assertIsBip44Account, BaseBip44AccountProvider } from "./BaseBip44AccountProvider.mjs";
16
18
  import { withRetry, withTimeout } from "./utils.mjs";
17
19
  const ETH_MAINNET_CHAIN_ID = '0x1';
@@ -81,31 +83,20 @@ export class EvmAccountProvider extends BaseBip44AccountProvider {
81
83
  async discoverAccounts(opts) {
82
84
  const provider = this.getEvmProvider();
83
85
  const { entropySource, groupIndex } = opts;
84
- const [address, didCreate] = await __classPrivateFieldGet(this, _EvmAccountProvider_instances, "m", _EvmAccountProvider_createAccount).call(this, {
86
+ const addressFromGroupIndex = await __classPrivateFieldGet(this, _EvmAccountProvider_instances, "m", _EvmAccountProvider_getAddressFromGroupIndex).call(this, {
85
87
  entropySource,
86
88
  groupIndex,
87
89
  });
88
- // We don't want to remove the account if it's the first one.
89
- const shouldCleanup = didCreate && groupIndex !== 0;
90
- try {
91
- const count = await __classPrivateFieldGet(this, _EvmAccountProvider_instances, "m", _EvmAccountProvider_getTransactionCount).call(this, provider, address);
92
- if (count === 0 && shouldCleanup) {
93
- await this.withKeyring({ id: entropySource }, async ({ keyring }) => {
94
- keyring.removeAccount?.(address);
95
- });
96
- return [];
97
- }
98
- }
99
- catch (error) {
100
- // If the RPC request fails and we just created this account for discovery,
101
- // remove it to avoid leaving a dangling account.
102
- if (shouldCleanup) {
103
- await this.withKeyring({ id: entropySource }, async ({ keyring }) => {
104
- keyring.removeAccount?.(address);
105
- });
106
- }
107
- throw error;
90
+ const count = await __classPrivateFieldGet(this, _EvmAccountProvider_instances, "m", _EvmAccountProvider_getTransactionCount).call(this, provider, addressFromGroupIndex);
91
+ if (count === 0) {
92
+ return [];
108
93
  }
94
+ // We have some activity on this address, we try to create the account.
95
+ const [address] = await __classPrivateFieldGet(this, _EvmAccountProvider_instances, "m", _EvmAccountProvider_createAccount).call(this, {
96
+ entropySource,
97
+ groupIndex,
98
+ });
99
+ assert(addressFromGroupIndex === address, 'Created account does not match address from group index.');
109
100
  const account = this.messenger.call('AccountsController:getAccountByAddress', address);
110
101
  assertInternalAccountExists(account);
111
102
  assertIsBip44Account(account);
@@ -135,6 +126,21 @@ _EvmAccountProvider_config = new WeakMap(), _EvmAccountProvider_instances = new
135
126
  backOffMs: __classPrivateFieldGet(this, _EvmAccountProvider_config, "f").discovery.backOffMs,
136
127
  });
137
128
  return parseInt(countHex, 16);
129
+ }, _EvmAccountProvider_getAddressFromGroupIndex = async function _EvmAccountProvider_getAddressFromGroupIndex({ entropySource, groupIndex, }) {
130
+ // NOTE: To avoid exposing this function at keyring level, we just re-use its internal state
131
+ // and compute the derivation here.
132
+ return await this.withKeyring({ id: entropySource }, async ({ keyring }) => {
133
+ // If the account already exist, do not re-derive and just re-use that account.
134
+ const existing = await keyring.getAccounts();
135
+ if (groupIndex < existing.length) {
136
+ return existing[groupIndex];
137
+ }
138
+ // If not, then we just "peek" the next address to avoid creating the account.
139
+ assert(keyring.root, 'Expected HD keyring.root to be set');
140
+ const hdKey = keyring.root.deriveChild(groupIndex);
141
+ assert(hdKey.publicKey, 'Expected public key to be set');
142
+ return add0x(bytesToHex(publicToAddress(hdKey.publicKey, true)).toLowerCase());
143
+ });
138
144
  };
139
145
  EvmAccountProvider.NAME = EVM_ACCOUNT_PROVIDER_NAME;
140
146
  //# sourceMappingURL=EvmAccountProvider.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"EvmAccountProvider.mjs","sourceRoot":"","sources":["../../src/providers/EvmAccountProvider.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Bip44Account } from '@metamask/account-api';\nimport type { EntropySourceId, KeyringAccount } from '@metamask/keyring-api';\nimport { EthAccountType } from '@metamask/keyring-api';\nimport { KeyringTypes } from '@metamask/keyring-controller';\nimport type {\n EthKeyring,\n InternalAccount,\n} from '@metamask/keyring-internal-api';\nimport type { Provider } from '@metamask/network-controller';\nimport type { Hex } from '@metamask/utils';\nimport type { MultichainAccountServiceMessenger } from 'src/types';\n\nimport {\n assertAreBip44Accounts,\n assertIsBip44Account,\n BaseBip44AccountProvider,\n} from './BaseBip44AccountProvider';\nimport { withRetry, withTimeout } from './utils';\n\nconst ETH_MAINNET_CHAIN_ID = '0x1';\n\n/**\n * Asserts an internal account exists.\n *\n * @param account - The internal account to check.\n * @throws An error if the internal account does not exist.\n */\nfunction assertInternalAccountExists(\n account: InternalAccount | undefined,\n): asserts account is InternalAccount {\n if (!account) {\n throw new Error('Internal account does not exist');\n }\n}\n\nexport type EvmAccountProviderConfig = {\n discovery: {\n maxAttempts: number;\n timeoutMs: number;\n backOffMs: number;\n };\n};\n\nexport const EVM_ACCOUNT_PROVIDER_NAME = 'EVM' as const;\n\nexport class EvmAccountProvider extends BaseBip44AccountProvider {\n static NAME = EVM_ACCOUNT_PROVIDER_NAME;\n\n readonly #config: EvmAccountProviderConfig;\n\n constructor(\n messenger: MultichainAccountServiceMessenger,\n config: EvmAccountProviderConfig = {\n discovery: {\n maxAttempts: 3,\n timeoutMs: 500,\n backOffMs: 500,\n },\n },\n ) {\n super(messenger);\n this.#config = config;\n }\n\n isAccountCompatible(account: Bip44Account<InternalAccount>): boolean {\n return (\n account.type === EthAccountType.Eoa &&\n account.metadata.keyring.type === (KeyringTypes.hd as string)\n );\n }\n\n getName(): string {\n return EvmAccountProvider.NAME;\n }\n\n /**\n * Get the EVM provider.\n *\n * @returns The EVM provider.\n */\n getEvmProvider(): Provider {\n const networkClientId = this.messenger.call(\n 'NetworkController:findNetworkClientIdByChainId',\n ETH_MAINNET_CHAIN_ID,\n );\n const { provider } = this.messenger.call(\n 'NetworkController:getNetworkClientById',\n networkClientId,\n );\n return provider;\n }\n\n async #createAccount({\n entropySource,\n groupIndex,\n throwOnGap = false,\n }: {\n entropySource: EntropySourceId;\n groupIndex: number;\n throwOnGap?: boolean;\n }): Promise<[Hex, boolean]> {\n const result = await this.withKeyring<EthKeyring, [Hex, boolean]>(\n { id: entropySource },\n async ({ keyring }) => {\n const existing = await keyring.getAccounts();\n if (groupIndex < existing.length) {\n return [existing[groupIndex], false];\n }\n\n // If the throwOnGap flag is set, we throw an error to prevent index gaps.\n if (throwOnGap && groupIndex !== existing.length) {\n throw new Error('Trying to create too many accounts');\n }\n\n const [added] = await keyring.addAccounts(1);\n return [added, true];\n },\n );\n\n return result;\n }\n\n async createAccounts({\n entropySource,\n groupIndex,\n }: {\n entropySource: EntropySourceId;\n groupIndex: number;\n }): Promise<Bip44Account<KeyringAccount>[]> {\n const [address] = await this.#createAccount({\n entropySource,\n groupIndex,\n throwOnGap: true,\n });\n\n const account = this.messenger.call(\n 'AccountsController:getAccountByAddress',\n address,\n );\n\n // We MUST have the associated internal account.\n assertInternalAccountExists(account);\n\n const accountsArray = [account];\n assertAreBip44Accounts(accountsArray);\n\n return accountsArray;\n }\n\n async #getTransactionCount(\n provider: Provider,\n address: Hex,\n ): Promise<number> {\n const countHex = await withRetry<Hex>(\n () =>\n withTimeout(\n provider.request({\n method: 'eth_getTransactionCount',\n params: [address, 'latest'],\n }),\n this.#config.discovery.timeoutMs,\n ),\n {\n maxAttempts: this.#config.discovery.maxAttempts,\n backOffMs: this.#config.discovery.backOffMs,\n },\n );\n\n return parseInt(countHex, 16);\n }\n\n /**\n * Discover and create accounts for the EVM provider.\n *\n * @param opts - The options for the discovery and creation of accounts.\n * @param opts.entropySource - The entropy source to use for the discovery and creation of accounts.\n * @param opts.groupIndex - The index of the group to create the accounts for.\n * @returns The accounts for the EVM provider.\n */\n async discoverAccounts(opts: {\n entropySource: EntropySourceId;\n groupIndex: number;\n }): Promise<Bip44Account<KeyringAccount>[]> {\n const provider = this.getEvmProvider();\n const { entropySource, groupIndex } = opts;\n\n const [address, didCreate] = await this.#createAccount({\n entropySource,\n groupIndex,\n });\n\n // We don't want to remove the account if it's the first one.\n const shouldCleanup = didCreate && groupIndex !== 0;\n try {\n const count = await this.#getTransactionCount(provider, address);\n\n if (count === 0 && shouldCleanup) {\n await this.withKeyring<EthKeyring>(\n { id: entropySource },\n async ({ keyring }) => {\n keyring.removeAccount?.(address);\n },\n );\n return [];\n }\n } catch (error) {\n // If the RPC request fails and we just created this account for discovery,\n // remove it to avoid leaving a dangling account.\n if (shouldCleanup) {\n await this.withKeyring<EthKeyring>(\n { id: entropySource },\n async ({ keyring }) => {\n keyring.removeAccount?.(address);\n },\n );\n }\n throw error;\n }\n\n const account = this.messenger.call(\n 'AccountsController:getAccountByAddress',\n address,\n );\n assertInternalAccountExists(account);\n assertIsBip44Account(account);\n return [account];\n }\n}\n"]}
1
+ {"version":3,"file":"EvmAccountProvider.mjs","sourceRoot":"","sources":["../../src/providers/EvmAccountProvider.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,OAAO,EAAE,eAAe,EAAE,yBAAyB;AAInD,OAAO,EAAE,cAAc,EAAE,8BAA8B;AACvD,OAAO,EAAE,YAAY,EAAE,qCAAqC;AAM5D,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAY,wBAAwB;AAGtE,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,wBAAwB,EACzB,uCAAmC;AACpC,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,oBAAgB;AAEjD,MAAM,oBAAoB,GAAG,KAAK,CAAC;AAEnC;;;;;GAKG;AACH,SAAS,2BAA2B,CAClC,OAAoC;IAEpC,IAAI,CAAC,OAAO,EAAE;QACZ,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;KACpD;AACH,CAAC;AAUD,MAAM,CAAC,MAAM,yBAAyB,GAAG,KAAc,CAAC;AAExD,MAAM,OAAO,kBAAmB,SAAQ,wBAAwB;IAK9D,YACE,SAA4C,EAC5C,SAAmC;QACjC,SAAS,EAAE;YACT,WAAW,EAAE,CAAC;YACd,SAAS,EAAE,GAAG;YACd,SAAS,EAAE,GAAG;SACf;KACF;QAED,KAAK,CAAC,SAAS,CAAC,CAAC;;QAZV,6CAAkC;QAazC,uBAAA,IAAI,8BAAW,MAAM,MAAA,CAAC;IACxB,CAAC;IAED,mBAAmB,CAAC,OAAsC;QACxD,OAAO,CACL,OAAO,CAAC,IAAI,KAAK,cAAc,CAAC,GAAG;YACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,KAAM,YAAY,CAAC,EAAa,CAC9D,CAAC;IACJ,CAAC;IAED,OAAO;QACL,OAAO,kBAAkB,CAAC,IAAI,CAAC;IACjC,CAAC;IAED;;;;OAIG;IACH,cAAc;QACZ,MAAM,eAAe,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CACzC,gDAAgD,EAChD,oBAAoB,CACrB,CAAC;QACF,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CACtC,wCAAwC,EACxC,eAAe,CAChB,CAAC;QACF,OAAO,QAAQ,CAAC;IAClB,CAAC;IAgCD,KAAK,CAAC,cAAc,CAAC,EACnB,aAAa,EACb,UAAU,GAIX;QACC,MAAM,CAAC,OAAO,CAAC,GAAG,MAAM,uBAAA,IAAI,wEAAe,MAAnB,IAAI,EAAgB;YAC1C,aAAa;YACb,UAAU;YACV,UAAU,EAAE,IAAI;SACjB,CAAC,CAAC;QAEH,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CACjC,wCAAwC,EACxC,OAAO,CACR,CAAC;QAEF,gDAAgD;QAChD,2BAA2B,CAAC,OAAO,CAAC,CAAC;QAErC,MAAM,aAAa,GAAG,CAAC,OAAO,CAAC,CAAC;QAChC,sBAAsB,CAAC,aAAa,CAAC,CAAC;QAEtC,OAAO,aAAa,CAAC;IACvB,CAAC;IAsDD;;;;;;;OAOG;IACH,KAAK,CAAC,gBAAgB,CAAC,IAGtB;QACC,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QACvC,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;QAE3C,MAAM,qBAAqB,GAAG,MAAM,uBAAA,IAAI,mFAA0B,MAA9B,IAAI,EAA2B;YACjE,aAAa;YACb,UAAU;SACX,CAAC,CAAC;QAEH,MAAM,KAAK,GAAG,MAAM,uBAAA,IAAI,8EAAqB,MAAzB,IAAI,EACtB,QAAQ,EACR,qBAAqB,CACtB,CAAC;QACF,IAAI,KAAK,KAAK,CAAC,EAAE;YACf,OAAO,EAAE,CAAC;SACX;QAED,uEAAuE;QACvE,MAAM,CAAC,OAAO,CAAC,GAAG,MAAM,uBAAA,IAAI,wEAAe,MAAnB,IAAI,EAAgB;YAC1C,aAAa;YACb,UAAU;SACX,CAAC,CAAC;QACH,MAAM,CACJ,qBAAqB,KAAK,OAAO,EACjC,0DAA0D,CAC3D,CAAC;QAEF,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CACjC,wCAAwC,EACxC,OAAO,CACR,CAAC;QACF,2BAA2B,CAAC,OAAO,CAAC,CAAC;QACrC,oBAAoB,CAAC,OAAO,CAAC,CAAC;QAC9B,OAAO,CAAC,OAAO,CAAC,CAAC;IACnB,CAAC;;+HA1JD,KAAK,4CAAgB,EACnB,aAAa,EACb,UAAU,EACV,UAAU,GAAG,KAAK,GAKnB;IACC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CACnC,EAAE,EAAE,EAAE,aAAa,EAAE,EACrB,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;QACpB,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,WAAW,EAAE,CAAC;QAC7C,IAAI,UAAU,GAAG,QAAQ,CAAC,MAAM,EAAE;YAChC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,KAAK,CAAC,CAAC;SACtC;QAED,0EAA0E;QAC1E,IAAI,UAAU,IAAI,UAAU,KAAK,QAAQ,CAAC,MAAM,EAAE;YAChD,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;SACvD;QAED,MAAM,CAAC,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;QAC7C,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACvB,CAAC,CACF,CAAC;IAEF,OAAO,MAAM,CAAC;AAChB,CAAC,4CA6BD,KAAK,kDACH,QAAkB,EAClB,OAAY;IAEZ,MAAM,QAAQ,GAAG,MAAM,SAAS,CAC9B,GAAG,EAAE,CACH,WAAW,CACT,QAAQ,CAAC,OAAO,CAAC;QACf,MAAM,EAAE,yBAAyB;QACjC,MAAM,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC;KAC5B,CAAC,EACF,uBAAA,IAAI,kCAAQ,CAAC,SAAS,CAAC,SAAS,CACjC,EACH;QACE,WAAW,EAAE,uBAAA,IAAI,kCAAQ,CAAC,SAAS,CAAC,WAAW;QAC/C,SAAS,EAAE,uBAAA,IAAI,kCAAQ,CAAC,SAAS,CAAC,SAAS;KAC5C,CACF,CAAC;IAEF,OAAO,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;AAChC,CAAC,iDAED,KAAK,uDAA2B,EAC9B,aAAa,EACb,UAAU,GAIX;IACC,4FAA4F;IAC5F,mCAAmC;IACnC,OAAO,MAAM,IAAI,CAAC,WAAW,CAC3B,EAAE,EAAE,EAAE,aAAa,EAAE,EACrB,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;QACpB,+EAA+E;QAC/E,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,WAAW,EAAE,CAAC;QAC7C,IAAI,UAAU,GAAG,QAAQ,CAAC,MAAM,EAAE;YAChC,OAAO,QAAQ,CAAC,UAAU,CAAC,CAAC;SAC7B;QAED,8EAA8E;QAC9E,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,oCAAoC,CAAC,CAAC;QAC3D,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;QACnD,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,+BAA+B,CAAC,CAAC;QAEzD,OAAO,KAAK,CACV,UAAU,CAAC,eAAe,CAAC,KAAK,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,WAAW,EAAE,CACjE,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC;AAzJM,uBAAI,GAAG,yBAAyB,AAA5B,CAA6B","sourcesContent":["import { publicToAddress } from '@ethereumjs/util';\nimport type { Bip44Account } from '@metamask/account-api';\nimport type { HdKeyring } from '@metamask/eth-hd-keyring';\nimport type { EntropySourceId, KeyringAccount } from '@metamask/keyring-api';\nimport { EthAccountType } from '@metamask/keyring-api';\nimport { KeyringTypes } from '@metamask/keyring-controller';\nimport type {\n EthKeyring,\n InternalAccount,\n} from '@metamask/keyring-internal-api';\nimport type { Provider } from '@metamask/network-controller';\nimport { add0x, assert, bytesToHex, type Hex } from '@metamask/utils';\nimport type { MultichainAccountServiceMessenger } from 'src/types';\n\nimport {\n assertAreBip44Accounts,\n assertIsBip44Account,\n BaseBip44AccountProvider,\n} from './BaseBip44AccountProvider';\nimport { withRetry, withTimeout } from './utils';\n\nconst ETH_MAINNET_CHAIN_ID = '0x1';\n\n/**\n * Asserts an internal account exists.\n *\n * @param account - The internal account to check.\n * @throws An error if the internal account does not exist.\n */\nfunction assertInternalAccountExists(\n account: InternalAccount | undefined,\n): asserts account is InternalAccount {\n if (!account) {\n throw new Error('Internal account does not exist');\n }\n}\n\nexport type EvmAccountProviderConfig = {\n discovery: {\n maxAttempts: number;\n timeoutMs: number;\n backOffMs: number;\n };\n};\n\nexport const EVM_ACCOUNT_PROVIDER_NAME = 'EVM' as const;\n\nexport class EvmAccountProvider extends BaseBip44AccountProvider {\n static NAME = EVM_ACCOUNT_PROVIDER_NAME;\n\n readonly #config: EvmAccountProviderConfig;\n\n constructor(\n messenger: MultichainAccountServiceMessenger,\n config: EvmAccountProviderConfig = {\n discovery: {\n maxAttempts: 3,\n timeoutMs: 500,\n backOffMs: 500,\n },\n },\n ) {\n super(messenger);\n this.#config = config;\n }\n\n isAccountCompatible(account: Bip44Account<InternalAccount>): boolean {\n return (\n account.type === EthAccountType.Eoa &&\n account.metadata.keyring.type === (KeyringTypes.hd as string)\n );\n }\n\n getName(): string {\n return EvmAccountProvider.NAME;\n }\n\n /**\n * Get the EVM provider.\n *\n * @returns The EVM provider.\n */\n getEvmProvider(): Provider {\n const networkClientId = this.messenger.call(\n 'NetworkController:findNetworkClientIdByChainId',\n ETH_MAINNET_CHAIN_ID,\n );\n const { provider } = this.messenger.call(\n 'NetworkController:getNetworkClientById',\n networkClientId,\n );\n return provider;\n }\n\n async #createAccount({\n entropySource,\n groupIndex,\n throwOnGap = false,\n }: {\n entropySource: EntropySourceId;\n groupIndex: number;\n throwOnGap?: boolean;\n }): Promise<[Hex, boolean]> {\n const result = await this.withKeyring<EthKeyring, [Hex, boolean]>(\n { id: entropySource },\n async ({ keyring }) => {\n const existing = await keyring.getAccounts();\n if (groupIndex < existing.length) {\n return [existing[groupIndex], false];\n }\n\n // If the throwOnGap flag is set, we throw an error to prevent index gaps.\n if (throwOnGap && groupIndex !== existing.length) {\n throw new Error('Trying to create too many accounts');\n }\n\n const [added] = await keyring.addAccounts(1);\n return [added, true];\n },\n );\n\n return result;\n }\n\n async createAccounts({\n entropySource,\n groupIndex,\n }: {\n entropySource: EntropySourceId;\n groupIndex: number;\n }): Promise<Bip44Account<KeyringAccount>[]> {\n const [address] = await this.#createAccount({\n entropySource,\n groupIndex,\n throwOnGap: true,\n });\n\n const account = this.messenger.call(\n 'AccountsController:getAccountByAddress',\n address,\n );\n\n // We MUST have the associated internal account.\n assertInternalAccountExists(account);\n\n const accountsArray = [account];\n assertAreBip44Accounts(accountsArray);\n\n return accountsArray;\n }\n\n async #getTransactionCount(\n provider: Provider,\n address: Hex,\n ): Promise<number> {\n const countHex = await withRetry<Hex>(\n () =>\n withTimeout(\n provider.request({\n method: 'eth_getTransactionCount',\n params: [address, 'latest'],\n }),\n this.#config.discovery.timeoutMs,\n ),\n {\n maxAttempts: this.#config.discovery.maxAttempts,\n backOffMs: this.#config.discovery.backOffMs,\n },\n );\n\n return parseInt(countHex, 16);\n }\n\n async #getAddressFromGroupIndex({\n entropySource,\n groupIndex,\n }: {\n entropySource: EntropySourceId,\n groupIndex: number,\n }): Promise<Hex> {\n // NOTE: To avoid exposing this function at keyring level, we just re-use its internal state\n // and compute the derivation here.\n return await this.withKeyring<HdKeyring, Hex>(\n { id: entropySource },\n async ({ keyring }) => {\n // If the account already exist, do not re-derive and just re-use that account.\n const existing = await keyring.getAccounts();\n if (groupIndex < existing.length) {\n return existing[groupIndex];\n }\n\n // If not, then we just \"peek\" the next address to avoid creating the account.\n assert(keyring.root, 'Expected HD keyring.root to be set');\n const hdKey = keyring.root.deriveChild(groupIndex);\n assert(hdKey.publicKey, 'Expected public key to be set');\n\n return add0x(\n bytesToHex(publicToAddress(hdKey.publicKey, true)).toLowerCase(),\n );\n },\n );\n }\n\n /**\n * Discover and create accounts for the EVM provider.\n *\n * @param opts - The options for the discovery and creation of accounts.\n * @param opts.entropySource - The entropy source to use for the discovery and creation of accounts.\n * @param opts.groupIndex - The index of the group to create the accounts for.\n * @returns The accounts for the EVM provider.\n */\n async discoverAccounts(opts: {\n entropySource: EntropySourceId;\n groupIndex: number;\n }): Promise<Bip44Account<KeyringAccount>[]> {\n const provider = this.getEvmProvider();\n const { entropySource, groupIndex } = opts;\n\n const addressFromGroupIndex = await this.#getAddressFromGroupIndex({\n entropySource,\n groupIndex,\n });\n\n const count = await this.#getTransactionCount(\n provider,\n addressFromGroupIndex,\n );\n if (count === 0) {\n return [];\n }\n\n // We have some activity on this address, we try to create the account.\n const [address] = await this.#createAccount({\n entropySource,\n groupIndex,\n });\n assert(\n addressFromGroupIndex === address,\n 'Created account does not match address from group index.',\n );\n\n const account = this.messenger.call(\n 'AccountsController:getAccountByAddress',\n address,\n );\n assertInternalAccountExists(account);\n assertIsBip44Account(account);\n return [account];\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask-previews/multichain-account-service",
3
- "version": "0.9.0-preview-3facabc",
3
+ "version": "0.9.0-preview-988ea6d",
4
4
  "description": "Service to manage multichain accounts",
5
5
  "keywords": [
6
6
  "MetaMask",
@@ -47,6 +47,7 @@
47
47
  "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch"
48
48
  },
49
49
  "dependencies": {
50
+ "@ethereumjs/util": "^9.1.0",
50
51
  "@metamask/base-controller": "^8.4.0",
51
52
  "@metamask/eth-snap-keyring": "^17.0.0",
52
53
  "@metamask/key-tree": "^10.1.1",