Skip to content

Commit af6b40e

Browse files
committed
feat(multichain-account-service): add :walletStatusChange event + mutex for concurrent mutable operations
1 parent ffbf1d2 commit af6b40e

12 files changed

+357
-364
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@
4747
"resolutions": {
4848
"[email protected]": "^6.5.7",
4949
"fast-xml-parser@^4.3.4": "^4.4.1",
50-
"[email protected]": "^7.5.10"
50+
"[email protected]": "^7.5.10",
51+
"@metamask/account-api@^0.9.0": "npm:@metamask-previews/[email protected]"
5152
},
5253
"devDependencies": {
5354
"@babel/core": "^7.23.5",

packages/multichain-account-service/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- Add mutable operation lock (per wallets) ([#6527](https://github.com/MetaMask/core/pull/6527))
13+
- Operations such as discovery, alignment, group creation will now lock an internal mutex (per wallets).
14+
- Add wallet status tracking with `:walletStatusChange` event ([#6527](https://github.com/MetaMask/core/pull/6527))
15+
- This can be used to track what's the current status of a wallet (e.g. which operation is currently running OR if the wallet is ready to run any new operations).
16+
- Add `MultichainAccountWalletStatus` enum ([#6527](https://github.com/MetaMask/core/pull/6527))
17+
- Enumeration of all possible wallet statuses.
18+
- Add `MultichainAccountWallet.status` ([#6527](https://github.com/MetaMask/core/pull/6527))
19+
- To get the current status of a multichain account wallet instance.
1220
- Add multichain account group lifecycle events ([#6441](https://github.com/MetaMask/core/pull/6441))
1321
- Add `multichainAccountGroupCreated` event emitted from wallet level when new groups are created.
1422
- Add `multichainAccountGroupUpdated` event emitted from wallet level when groups are synchronized.
1523

1624
### Changed
1725

26+
- **BREAKING:** Remove `MultichainAccountService:getIsAlignementInProgress` action ([#6527](https://github.com/MetaMask/core/pull/6527))
27+
- This is now being replaced with the wallet's status logic.
1828
- Bump `@metamask/keyring-api` from `^20.1.0` to `^21.0.0` ([#6560](https://github.com/MetaMask/core/pull/6560))
1929
- Bump `@metamask/keyring-internal-api` from `^8.1.0` to `^9.0.0` ([#6560](https://github.com/MetaMask/core/pull/6560))
2030
- Bump `@metamask/keyring-snap-client` from `^7.0.0` to `^8.0.0` ([#6560](https://github.com/MetaMask/core/pull/6560))

packages/multichain-account-service/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@
5656
"@metamask/snaps-sdk": "^9.0.0",
5757
"@metamask/snaps-utils": "^11.0.0",
5858
"@metamask/superstruct": "^3.1.0",
59-
"@metamask/utils": "^11.4.2"
59+
"@metamask/utils": "^11.4.2",
60+
"async-mutex": "^0.5.0"
6061
},
6162
"devDependencies": {
6263
"@metamask/account-api": "^0.12.0",

packages/multichain-account-service/src/MultichainAccountGroup.test.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
toMultichainAccountGroupId,
66
toMultichainAccountWalletId,
77
} from '@metamask/account-api';
8+
import type { Messenger } from '@metamask/base-controller';
89
import { EthScope, SolScope } from '@metamask/keyring-api';
910
import type { InternalAccount } from '@metamask/keyring-internal-api';
1011

@@ -18,13 +19,20 @@ import {
1819
MOCK_WALLET_1_ENTROPY_SOURCE,
1920
MOCK_WALLET_1_EVM_ACCOUNT,
2021
MOCK_WALLET_1_SOL_ACCOUNT,
21-
setupAccountProvider,
22+
setupNamedAccountProvider,
2223
getMultichainAccountServiceMessenger,
2324
getRootMessenger,
2425
} from './tests';
26+
import type {
27+
AllowedActions,
28+
AllowedEvents,
29+
MultichainAccountServiceActions,
30+
MultichainAccountServiceEvents,
31+
} from './types';
2532

2633
function setup({
2734
groupIndex = 0,
35+
messenger = getRootMessenger(),
2836
accounts = [
2937
[MOCK_WALLET_1_EVM_ACCOUNT],
3038
[
@@ -34,26 +42,33 @@ function setup({
3442
MOCK_SNAP_ACCOUNT_2, // Non-BIP-44 account.
3543
],
3644
],
37-
}: { groupIndex?: number; accounts?: InternalAccount[][] } = {}): {
45+
}: {
46+
groupIndex?: number;
47+
messenger?: Messenger<
48+
MultichainAccountServiceActions | AllowedActions,
49+
MultichainAccountServiceEvents | AllowedEvents
50+
>;
51+
accounts?: InternalAccount[][];
52+
} = {}): {
3853
wallet: MultichainAccountWallet<Bip44Account<InternalAccount>>;
3954
group: MultichainAccountGroup<Bip44Account<InternalAccount>>;
4055
providers: MockAccountProvider[];
4156
} {
4257
const providers = accounts.map((providerAccounts) => {
43-
return setupAccountProvider({ accounts: providerAccounts });
58+
return setupNamedAccountProvider({ accounts: providerAccounts });
4459
});
4560

4661
const wallet = new MultichainAccountWallet<Bip44Account<InternalAccount>>({
47-
providers,
4862
entropySource: MOCK_WALLET_1_ENTROPY_SOURCE,
49-
messenger: getMultichainAccountServiceMessenger(getRootMessenger()),
63+
messenger: getMultichainAccountServiceMessenger(messenger),
64+
providers,
5065
});
5166

5267
const group = new MultichainAccountGroup({
5368
wallet,
5469
groupIndex,
5570
providers,
56-
messenger: getMultichainAccountServiceMessenger(getRootMessenger()),
71+
messenger: getMultichainAccountServiceMessenger(messenger),
5772
});
5873

5974
return { wallet, group, providers };

packages/multichain-account-service/src/MultichainAccountService.test.ts

Lines changed: 2 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {
2626
getRootMessenger,
2727
makeMockAccountProvider,
2828
mockAsInternalAccount,
29-
setupAccountProvider,
29+
setupNamedAccountProvider,
3030
} from './tests';
3131
import type {
3232
AllowedActions,
@@ -72,7 +72,7 @@ function mockAccountProvider<Provider>(
7272
.mocked(providerClass)
7373
.mockImplementation(() => mocks as unknown as Provider);
7474

75-
setupAccountProvider({
75+
setupNamedAccountProvider({
7676
mocks,
7777
accounts,
7878
filter: (account) => account.type === type,
@@ -692,58 +692,6 @@ describe('MultichainAccountService', () => {
692692
});
693693
});
694694

695-
describe('getIsAlignmentInProgress', () => {
696-
it('returns false initially', () => {
697-
const { service } = setup({
698-
accounts: [MOCK_HD_ACCOUNT_1],
699-
});
700-
expect(service.getIsAlignmentInProgress()).toBe(false);
701-
});
702-
703-
it('returns true during alignWallets and false after completion', async () => {
704-
const { service } = setup({
705-
accounts: [MOCK_HD_ACCOUNT_1],
706-
});
707-
708-
const alignmentPromise = service.alignWallets();
709-
expect(service.getIsAlignmentInProgress()).toBe(true);
710-
711-
await alignmentPromise;
712-
expect(service.getIsAlignmentInProgress()).toBe(false);
713-
});
714-
715-
it('returns true during alignWallet and false after completion', async () => {
716-
const { service } = setup({
717-
accounts: [MOCK_HD_ACCOUNT_1],
718-
});
719-
720-
const alignmentPromise = service.alignWallet(
721-
MOCK_HD_KEYRING_1.metadata.id,
722-
);
723-
expect(service.getIsAlignmentInProgress()).toBe(true);
724-
725-
await alignmentPromise;
726-
expect(service.getIsAlignmentInProgress()).toBe(false);
727-
});
728-
729-
it('returns false after alignment completes even with provider errors', async () => {
730-
const { service, mocks } = setup({
731-
accounts: [MOCK_HD_ACCOUNT_1],
732-
});
733-
734-
// Mock a provider error during alignment
735-
mocks.EvmAccountProvider.createAccounts.mockRejectedValueOnce(
736-
new Error('Test error'),
737-
);
738-
739-
// Alignment should complete gracefully without throwing
740-
await service.alignWallets();
741-
742-
// Flag should be reset even after provider errors
743-
expect(service.getIsAlignmentInProgress()).toBe(false);
744-
});
745-
});
746-
747695
describe('actions', () => {
748696
it('gets a multichain account with MultichainAccountService:getMultichainAccount', () => {
749697
const accounts = [MOCK_HD_ACCOUNT_1];
@@ -885,18 +833,6 @@ describe('MultichainAccountService', () => {
885833
),
886834
).toBeUndefined();
887835
});
888-
889-
it('gets alignment progress with MultichainAccountService:getIsAlignmentInProgress', () => {
890-
const { messenger } = setup({
891-
accounts: [MOCK_HD_ACCOUNT_1],
892-
});
893-
894-
const isInProgress = messenger.call(
895-
'MultichainAccountService:getIsAlignmentInProgress',
896-
);
897-
898-
expect(isInProgress).toBe(false);
899-
});
900836
});
901837

902838
describe('setBasicFunctionality', () => {

packages/multichain-account-service/src/MultichainAccountService.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,6 @@ export class MultichainAccountService {
120120
'MultichainAccountService:alignWallet',
121121
(...args) => this.alignWallet(...args),
122122
);
123-
this.#messenger.registerActionHandler(
124-
'MultichainAccountService:getIsAlignmentInProgress',
125-
() => this.getIsAlignmentInProgress(),
126-
);
127123
this.#messenger.subscribe('AccountsController:accountAdded', (account) =>
128124
this.#handleOnAccountAdded(account),
129125
);
@@ -397,17 +393,6 @@ export class MultichainAccountService {
397393
}
398394
}
399395

400-
/**
401-
* Gets whether wallet alignment is currently in progress.
402-
*
403-
* @returns True if any wallet alignment is in progress, false otherwise.
404-
*/
405-
getIsAlignmentInProgress(): boolean {
406-
return Array.from(this.#wallets.values()).some((wallet) =>
407-
wallet.getIsAlignmentInProgress(),
408-
);
409-
}
410-
411396
/**
412397
* Align all multichain account wallets.
413398
*/

0 commit comments

Comments
 (0)