UNPKG

@metamask/multichain-account-service

Version:
260 lines 13 kB
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); 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"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var _MultichainAccountWallet_id, _MultichainAccountWallet_providers, _MultichainAccountWallet_entropySource, _MultichainAccountWallet_accountGroups; import { getGroupIndexFromMultichainAccountGroupId, isMultichainAccountGroupId, toMultichainAccountWalletId } from "@metamask/account-api"; import { toDefaultAccountGroupId } from "@metamask/account-api"; import { AccountWalletType } from "@metamask/account-api"; import { MultichainAccountGroup } from "./MultichainAccountGroup.mjs"; /** * A multichain account wallet that holds multiple multichain accounts (one multichain account per * group index). */ export class MultichainAccountWallet { constructor({ providers, entropySource, }) { _MultichainAccountWallet_id.set(this, void 0); _MultichainAccountWallet_providers.set(this, void 0); _MultichainAccountWallet_entropySource.set(this, void 0); _MultichainAccountWallet_accountGroups.set(this, void 0); __classPrivateFieldSet(this, _MultichainAccountWallet_id, toMultichainAccountWalletId(entropySource), "f"); __classPrivateFieldSet(this, _MultichainAccountWallet_providers, providers, "f"); __classPrivateFieldSet(this, _MultichainAccountWallet_entropySource, entropySource, "f"); __classPrivateFieldSet(this, _MultichainAccountWallet_accountGroups, new Map(), "f"); // Initial synchronization. this.sync(); } /** * Force wallet synchronization. * * This can be used if account providers got new accounts that the wallet * doesn't know about. */ sync() { for (const provider of __classPrivateFieldGet(this, _MultichainAccountWallet_providers, "f")) { for (const account of provider.getAccounts()) { const { entropy } = account.options; // Filter for this wallet only. if (entropy.id !== this.entropySource) { continue; } // This multichain account might exists already. let multichainAccount = __classPrivateFieldGet(this, _MultichainAccountWallet_accountGroups, "f").get(entropy.groupIndex); if (!multichainAccount) { multichainAccount = new MultichainAccountGroup({ groupIndex: entropy.groupIndex, wallet: this, providers: __classPrivateFieldGet(this, _MultichainAccountWallet_providers, "f"), }); // This existing multichain account group might differ from the // `createMultichainAccountGroup` behavior. When creating a new // group, we expect the providers to all succeed. But here, we're // just fetching the account lists from them, so this group might // not be "aligned" yet (e.g having a missing Solana account). // // Since "aligning" is an async operation, it would have to be run // after the first-sync. // TODO: Implement align mechanism to create "missing" accounts. __classPrivateFieldGet(this, _MultichainAccountWallet_accountGroups, "f").set(entropy.groupIndex, multichainAccount); } } } // Now force-sync all remaining multichain accounts. for (const [groupIndex, multichainAccount,] of __classPrivateFieldGet(this, _MultichainAccountWallet_accountGroups, "f").entries()) { multichainAccount.sync(); // Clean up old multichain accounts. if (!multichainAccount.hasAccounts()) { __classPrivateFieldGet(this, _MultichainAccountWallet_accountGroups, "f").delete(groupIndex); } } } /** * Gets the multichain account wallet ID. * * @returns The multichain account wallet ID. */ get id() { return __classPrivateFieldGet(this, _MultichainAccountWallet_id, "f"); } /** * Gets the multichain account wallet type, which is always {@link AccountWalletType.Entropy}. * * @returns The multichain account wallet type. */ get type() { return AccountWalletType.Entropy; } /** * Gets the multichain account wallet entropy source. * * @returns The multichain account wallet entropy source. */ get entropySource() { return __classPrivateFieldGet(this, _MultichainAccountWallet_entropySource, "f"); } /** * Gets multichain account for a given ID. * The default group ID will default to the multichain account with index 0. * * @param id - Account group ID. * @returns Account group. */ getAccountGroup(id) { // We consider the "default case" to be mapped to index 0. if (id === toDefaultAccountGroupId(this.id)) { return __classPrivateFieldGet(this, _MultichainAccountWallet_accountGroups, "f").get(0); } // If it is not a valid ID, we cannot extract the group index // from it, so we fail fast. if (!isMultichainAccountGroupId(id)) { return undefined; } const groupIndex = getGroupIndexFromMultichainAccountGroupId(id); return __classPrivateFieldGet(this, _MultichainAccountWallet_accountGroups, "f").get(groupIndex); } /** * Gets all multichain accounts. Similar to {@link MultichainAccountWallet.getMultichainAccountGroups}. * * @returns The multichain accounts. */ getAccountGroups() { return this.getMultichainAccountGroups(); } /** * Gets multichain account group for a given index. * * @param groupIndex - Multichain account index. * @returns The multichain account associated with the given index. */ getMultichainAccountGroup(groupIndex) { return __classPrivateFieldGet(this, _MultichainAccountWallet_accountGroups, "f").get(groupIndex); } /** * Gets all multichain account groups. * * @returns The multichain accounts. */ getMultichainAccountGroups() { return Array.from(__classPrivateFieldGet(this, _MultichainAccountWallet_accountGroups, "f").values()); // TODO: Prevent copy here. } /** * Gets next group index for this wallet. * * @returns The next group index of this wallet. */ getNextGroupIndex() { // We do not check for gaps. return (Math.max(-1, // So it will default to 0 if no groups. ...__classPrivateFieldGet(this, _MultichainAccountWallet_accountGroups, "f").keys()) + 1); } /** * Creates a multichain account group for a given group index. * * @param groupIndex - The group index to use. * @throws If any of the account providers fails to create their accounts. * @returns The multichain account group for this group index. */ async createMultichainAccountGroup(groupIndex) { const nextGroupIndex = this.getNextGroupIndex(); if (groupIndex > nextGroupIndex) { throw new Error(`You cannot use a group index that is higher than the next available one: expected <=${nextGroupIndex}, got ${groupIndex}`); } let group = this.getMultichainAccountGroup(groupIndex); if (group) { // If the group already exists, we just `sync` it and returns the same // reference. group.sync(); return group; } const results = await Promise.allSettled(__classPrivateFieldGet(this, _MultichainAccountWallet_providers, "f").map((provider) => provider.createAccounts({ entropySource: __classPrivateFieldGet(this, _MultichainAccountWallet_entropySource, "f"), groupIndex, }))); // -------------------------------------------------------------------------------- // READ THIS CAREFULLY: // // Since we're not "fully supporting multichain" for now, we still rely on single // :accountCreated events to sync multichain account groups and wallets. Which means // that even if of the provider fails, some accounts will still be created on some // other providers and will become "available" on the `AccountsController`, like: // // 1. Creating a multichain account group for index 1 // 2. EvmAccountProvider.createAccounts returns the EVM account for index 1 // * AccountsController WILL fire :accountCreated for this account // * This account WILL BE "available" on the AccountsController state // 3. SolAccountProvider.createAccounts fails to create a Solana account for index 1 // * AccountsController WON't fire :accountCreated for this account // * This account WON'T be "available" on the Account // 4. MultichainAccountService will receive a :accountCreated for the EVM account from // step 2 and will create a new multichain account group for index 1, but it won't // receive any event for the Solana account of this group. Thus, this group won't be // "aligned" (missing "blockchain account" on this group). // // -------------------------------------------------------------------------------- // If any of the provider failed to create their accounts, then we consider the // multichain account group to have failed too. if (results.some((result) => result.status === 'rejected')) { // NOTE: Some accounts might still have been created on other account providers. We // don't rollback them. const error = `Unable to create multichain account group for index: ${groupIndex}`; let warn = `${error}:`; for (const result of results) { if (result.status === 'rejected') { warn += `\n- ${result.reason}`; } } console.warn(warn); throw new Error(error); } // Because of the :accountAdded automatic sync, we might already have created the // group, so we first try to get it. group = this.getMultichainAccountGroup(groupIndex); if (!group) { // If for some reason it's still not created, we're creating it explicitly now: group = new MultichainAccountGroup({ wallet: this, providers: __classPrivateFieldGet(this, _MultichainAccountWallet_providers, "f"), groupIndex, }); } // Register the account to our internal map. __classPrivateFieldGet(this, _MultichainAccountWallet_accountGroups, "f").set(groupIndex, group); // `group` cannot be undefined here. return group; } /** * Creates the next multichain account group. * * @throws If any of the account providers fails to create their accounts. * @returns The multichain account group for the next group index available. */ async createNextMultichainAccountGroup() { return this.createMultichainAccountGroup(this.getNextGroupIndex()); } /** * Align all multichain account groups. */ async alignGroups() { const groups = this.getMultichainAccountGroups(); await Promise.all(groups.map((g) => g.align())); } /** * Align a specific multichain account group. * * @param groupIndex - The group index to align. */ async alignGroup(groupIndex) { const group = this.getMultichainAccountGroup(groupIndex); if (group) { await group.align(); } } } _MultichainAccountWallet_id = new WeakMap(), _MultichainAccountWallet_providers = new WeakMap(), _MultichainAccountWallet_entropySource = new WeakMap(), _MultichainAccountWallet_accountGroups = new WeakMap(); //# sourceMappingURL=MultichainAccountWallet.mjs.map