@metamask-previews/multichain-account-service 0.9.0-preview-33665a7 → 0.9.0-preview-34d02af7
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 +7 -0
- package/dist/providers/EvmAccountProvider.cjs +30 -24
- package/dist/providers/EvmAccountProvider.cjs.map +1 -1
- package/dist/providers/EvmAccountProvider.d.cts.map +1 -1
- package/dist/providers/EvmAccountProvider.d.mts.map +1 -1
- package/dist/providers/EvmAccountProvider.mjs +28 -22
- package/dist/providers/EvmAccountProvider.mjs.map +1 -1
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -13,6 +13,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
13
13
|
- Add custom account provider configs ([#6624](https://github.com/MetaMask/core/pull/6624))
|
|
14
14
|
- This new config can be set by the clients to update discovery timeout/retry values.
|
|
15
15
|
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- No longer create temporary EVM account during discovery ([#6650](https://github.com/MetaMask/core/pull/6650))
|
|
19
|
+
- We used to create the EVM account and remove it if there was no activity for that account. Now we're just deriving the next address directly, which avoids state mutation.
|
|
20
|
+
- This prevents `:accountAdded` event from being published, which also prevents account-tree and multichain-account service updates.
|
|
21
|
+
- Backup & sync will no longer synchronize this temporary account group, which was causing a bug that persisted it on the user profile and left it permanently.
|
|
22
|
+
|
|
16
23
|
## [0.9.0]
|
|
17
24
|
|
|
18
25
|
### Added
|
|
@@ -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
|
|
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
|
|
89
|
+
const addressFromGroupIndex = await __classPrivateFieldGet(this, _EvmAccountProvider_instances, "m", _EvmAccountProvider_getAddressFromGroupIndex).call(this, {
|
|
88
90
|
entropySource,
|
|
89
91
|
groupIndex,
|
|
90
92
|
});
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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,
|
|
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":";;;;;;;;;;;;;;;AAAA,2CAAmD;AAInD,uDAAuD;AACvD,qEAA4D;AAM5D,2CAAsE;AAGtE,6EAIoC;AACpC,uCAAiD;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;AAUY,QAAA,yBAAyB,GAAG,KAAc,CAAC;AAExD,MAAa,kBAAmB,SAAQ,mDAAwB;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,4BAAc,CAAC,GAAG;YACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,KAAM,iCAAY,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,IAAA,iDAAsB,EAAC,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,IAAA,cAAM,EACJ,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,IAAA,+CAAoB,EAAC,OAAO,CAAC,CAAC;QAC9B,OAAO,CAAC,OAAO,CAAC,CAAC;IACnB,CAAC;;AAzMH,gDA0MC;+HA3JC,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,IAAA,iBAAS,EAC9B,GAAG,EAAE,CACH,IAAA,mBAAW,EACT,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,IAAA,cAAM,EAAC,OAAO,CAAC,IAAI,EAAE,oCAAoC,CAAC,CAAC;QAC3D,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;QACnD,IAAA,cAAM,EAAC,KAAK,CAAC,SAAS,EAAE,+BAA+B,CAAC,CAAC;QAEzD,OAAO,IAAA,aAAK,EACV,IAAA,kBAAU,EAAC,IAAA,sBAAe,EAAC,KAAK,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,WAAW,EAAE,CACjE,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC;AAzJM,uBAAI,GAAG,iCAAyB,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"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EvmAccountProvider.d.cts","sourceRoot":"","sources":["../../src/providers/EvmAccountProvider.ts"],"names":[],"mappings":"
|
|
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":"
|
|
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
|
|
86
|
+
const addressFromGroupIndex = await __classPrivateFieldGet(this, _EvmAccountProvider_instances, "m", _EvmAccountProvider_getAddressFromGroupIndex).call(this, {
|
|
85
87
|
entropySource,
|
|
86
88
|
groupIndex,
|
|
87
89
|
});
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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":";;;;;;;;;;;;AAEA,OAAO,EAAE,cAAc,EAAE,8BAA8B;AACvD,OAAO,EAAE,YAAY,EAAE,qCAAqC;AAS5D,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;IAwBD;;;;;;;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,CAAC,OAAO,EAAE,SAAS,CAAC,GAAG,MAAM,uBAAA,IAAI,wEAAe,MAAnB,IAAI,EAAgB;YACrD,aAAa;YACb,UAAU;SACX,CAAC,CAAC;QAEH,6DAA6D;QAC7D,MAAM,aAAa,GAAG,SAAS,IAAI,UAAU,KAAK,CAAC,CAAC;QACpD,IAAI;YACF,MAAM,KAAK,GAAG,MAAM,uBAAA,IAAI,8EAAqB,MAAzB,IAAI,EAAsB,QAAQ,EAAE,OAAO,CAAC,CAAC;YAEjE,IAAI,KAAK,KAAK,CAAC,IAAI,aAAa,EAAE;gBAChC,MAAM,IAAI,CAAC,WAAW,CACpB,EAAE,EAAE,EAAE,aAAa,EAAE,EACrB,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;oBACpB,OAAO,CAAC,aAAa,EAAE,CAAC,OAAO,CAAC,CAAC;gBACnC,CAAC,CACF,CAAC;gBACF,OAAO,EAAE,CAAC;aACX;SACF;QAAC,OAAO,KAAK,EAAE;YACd,2EAA2E;YAC3E,iDAAiD;YACjD,IAAI,aAAa,EAAE;gBACjB,MAAM,IAAI,CAAC,WAAW,CACpB,EAAE,EAAE,EAAE,aAAa,EAAE,EACrB,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;oBACpB,OAAO,CAAC,aAAa,EAAE,CAAC,OAAO,CAAC,CAAC;gBACnC,CAAC,CACF,CAAC;aACH;YACD,MAAM,KAAK,CAAC;SACb;QAED,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;;+HAtID,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;AA3HM,uBAAI,GAAG,yBAAyB,AAA5B,CAA6B","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":"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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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metamask-previews/multichain-account-service",
|
|
3
|
-
"version": "0.9.0-preview-
|
|
3
|
+
"version": "0.9.0-preview-34d02af7",
|
|
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",
|