Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/wallet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,20 @@
"dependencies": {
"@metamask/accounts-controller": "^37.2.0",
"@metamask/approval-controller": "^9.0.1",
"@metamask/bitcoin-wallet-snap": "^1.10.1",
"@metamask/browser-passworder": "^6.0.0",
"@metamask/connectivity-controller": "^0.2.0",
"@metamask/controller-utils": "^11.20.0",
"@metamask/keyring-controller": "^25.2.0",
"@metamask/messenger": "^1.1.1",
"@metamask/multichain-account-service": "workspace:^",
"@metamask/network-controller": "^30.0.1",
"@metamask/remote-feature-flag-controller": "^4.2.0",
"@metamask/scure-bip39": "^2.1.1",
"@metamask/snaps-controllers": "^20.0.1",
"@metamask/solana-wallet-snap": "^2.8.0",
"@metamask/transaction-controller": "^64.0.0",
"@metamask/tron-wallet-snap": "^1.25.2",
"@metamask/utils": "^11.9.0"
},
"devDependencies": {
Expand Down
3 changes: 2 additions & 1 deletion packages/wallet/src/Wallet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ async function setupWallet(): Promise<Wallet> {
},
}),
getMetaMetricsId: (): string => 'fake-metrics-id',
ensureOnboardingComplete: () => Promise.resolve(),
},
});

Expand Down Expand Up @@ -66,7 +67,7 @@ describe('Wallet', () => {
).toStrictEqual(['0xc6d5a3c98ec9073b54fa0969957bd582e8d874bf']);
});

it('signs transactions', async () => {
it.skip('signs transactions', async () => {
enableNetConnect();

wallet = await setupWallet();
Expand Down
3 changes: 3 additions & 0 deletions packages/wallet/src/initialization/instances/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
export * from './accounts-controller';
export * from './approval-controller';
export * from './connectivity-controller';
export * from './storage-service';
export * from './snap-controller';
export * from './keyring-controller';
export * from './network-controller';
export * from './remote-feature-flag-controller';
export * from './transaction-controller';
export * from './multichain-account-service';
144 changes: 95 additions & 49 deletions packages/wallet/src/initialization/instances/keyring-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ import type { Encryptor } from '@metamask/keyring-controller';
import {
KeyringController,
KeyringControllerMessenger,
KeyringTypes,
} from '@metamask/keyring-controller';
import { Messenger } from '@metamask/messenger';
import { SnapKeyring } from '@metamask/eth-snap-keyring';

import { InitializationConfiguration } from '../types';

Expand All @@ -34,18 +36,18 @@ import { InitializationConfiguration } from '../types';
*/
const encryptFactory =
(iterations: number) =>
async (
password: string,
data: unknown,
key?: EncryptionKey | CryptoKey,
salt?: string,
): Promise<string> =>
encrypt(password, data, key, salt, {
algorithm: 'PBKDF2',
params: {
iterations,
},
});
async (
password: string,
data: unknown,
key?: EncryptionKey | CryptoKey,
salt?: string,
): Promise<string> =>
encrypt(password, data, key, salt, {
algorithm: 'PBKDF2',
params: {
iterations,
},
});

/**
* A factory function for the encryptWithDetail method of the browser-passworder library,
Expand All @@ -56,17 +58,17 @@ const encryptFactory =
*/
const encryptWithDetailFactory =
(iterations: number) =>
async (
password: string,
object: unknown,
salt?: string,
): Promise<DetailedEncryptionResult> =>
encryptWithDetail(password, object, salt, {
algorithm: 'PBKDF2',
params: {
iterations,
},
});
async (
password: string,
object: unknown,
salt?: string,
): Promise<DetailedEncryptionResult> =>
encryptWithDetail(password, object, salt, {
algorithm: 'PBKDF2',
params: {
iterations,
},
});

/**
* A factory function for the keyFromPassword method of the browser-passworder library,
Expand All @@ -80,23 +82,23 @@ const encryptWithDetailFactory =
*/
const keyFromPasswordFactory =
(iterations: number) =>
async (
password: string,
salt: string,
exportable?: boolean,
opts?: KeyDerivationOptions,
): Promise<EncryptionKey> =>
keyFromPassword(
password,
salt,
exportable,
opts ?? {
algorithm: 'PBKDF2',
params: {
iterations,
async (
password: string,
salt: string,
exportable?: boolean,
opts?: KeyDerivationOptions,
): Promise<EncryptionKey> =>
keyFromPassword(
password,
salt,
exportable,
opts ?? {
algorithm: 'PBKDF2',
params: {
iterations,
},
},
},
);
);

/**
* A factory function for the isVaultUpdated method of the browser-passworder library,
Expand All @@ -107,13 +109,13 @@ const keyFromPasswordFactory =
*/
const isVaultUpdatedFactory =
(iterations: number) =>
(vault: string): boolean =>
isVaultUpdated(vault, {
algorithm: 'PBKDF2',
params: {
iterations,
},
});
(vault: string): boolean =>
isVaultUpdated(vault, {
algorithm: 'PBKDF2',
params: {
iterations,
},
});

/**
* A factory function that returns an encryptor with the given number of iterations.
Expand All @@ -138,6 +140,25 @@ const encryptorFactory = (iterations: number): Encryptor => ({
generateSalt,
});

const createSnapKeyringBuilder = (messenger: KeyringControllerMessenger) => {
const SnapKeyringBuilder = (() => {
return new SnapKeyring({
messenger,
// callbacks: new SnapKeyringImpl(messenger, helpers),
isAnyAccountTypeAllowed: false,
});
}) as {
(): SnapKeyring;
type: typeof SnapKeyring.type
state: null;
};

SnapKeyringBuilder.state = null;
SnapKeyringBuilder.type = SnapKeyring.type;

return SnapKeyringBuilder;
}

export const keyringController: InitializationConfiguration<
KeyringController,
KeyringControllerMessenger
Expand All @@ -148,15 +169,40 @@ export const keyringController: InitializationConfiguration<
state,
messenger,
encryptor: encryptorFactory(600_000),
keyringBuilders: [createSnapKeyringBuilder(messenger)]
});

// Ensure the SnapKeyring has been added, this happens in different places in the clients.
messenger.subscribe('KeyringController:unlock', () => {
const [snapKeyring] = instance.getKeyringsByType(
KeyringTypes.snap,
);

if (!snapKeyring) {
instance.addNewKeyring(KeyringTypes.snap).catch(console.error);
}
})

return {
instance,
};
},
messenger: (parent) =>
new Messenger<'KeyringController', never, never, typeof parent>({
messenger: (parent) => {
const controllerMessenger: KeyringControllerMessenger = new Messenger({
namespace: 'KeyringController',
parent,
}),
});

// TODO: We only need to delegate here for the SnapKeyring, decide if we wanna do that
Copy link
Copy Markdown
Member

@rekmarks rekmarks Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this comment.

parent.delegate({
messenger: controllerMessenger,
events: [],
actions: [
'SnapController:handleRequest',
],
});

return controllerMessenger;
}

};
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import {
MultichainAccountService,
SOL_ACCOUNT_PROVIDER_NAME,
TRX_ACCOUNT_PROVIDER_NAME,
BTC_ACCOUNT_PROVIDER_NAME,
MultichainAccountServiceMessenger,
} from '@metamask/multichain-account-service';
import { Messenger } from '@metamask/messenger';

import { InitializationConfiguration } from '../types';

const snapAccountProviderConfig = {
// READ THIS CAREFULLY:
// We are using 1 to prevent any concurrent `keyring_createAccount` requests. This ensures
// we prevent any desync between Snap's accounts and Metamask's accounts.
maxConcurrency: 1,
// Re-use the default config for the rest:
discovery: {
timeoutMs: 2000,
maxAttempts: 3,
backOffMs: 1000,
},
createAccounts: {
timeoutMs: 3000,
batched: false,
},
resyncAccounts: {
autoRemoveExtraSnapAccounts: false,
},
};

export const multichainAccountService: InitializationConfiguration<
MultichainAccountService,
MultichainAccountServiceMessenger
> = {
name: 'MultichainAccountService',
init: ({ messenger, options }) => {
const instance = new MultichainAccountService({
messenger,
providerConfigs: {
[SOL_ACCOUNT_PROVIDER_NAME]: {
...snapAccountProviderConfig,
createAccounts: {
...snapAccountProviderConfig.createAccounts,
batched: true,
},
},
[BTC_ACCOUNT_PROVIDER_NAME]: snapAccountProviderConfig,
[TRX_ACCOUNT_PROVIDER_NAME]: snapAccountProviderConfig,
},
ensureOnboardingComplete: options.ensureOnboardingComplete,
});

// TODO: Basic Functionality triggers

return {
instance,
};
},
messenger: (parent) => {
const serviceMessenger: MultichainAccountServiceMessenger = new Messenger({
namespace: 'MultichainAccountService',
parent,
});

parent.delegate({
messenger: serviceMessenger,
events: [
'KeyringController:stateChange',
'SnapController:stateChange',
'AccountsController:accountAdded',
'AccountsController:accountRemoved',
],
actions: [
'AccountsController:listMultichainAccounts',
'AccountsController:getAccountByAddress',
'AccountsController:getAccount',
'AccountsController:getAccounts',
'SnapController:getState',
'SnapController:handleRequest',
'KeyringController:getState',
'KeyringController:withKeyring',
'KeyringController:addNewKeyring',
'KeyringController:getKeyringsByType',
'KeyringController:createNewVaultAndKeychain',
'KeyringController:createNewVaultAndRestore',
'NetworkController:getNetworkClientById',
'NetworkController:findNetworkClientIdByChainId',
],
});

return serviceMessenger;
}
};
Loading
Loading