@metamask/multichain-account-service
Version:
Service to manage multichain accounts
666 lines • 38.4 kB
JavaScript
"use strict";
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_instances, _MultichainAccountWallet_lock, _MultichainAccountWallet_id, _MultichainAccountWallet_providers, _MultichainAccountWallet_entropySource, _MultichainAccountWallet_accountGroups, _MultichainAccountWallet_messenger, _MultichainAccountWallet_trace, _MultichainAccountWallet_log, _MultichainAccountWallet_initialized, _MultichainAccountWallet_status, _MultichainAccountWallet_withLock, _MultichainAccountWallet_getProviders, _MultichainAccountWallet_createAccountsRangeForProvider, _MultichainAccountWallet_createOrUpdateMultichainAccountGroup, _MultichainAccountWallet_buildGroupState, _MultichainAccountWallet_getUnalignedSubRangesForProvider, _MultichainAccountWallet_createMultichainAccountGroupsRange, _MultichainAccountWallet_alignAccountsForRange, _MultichainAccountWallet_createMultichainAccountGroups;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MultichainAccountWallet = void 0;
const account_api_1 = require("@metamask/account-api");
const keyring_api_1 = require("@metamask/keyring-api");
const utils_1 = require("@metamask/utils");
const async_mutex_1 = require("async-mutex");
const analytics_1 = require("./analytics/index.cjs");
const errors_1 = require("./errors.cjs");
const logger_1 = require("./logger.cjs");
const MultichainAccountGroup_1 = require("./MultichainAccountGroup.cjs");
const EvmAccountProvider_1 = require("./providers/EvmAccountProvider.cjs");
const utils_2 = require("./utils.cjs");
/**
* A multichain account wallet that holds multiple multichain accounts (one multichain account per
* group index).
*/
class MultichainAccountWallet {
constructor({ providers, entropySource, messenger, trace, }) {
_MultichainAccountWallet_instances.add(this);
_MultichainAccountWallet_lock.set(this, new async_mutex_1.Mutex());
_MultichainAccountWallet_id.set(this, void 0);
_MultichainAccountWallet_providers.set(this, void 0);
_MultichainAccountWallet_entropySource.set(this, void 0);
_MultichainAccountWallet_accountGroups.set(this, void 0);
_MultichainAccountWallet_messenger.set(this, void 0);
_MultichainAccountWallet_trace.set(this, void 0);
_MultichainAccountWallet_log.set(this, void 0);
_MultichainAccountWallet_initialized.set(this, false);
_MultichainAccountWallet_status.set(this, void 0);
__classPrivateFieldSet(this, _MultichainAccountWallet_id, (0, account_api_1.toMultichainAccountWalletId)(entropySource), "f");
__classPrivateFieldSet(this, _MultichainAccountWallet_providers, providers, "f");
__classPrivateFieldSet(this, _MultichainAccountWallet_entropySource, entropySource, "f");
__classPrivateFieldSet(this, _MultichainAccountWallet_messenger, messenger, "f");
__classPrivateFieldSet(this, _MultichainAccountWallet_accountGroups, new Map(), "f");
__classPrivateFieldSet(this, _MultichainAccountWallet_trace, trace ?? analytics_1.traceFallback, "f");
__classPrivateFieldSet(this, _MultichainAccountWallet_log, (0, logger_1.createModuleLogger)(logger_1.projectLogger, `[${__classPrivateFieldGet(this, _MultichainAccountWallet_id, "f")}]`), "f");
// Initial synchronization (don't emit events during initialization).
__classPrivateFieldSet(this, _MultichainAccountWallet_status, 'uninitialized', "f");
}
/**
* Initialize the wallet and construct the internal representation of multichain account groups.
*
* @param walletState - The wallet state.
*/
init(walletState) {
__classPrivateFieldGet(this, _MultichainAccountWallet_log, "f").call(this, 'Initializing wallet state...');
for (const [groupIndexString, groupState] of Object.entries(walletState)) {
// Have to convert to number because the state keys become strings when we construct the state object in the service
const groupIndex = Number(groupIndexString);
const group = new MultichainAccountGroup_1.MultichainAccountGroup({
groupIndex,
wallet: this,
providers: __classPrivateFieldGet(this, _MultichainAccountWallet_providers, "f"),
messenger: __classPrivateFieldGet(this, _MultichainAccountWallet_messenger, "f"),
});
__classPrivateFieldGet(this, _MultichainAccountWallet_log, "f").call(this, `Creating new group for index ${groupIndex}...`);
group.init(groupState);
__classPrivateFieldGet(this, _MultichainAccountWallet_accountGroups, "f").set(groupIndex, group);
}
if (!__classPrivateFieldGet(this, _MultichainAccountWallet_initialized, "f")) {
__classPrivateFieldSet(this, _MultichainAccountWallet_initialized, true, "f");
__classPrivateFieldSet(this, _MultichainAccountWallet_status, 'ready', "f");
}
__classPrivateFieldGet(this, _MultichainAccountWallet_log, "f").call(this, 'Finished initializing wallet state...');
}
/**
* 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 account_api_1.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 the multichain account wallet current status.
*
* @returns The multichain account wallet current status.
*/
get status() {
return __classPrivateFieldGet(this, _MultichainAccountWallet_status, "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 === (0, account_api_1.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 (!(0, account_api_1.isMultichainAccountGroupId)(id)) {
return undefined;
}
const groupIndex = (0, account_api_1.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.
*
* NOTE: This operation WILL lock the wallet's mutex.
*
* @param groupIndex - The group index to use.
* @param options - Options to configure the account creation.
* @param options.waitForAllProvidersToFinishCreatingAccounts - Whether to wait for all
* account providers to finish creating their accounts before returning. If `false`, only
* the EVM provider is used and non-EVM account creation is deferred via
* {@link MultichainAccountWallet.alignAccountsOf}. Defaults to `false`.
* @throws If groupIndex is greater than the next available group index.
* @throws If any account provider fails to create accounts.
* @returns The multichain account group for this group index.
*/
async createMultichainAccountGroup(groupIndex, options = {}) {
// Use this to avoid having it as `boolean | undefined`.
const waitForAllProvidersToFinishCreatingAccounts = options.waitForAllProvidersToFinishCreatingAccounts ?? false;
return await __classPrivateFieldGet(this, _MultichainAccountWallet_trace, "f").call(this, {
name: analytics_1.TraceName.WalletCreateMultichainAccountGroup,
data: {
groupIndex,
waitForAllProvidersToFinishCreatingAccounts,
},
}, async () => {
(0, utils_2.assertGroupIndexIsValid)(groupIndex, this.getNextGroupIndex());
// If the group already exists, return it.
const existingGroup = this.getMultichainAccountGroup(groupIndex);
if (existingGroup) {
__classPrivateFieldGet(this, _MultichainAccountWallet_log, "f").call(this, `Trying to re-create existing group: [${existingGroup.id}] (idempotent)`);
return existingGroup;
}
// Create a single group with a range of 1 (so we can reuse the batch creation logic) for the
// given group index.
const groups = await __classPrivateFieldGet(this, _MultichainAccountWallet_instances, "m", _MultichainAccountWallet_createMultichainAccountGroups).call(this, { from: groupIndex, to: groupIndex }, options);
const group = groups[0];
(0, utils_1.assert)(group, `Expected group at index ${groupIndex} to exist`);
return group;
});
}
/**
* Creates multiple multichain account groups up to maxGroupIndex.
*
* NOTE: This operation WILL lock the wallet's mutex.
*
* @param range - The range of group indices to create.
* @param range.from - Starting group index to create (inclusive) (defaults to 0).
* @param range.to - Maximum group index to create (inclusive).
* @param options - Options to configure the account creation.
* @param options.waitForAllProvidersToFinishCreatingAccounts - Whether to wait for all
* account providers to finish creating their accounts before returning. If `false`, only
* the EVM provider is used and non-EVM account creation is deferred via
* {@link MultichainAccountWallet.alignAccounts}. Defaults to false.
* @throws If range is invalid (e.g. from is greater than to, from or to is negative, etc.).
* @returns Array of created multichain account groups.
*/
async createMultichainAccountGroups({ from = 0, to }, options = {}) {
// Use this to avoid having it as `boolean | undefined`.
const waitForAllProvidersToFinishCreatingAccounts = options.waitForAllProvidersToFinishCreatingAccounts ?? false;
return await __classPrivateFieldGet(this, _MultichainAccountWallet_trace, "f").call(this, {
name: analytics_1.TraceName.WalletCreateMultichainAccountGroups,
data: {
from,
to,
waitForAllProvidersToFinishCreatingAccounts,
},
}, async () => await __classPrivateFieldGet(this, _MultichainAccountWallet_instances, "m", _MultichainAccountWallet_createMultichainAccountGroups).call(this, { from, to }, options));
}
/**
* 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(), {
waitForAllProvidersToFinishCreatingAccounts: true,
});
}
/**
* Align all accounts from each existing multichain account groups.
*
* NOTE: This operation WILL lock the wallet's mutex.
*/
async alignAccounts() {
if (this.isAligned()) {
__classPrivateFieldGet(this, _MultichainAccountWallet_log, "f").call(this, 'Already aligned, skipping...');
return;
}
const nextGroupIndex = this.getNextGroupIndex();
if (nextGroupIndex > 0) {
__classPrivateFieldGet(this, _MultichainAccountWallet_log, "f").call(this, 'Aligning accounts...');
const from = 0;
const to = nextGroupIndex - 1;
await __classPrivateFieldGet(this, _MultichainAccountWallet_instances, "m", _MultichainAccountWallet_withLock).call(this, 'in-progress:alignment', async () => await __classPrivateFieldGet(this, _MultichainAccountWallet_instances, "m", _MultichainAccountWallet_alignAccountsForRange).call(this, { from, to }, __classPrivateFieldGet(this, _MultichainAccountWallet_providers, "f")));
__classPrivateFieldGet(this, _MultichainAccountWallet_log, "f").call(this, 'Aligned!');
}
}
/**
* Check whether every group in this wallet is aligned.
*
* A wallet is aligned when every multichain account group reports that all
* of its registered providers have contributed at least one account.
* Returns `true` if the wallet has no groups.
*
* @returns `true` when all groups are aligned.
*/
isAligned() {
return this.getMultichainAccountGroups().every((group) => group.isAligned());
}
/**
* Align a specific multichain account group.
*
* NOTE: This operation WILL lock the wallet's mutex.
*
* @param groupIndex - The group index to align.
*/
async alignAccountsOf(groupIndex) {
const group = this.getMultichainAccountGroup(groupIndex);
if (group) {
if (group.isAligned()) {
__classPrivateFieldGet(this, _MultichainAccountWallet_log, "f").call(this, `Group "${group.id}" is already aligned, skipping...`);
return;
}
__classPrivateFieldGet(this, _MultichainAccountWallet_log, "f").call(this, `Aligning accounts for group "${group.id}"...`);
await __classPrivateFieldGet(this, _MultichainAccountWallet_instances, "m", _MultichainAccountWallet_withLock).call(this, 'in-progress:alignment', async () => await __classPrivateFieldGet(this, _MultichainAccountWallet_instances, "m", _MultichainAccountWallet_alignAccountsForRange).call(this, { from: groupIndex, to: groupIndex }, __classPrivateFieldGet(this, _MultichainAccountWallet_providers, "f"), { trace: { data: { groupIndex } } }));
__classPrivateFieldGet(this, _MultichainAccountWallet_log, "f").call(this, `Aligned accounts for group "${group.id}"!`);
}
}
/**
* Discover and create accounts for all providers.
*
* NOTE: This operation WILL lock the wallet's mutex.
*
* @returns The discovered accounts for each provider.
*/
async discoverAccounts() {
return __classPrivateFieldGet(this, _MultichainAccountWallet_instances, "m", _MultichainAccountWallet_withLock).call(this, 'in-progress:discovery', async () => {
// Start with the next available group index (so we can resume the discovery
// from there).
let maxGroupIndex = this.getNextGroupIndex();
const discoveredGroupsState = {};
const addDiscoveryResultToState = (result, providerName, groupIndex) => {
const accountIds = result.map((account) => account.id);
discoveredGroupsState[groupIndex] ?? (discoveredGroupsState[groupIndex] = {});
discoveredGroupsState[groupIndex][providerName] = accountIds;
};
// One serialized loop per provider; all run concurrently
const runProviderDiscovery = async (context) => {
const providerName = context.provider.getName();
const message = (stepName, groupIndex) => `[${providerName}] Discovery ${stepName} for group index: ${groupIndex}`;
while (!context.stopped) {
// Fast‑forward to current high‑water mark
const targetGroupIndex = Math.max(context.groupIndex, maxGroupIndex);
(0, logger_1.projectLogger)(message('started', targetGroupIndex));
let accounts = [];
try {
accounts = await context.provider.discoverAccounts({
entropySource: __classPrivateFieldGet(this, _MultichainAccountWallet_entropySource, "f"),
groupIndex: targetGroupIndex,
});
}
catch (error) {
context.stopped = true;
(0, logger_1.projectLogger)(message(`failed (with: "${(0, utils_2.toErrorMessage)(error)}")`, targetGroupIndex));
(0, errors_1.reportError)(__classPrivateFieldGet(this, _MultichainAccountWallet_messenger, "f"), `Unable to discover accounts with provider "${providerName}"`, error, {
provider: providerName,
groupIndex: targetGroupIndex,
});
break;
}
if (!accounts.length) {
(0, logger_1.projectLogger)(message('stopped (no accounts got discovered)', targetGroupIndex));
context.stopped = true;
break;
}
(0, logger_1.projectLogger)(message('**succeeded**', targetGroupIndex));
context.accounts = context.accounts.concat(accounts);
addDiscoveryResultToState(accounts, providerName, targetGroupIndex);
const nextGroupIndex = targetGroupIndex + 1;
context.groupIndex = nextGroupIndex;
if (nextGroupIndex > maxGroupIndex) {
maxGroupIndex = nextGroupIndex;
}
}
};
const providerContexts = __classPrivateFieldGet(this, _MultichainAccountWallet_providers, "f").map((provider) => ({
provider,
stopped: false,
groupIndex: maxGroupIndex,
accounts: [],
}));
// Start discovery for each providers.
await Promise.all(providerContexts.map(runProviderDiscovery));
// Create discovered groups
for (const [groupIndexString, groupState] of Object.entries(discoveredGroupsState)) {
const groupIndex = Number(groupIndexString);
const group = new MultichainAccountGroup_1.MultichainAccountGroup({
wallet: this,
providers: __classPrivateFieldGet(this, _MultichainAccountWallet_providers, "f"),
groupIndex,
messenger: __classPrivateFieldGet(this, _MultichainAccountWallet_messenger, "f"),
});
group.init(groupState);
__classPrivateFieldGet(this, _MultichainAccountWallet_accountGroups, "f").set(groupIndex, group);
}
// Align missing accounts from group. This is required to create missing account from non-discovered
// indexes for some providers.
const nextGroupIndex = this.getNextGroupIndex();
if (nextGroupIndex > 0) {
await __classPrivateFieldGet(this, _MultichainAccountWallet_instances, "m", _MultichainAccountWallet_alignAccountsForRange).call(this, { from: 0, to: nextGroupIndex - 1 }, __classPrivateFieldGet(this, _MultichainAccountWallet_providers, "f"), {
trace: {
data: {
discovery: true, // Tag to identify discovery-alignment traces in analytics.
},
},
});
}
return providerContexts.flatMap((context) => context.accounts);
});
}
}
exports.MultichainAccountWallet = MultichainAccountWallet;
_MultichainAccountWallet_lock = new WeakMap(), _MultichainAccountWallet_id = new WeakMap(), _MultichainAccountWallet_providers = new WeakMap(), _MultichainAccountWallet_entropySource = new WeakMap(), _MultichainAccountWallet_accountGroups = new WeakMap(), _MultichainAccountWallet_messenger = new WeakMap(), _MultichainAccountWallet_trace = new WeakMap(), _MultichainAccountWallet_log = new WeakMap(), _MultichainAccountWallet_initialized = new WeakMap(), _MultichainAccountWallet_status = new WeakMap(), _MultichainAccountWallet_instances = new WeakSet(), _MultichainAccountWallet_withLock =
/**
* Set the wallet status and run the associated operation callback.
*
* @param status - Wallet status associated with this operation.
* @param operation - Operation to run.
* @returns The operation's result.
* @throws {Error} If the wallet is already running a mutable operation.
*/
async function _MultichainAccountWallet_withLock(status, operation) {
const release = await __classPrivateFieldGet(this, _MultichainAccountWallet_lock, "f").acquire();
try {
__classPrivateFieldGet(this, _MultichainAccountWallet_log, "f").call(this, `Locking wallet with status "${status}"...`);
__classPrivateFieldSet(this, _MultichainAccountWallet_status, status, "f");
__classPrivateFieldGet(this, _MultichainAccountWallet_messenger, "f").publish('MultichainAccountService:walletStatusChange', this.id, __classPrivateFieldGet(this, _MultichainAccountWallet_status, "f"));
return await operation();
}
finally {
__classPrivateFieldSet(this, _MultichainAccountWallet_status, 'ready', "f");
__classPrivateFieldGet(this, _MultichainAccountWallet_messenger, "f").publish('MultichainAccountService:walletStatusChange', this.id, __classPrivateFieldGet(this, _MultichainAccountWallet_status, "f"));
release();
__classPrivateFieldGet(this, _MultichainAccountWallet_log, "f").call(this, `Releasing wallet lock (was "${status}")`);
}
}, _MultichainAccountWallet_getProviders = function _MultichainAccountWallet_getProviders() {
const [evmProvider, ...otherProviders] = __classPrivateFieldGet(this, _MultichainAccountWallet_providers, "f");
(0, utils_1.assert)(evmProvider instanceof EvmAccountProvider_1.EvmAccountProvider, 'EVM account provider must be first');
return [evmProvider, ...otherProviders];
}, _MultichainAccountWallet_createAccountsRangeForProvider =
/**
* Create accounts for a given provider and group index range.
*
* @param provider - The provider to create accounts for.
* @param from - The starting group index (inclusive).
* @param to - The ending group index (inclusive).
* @returns The created accounts.
*/
async function _MultichainAccountWallet_createAccountsRangeForProvider(provider, from, to) {
const isBatching = to > from;
try {
return await provider.createAccounts({
type: keyring_api_1.AccountCreationType.Bip44DeriveIndexRange,
entropySource: __classPrivateFieldGet(this, _MultichainAccountWallet_entropySource, "f"),
range: {
from,
to,
},
});
}
catch (error) {
(0, errors_1.reportError)(__classPrivateFieldGet(this, _MultichainAccountWallet_messenger, "f"), `Unable to create ${isBatching ? 'some accounts (batch)' : 'some accounts'} with provider "${provider.getName()}"`, error, {
range: { from, to },
provider: provider.getName(),
isBatching,
});
throw error;
}
}, _MultichainAccountWallet_createOrUpdateMultichainAccountGroup = function _MultichainAccountWallet_createOrUpdateMultichainAccountGroup(groupIndex, groupState) {
let group = __classPrivateFieldGet(this, _MultichainAccountWallet_accountGroups, "f").get(groupIndex);
if (group) {
// NOTE: This will publish an update event automatically.
group.update(groupState);
__classPrivateFieldGet(this, _MultichainAccountWallet_log, "f").call(this, `Group updated: [${group.id}]`);
}
else {
group = new MultichainAccountGroup_1.MultichainAccountGroup({
wallet: this,
providers: __classPrivateFieldGet(this, _MultichainAccountWallet_providers, "f"),
groupIndex,
messenger: __classPrivateFieldGet(this, _MultichainAccountWallet_messenger, "f"),
});
group.init(groupState);
__classPrivateFieldGet(this, _MultichainAccountWallet_accountGroups, "f").set(groupIndex, group);
__classPrivateFieldGet(this, _MultichainAccountWallet_log, "f").call(this, `Group created: [${group.id}]`);
if (__classPrivateFieldGet(this, _MultichainAccountWallet_initialized, "f")) {
__classPrivateFieldGet(this, _MultichainAccountWallet_messenger, "f").publish('MultichainAccountService:multichainAccountGroupCreated', group);
}
}
return group;
}, _MultichainAccountWallet_buildGroupState =
/**
* Build group state by calling all providers in parallel.
*
* This is a non-locking shared core used by both creation and alignment paths.
* Each provider is asked which contiguous sub-ranges of group indices it needs
* accounts created for via `getSubRanges`, so callers can skip indices that are
* already satisfied (e.g. during alignment).
*
* @param providers - The providers to create accounts for.
* @param getSubRanges - Resolver returning the sub-ranges to create for a
* given provider. Returning an empty array means the provider is skipped.
* @returns The collected group state and any provider failure messages.
*/
async function _MultichainAccountWallet_buildGroupState(providers, getSubRanges) {
const groupStateByGroupIndex = new Map();
const results = await Promise.allSettled(providers.map(async (provider) => {
const providerName = provider.getName();
const subRanges = getSubRanges(provider);
for (const { from, to } of subRanges) {
const accounts = await __classPrivateFieldGet(this, _MultichainAccountWallet_instances, "m", _MultichainAccountWallet_createAccountsRangeForProvider).call(this, provider, from, to);
accounts.forEach((account) => {
const { groupIndex } = account.options.entropy;
let groupState = groupStateByGroupIndex.get(groupIndex);
if (!groupState) {
groupState = {};
groupStateByGroupIndex.set(groupIndex, groupState);
}
if (!groupState[providerName]) {
groupState[providerName] = [];
}
groupState[providerName].push(account.id);
});
}
}));
const failures = providers.reduce((messages, provider, index) => {
const result = results[index];
if (result?.status === 'rejected') {
messages.push(`[${provider.getName()}] ${(0, utils_2.toErrorMessage)(result.reason)}`);
}
return messages;
}, []);
return { groupStateByGroupIndex, failures };
}, _MultichainAccountWallet_getUnalignedSubRangesForProvider = function _MultichainAccountWallet_getUnalignedSubRangesForProvider(provider, from, to) {
const subRanges = [];
let runStart;
for (let groupIndex = from; groupIndex <= to; groupIndex++) {
const group = this.getMultichainAccountGroup(groupIndex);
const aligned = group ? group.isProviderAligned(provider) : false;
if (!aligned) {
runStart ?? (runStart = groupIndex);
}
else if (runStart !== undefined) {
subRanges.push({ from: runStart, to: groupIndex - 1 });
runStart = undefined;
}
}
if (runStart !== undefined) {
subRanges.push({ from: runStart, to });
}
return subRanges;
}, _MultichainAccountWallet_createMultichainAccountGroupsRange =
/**
* Internal method to create a range of multichain account groups.
*
* This method acquires the wallet lock internally and creates accounts for all
* given providers synchronously. Callers decide which providers to pass.
*
* @param range - The range of group indices to create.
* @param range.from - Starting group index to create (inclusive).
* @param range.to - Maximum group index to create (inclusive).
* @param providers - The providers to create accounts for.
* @returns Array of created multichain account groups.
*/
async function _MultichainAccountWallet_createMultichainAccountGroupsRange({ from: rangeFrom, to }, providers) {
return await __classPrivateFieldGet(this, _MultichainAccountWallet_instances, "m", _MultichainAccountWallet_withLock).call(this, 'in-progress:create-accounts', async () => {
const groups = [];
// Get existing groups (fromGroupIndex to nextGroupIndex - 1).
let from = rangeFrom;
for (; from <= to; from++) {
const group = this.getMultichainAccountGroup(from);
if (group) {
groups.push(group);
}
else {
break; // Assuming we have no gap, if the group does not exist, we can stop and create the remaining ones.
}
}
// Create new groups now.
if (from <= to) {
__classPrivateFieldGet(this, _MultichainAccountWallet_log, "f").call(this, `Creating groups from index ${from} to ${to}...`);
const { groupStateByGroupIndex, failures } = await __classPrivateFieldGet(this, _MultichainAccountWallet_instances, "m", _MultichainAccountWallet_buildGroupState).call(this, providers, () => [{ from, to }]);
// Check for provider failures — always treated as hard errors.
if (failures.length) {
throw new Error(failures.reduce((message, failure) => `${message}\n- ${failure}`, 'Unable to create some accounts. Providers threw the following errors:'));
}
// Create or update groups from the collected state.
for (let groupIndex = from; groupIndex <= to; groupIndex++) {
const groupState = groupStateByGroupIndex.get(groupIndex);
if (groupState) {
const group = __classPrivateFieldGet(this, _MultichainAccountWallet_instances, "m", _MultichainAccountWallet_createOrUpdateMultichainAccountGroup).call(this, groupIndex, groupState);
groups.push(group);
}
else {
__classPrivateFieldGet(this, _MultichainAccountWallet_log, "f").call(this, `${logger_1.WARNING_PREFIX} Failed to create new group for group index: ${groupIndex} because no accounts were created for it`);
}
}
}
return groups;
});
}, _MultichainAccountWallet_alignAccountsForRange =
/**
* Align accounts for a range of group indices (non-locking).
*
* Calls all providers in parallel via the batch API. Provider failures are
* logged as warnings (best-effort); no error is thrown.
*
* @param range - The range of group indices to align.
* @param range.from - Starting group index (inclusive).
* @param range.to - Ending group index (inclusive).
* @param providers - The providers to align accounts for.
* @param options - Options.
* @param options.trace - Trace options.
* @param options.trace.data - Optional trace data.
*/
async function _MultichainAccountWallet_alignAccountsForRange({ from, to }, providers, options = {}) {
await __classPrivateFieldGet(this, _MultichainAccountWallet_trace, "f").call(this, {
name: analytics_1.TraceName.WalletAlignment,
data: {
from,
to,
...(0, analytics_1.toProviderDataTraces)(providers),
...options.trace?.data,
},
}, async () => {
const { groupStateByGroupIndex, failures } = await __classPrivateFieldGet(this, _MultichainAccountWallet_instances, "m", _MultichainAccountWallet_buildGroupState).call(this, providers, (provider) => __classPrivateFieldGet(this, _MultichainAccountWallet_instances, "m", _MultichainAccountWallet_getUnalignedSubRangesForProvider).call(this, provider, from, to));
if (failures.length) {
const error = failures.reduce((message, failure) => `${message}\n- ${failure}`, 'Unable to align some accounts. Providers threw the following errors:');
console.warn(error);
__classPrivateFieldGet(this, _MultichainAccountWallet_log, "f").call(this, `${logger_1.WARNING_PREFIX} ${error}`);
}
for (let groupIndex = from; groupIndex <= to; groupIndex++) {
const groupState = groupStateByGroupIndex.get(groupIndex);
if (groupState) {
__classPrivateFieldGet(this, _MultichainAccountWallet_instances, "m", _MultichainAccountWallet_createOrUpdateMultichainAccountGroup).call(this, groupIndex, groupState);
}
}
});
}, _MultichainAccountWallet_createMultichainAccountGroups =
/**
* Creates multiple multichain account groups up to maxGroupIndex.
*
* NOTE: This operation WILL lock the wallet's mutex.
*
* @param range - The range of group indices to create.
* @param range.from - Starting group index to create (inclusive).
* @param range.to - Maximum group index to create (inclusive).
* @param options - Options to configure the account creation.
* @param options.waitForAllProvidersToFinishCreatingAccounts - Whether to wait for all
* account providers to finish creating their accounts before returning. If `false`, only
* the EVM provider is used and non-EVM account creation is deferred via
* {@link MultichainAccountWallet.alignAccounts}. Defaults to false.
* @throws If range is invalid (e.g. from is greater than to, from or to is negative, etc.).
* @returns Array of created multichain account groups.
*/
async function _MultichainAccountWallet_createMultichainAccountGroups({ from, to }, options) {
(0, utils_2.assertGroupIndexRangeIsValid)({ from, to });
(0, utils_2.assertGroupIndexIsValid)(from, this.getNextGroupIndex());
const waitForAllProvidersToFinishCreatingAccounts = options.waitForAllProvidersToFinishCreatingAccounts ?? false;
const [evmProvider, ...otherProviders] = __classPrivateFieldGet(this, _MultichainAccountWallet_instances, "m", _MultichainAccountWallet_getProviders).call(this);
const providers = waitForAllProvidersToFinishCreatingAccounts
? __classPrivateFieldGet(this, _MultichainAccountWallet_providers, "f")
: [evmProvider];
const groups = await __classPrivateFieldGet(this, _MultichainAccountWallet_instances, "m", _MultichainAccountWallet_createMultichainAccountGroupsRange).call(this, { from, to }, providers);
// We need to run a post-alignment since non-EVM accounts have not
// been created yet.
if (!waitForAllProvidersToFinishCreatingAccounts) {
const alignOtherAccounts = async () => {
__classPrivateFieldGet(this, _MultichainAccountWallet_log, "f").call(this, `Aligning accounts... (post)`);
await __classPrivateFieldGet(this, _MultichainAccountWallet_instances, "m", _MultichainAccountWallet_withLock).call(this, 'in-progress:alignment', async () => {
await __classPrivateFieldGet(this, _MultichainAccountWallet_instances, "m", _MultichainAccountWallet_alignAccountsForRange).call(this, { from, to }, otherProviders, {
trace: {
data: {
post: true, // Tag to identify post-alignment traces in analytics.
},
},
});
});
__classPrivateFieldGet(this, _MultichainAccountWallet_log, "f").call(this, 'Aligned accounts! (post)');
};
// eslint-disable-next-line no-void
void alignOtherAccounts().catch((error) => {
const errorMessage = `Unable to align non-EVM accounts from group index ${from} to ${to}`;
__classPrivateFieldGet(this, _MultichainAccountWallet_log, "f").call(this, `${logger_1.ERROR_PREFIX} ${errorMessage}: ${(0, utils_2.toErrorMessage)(error)} (post)`);
console.error(errorMessage, error);
});
}
return groups;
};
//# sourceMappingURL=MultichainAccountWallet.cjs.map