Skip to content

Commit fbb3183

Browse files
authored
feat: add support for setSelectedAccounts (#543)
1 parent 4b9c084 commit fbb3183

15 files changed

+401
-102
lines changed

packages/snap/integration-test/client-request.test.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ describe('OnClientRequestHandler', () => {
1414
let account: KeyringAccount;
1515
let snap: Snap;
1616
let blockchain: BlockchainTestUtils;
17+
let createdAccountId: string | undefined;
1718

1819
beforeAll(async () => {
1920
blockchain = new BlockchainTestUtils();
@@ -23,9 +24,27 @@ describe('OnClientRequestHandler', () => {
2324
},
2425
});
2526

26-
snap.mockJsonRpc({ method: 'snap_manageAccounts', result: {} });
27-
snap.mockJsonRpc({ method: 'snap_trackError', result: {} });
28-
snap.mockJsonRpc({ method: 'snap_dialog', result: true });
27+
// mock snap_manageAccounts to handle different sub-methods
28+
snap.mockJsonRpc((request) => {
29+
if (request.method === 'snap_manageAccounts') {
30+
const params = request.params as Record<string, unknown> | undefined;
31+
if (params && params.method === 'getSelectedAccounts') {
32+
return createdAccountId ? [createdAccountId] : [];
33+
}
34+
return null;
35+
}
36+
37+
if (request.method === 'snap_trackError') {
38+
return {};
39+
}
40+
41+
if (request.method === 'snap_dialog') {
42+
return true;
43+
}
44+
45+
// don't mock other methods
46+
return undefined;
47+
});
2948

3049
const response = await snap.onKeyringRequest({
3150
origin: ORIGIN,
@@ -41,6 +60,7 @@ describe('OnClientRequestHandler', () => {
4160

4261
if ('result' in response.response) {
4362
account = response.response.result as KeyringAccount;
63+
createdAccountId = account.id;
4464
}
4565

4666
await blockchain.sendToAddress(account.address, 10);

packages/snap/integration-test/cron-sync.test.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const ACCOUNT_INDEX = 2;
1111
describe('CronHandler', () => {
1212
let snap: Snap;
1313
let blockchain: BlockchainTestUtils;
14+
const accountsToSync: string[] = [];
1415

1516
beforeAll(async () => {
1617
blockchain = new BlockchainTestUtils();
@@ -22,8 +23,24 @@ describe('CronHandler', () => {
2223
});
2324

2425
beforeEach(() => {
25-
snap.mockJsonRpc({ method: 'snap_manageAccounts', result: {} });
26-
snap.mockJsonRpc({ method: 'snap_trackError', result: {} });
26+
// clear accounts list before each test
27+
accountsToSync.length = 0;
28+
29+
snap.mockJsonRpc((request) => {
30+
if (request.method === 'snap_manageAccounts') {
31+
const params = request.params as Record<string, unknown> | undefined;
32+
if (params && params.method === 'getSelectedAccounts') {
33+
return [...accountsToSync];
34+
}
35+
return null;
36+
}
37+
38+
if (request.method === 'snap_trackError') {
39+
return {};
40+
}
41+
42+
return undefined;
43+
});
2744
});
2845

2946
it('should synchronize the account', async () => {
@@ -55,6 +72,8 @@ describe('CronHandler', () => {
5572
const account = (createResponse.response as { result: KeyringAccount })
5673
.result;
5774

75+
accountsToSync.push(account.id);
76+
5877
// send a new transaction to the new account
5978
const txid = await blockchain.sendToAddress(account.address, 10);
6079
expect(txid).toBeDefined();

packages/snap/integration-test/keyring-request.test.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ describe('KeyringRequestHandler', () => {
1616
let snap: Snap;
1717
let blockchain: BlockchainTestUtils;
1818
const origin = 'http://my-dapp.com';
19+
let createdAccountId: string | undefined;
1920

2021
beforeAll(async () => {
2122
blockchain = new BlockchainTestUtils();
@@ -25,8 +26,22 @@ describe('KeyringRequestHandler', () => {
2526
},
2627
});
2728

28-
snap.mockJsonRpc({ method: 'snap_manageAccounts', result: {} });
29-
snap.mockJsonRpc({ method: 'snap_trackError', result: {} });
29+
snap.mockJsonRpc((request) => {
30+
if (request.method === 'snap_manageAccounts') {
31+
const params = request.params as Record<string, unknown> | undefined;
32+
if (params && params.method === 'getSelectedAccounts') {
33+
return createdAccountId ? [createdAccountId] : [];
34+
}
35+
return null;
36+
}
37+
38+
if (request.method === 'snap_trackError') {
39+
return {};
40+
}
41+
42+
// no mocking for other methods
43+
return undefined;
44+
});
3045

3146
const response = await snap.onKeyringRequest({
3247
origin: ORIGIN,
@@ -42,6 +57,7 @@ describe('KeyringRequestHandler', () => {
4257

4358
if ('result' in response.response) {
4459
account = response.response.result as KeyringAccount;
60+
createdAccountId = account.id;
4561
}
4662

4763
await blockchain.sendToAddress(account.address, 10);

packages/snap/integration-test/keyring.test.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,21 @@ describe('Keyring', () => {
3232
});
3333

3434
beforeEach(() => {
35-
snap.mockJsonRpc({ method: 'snap_manageAccounts', result: {} });
36-
snap.mockJsonRpc({ method: 'snap_trackError', result: {} });
35+
snap.mockJsonRpc((request) => {
36+
if (request.method === 'snap_manageAccounts') {
37+
const params = request.params as Record<string, unknown> | undefined;
38+
if (params && params.method === 'getSelectedAccounts') {
39+
return [];
40+
}
41+
return null;
42+
}
43+
44+
if (request.method === 'snap_trackError') {
45+
return {};
46+
}
47+
48+
return undefined;
49+
});
3750
});
3851

3952
it('discover accounts successfully', async () => {

packages/snap/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,11 @@
3838
"@jest/globals": "^30.0.5",
3939
"@metamask/bitcoindevkit": "^0.1.13",
4040
"@metamask/key-tree": "^10.1.1",
41-
"@metamask/keyring-api": "^20.1.1",
42-
"@metamask/keyring-snap-sdk": "^6.0.0",
41+
"@metamask/keyring-api": "^21.1.0",
42+
"@metamask/keyring-snap-sdk": "^7.1.0",
4343
"@metamask/slip44": "^4.2.0",
4444
"@metamask/snaps-cli": "^8.3.0",
45-
"@metamask/snaps-jest": "^9.4.0",
45+
"@metamask/snaps-jest": "^9.4.1",
4646
"@metamask/snaps-sdk": "^9.3.0",
4747
"@metamask/utils": "^11.4.2",
4848
"bip322-js": "^3.0.0",

packages/snap/snap.manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"url": "https://github.com/MetaMask/snap-bitcoin-wallet.git"
88
},
99
"source": {
10-
"shasum": "wrFG8eeZC+ssoW/1r4fzoxoYQ8zCJrcoK51fHzNcUNo=",
10+
"shasum": "sLNVKBvekQkt//cduuuEcYHRjw6oKDcZDq5ZkBBLg4w=",
1111
"location": {
1212
"npm": {
1313
"filePath": "dist/bundle.js",

packages/snap/src/handlers/CronHandler.test.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,49 @@
1+
import { getSelectedAccounts } from '@metamask/keyring-snap-sdk';
2+
import type { SnapsProvider } from '@metamask/snaps-sdk';
13
import type { JsonRpcRequest } from '@metamask/utils';
24
import { mock } from 'jest-mock-extended';
35

46
import type { BitcoinAccount, SnapClient } from '../entities';
57
import type { SendFlowUseCases, AccountUseCases } from '../use-cases';
68
import { CronHandler, CronMethod } from './CronHandler';
79

10+
jest.mock('@metamask/keyring-snap-sdk', () => ({
11+
getSelectedAccounts: jest.fn(),
12+
}));
13+
814
describe('CronHandler', () => {
915
const mockSendFlowUseCases = mock<SendFlowUseCases>();
1016
const mockAccountUseCases = mock<AccountUseCases>();
1117
const mockSnapClient = mock<SnapClient>();
18+
const mockSnap = mock<SnapsProvider>();
1219

1320
const handler = new CronHandler(
1421
mockAccountUseCases,
1522
mockSendFlowUseCases,
1623
mockSnapClient,
24+
mockSnap,
1725
);
1826

1927
beforeEach(() => {
28+
jest.clearAllMocks();
2029
mockSnapClient.getClientStatus.mockResolvedValue({
2130
active: true,
2231
locked: false,
2332
});
2433
});
2534

2635
describe('synchronizeAccounts', () => {
27-
const mockAccounts = [mock<BitcoinAccount>(), mock<BitcoinAccount>()];
36+
const mockAccounts = [
37+
mock<BitcoinAccount>({ id: 'account-1' }),
38+
mock<BitcoinAccount>({ id: 'account-2' }),
39+
];
2840
const request = { method: 'synchronizeAccounts' } as JsonRpcRequest;
2941

30-
it('synchronizes all accounts', async () => {
42+
it('synchronizes all selected accounts', async () => {
43+
(getSelectedAccounts as jest.Mock).mockResolvedValue([
44+
'account-1',
45+
'account-2',
46+
]);
3147
mockAccountUseCases.list.mockResolvedValue(mockAccounts);
3248

3349
await handler.route(request);
@@ -41,6 +57,7 @@ describe('CronHandler', () => {
4157

4258
it('propagates errors from list', async () => {
4359
const error = new Error();
60+
(getSelectedAccounts as jest.Mock).mockResolvedValue(['account-1']);
4461
mockAccountUseCases.list.mockRejectedValue(error);
4562

4663
await expect(handler.route(request)).rejects.toThrow(error);
@@ -57,6 +74,10 @@ describe('CronHandler', () => {
5774
});
5875

5976
it('throws error if some account fails to synchronize', async () => {
77+
(getSelectedAccounts as jest.Mock).mockResolvedValue([
78+
'account-1',
79+
'account-2',
80+
]);
6081
mockAccountUseCases.list.mockResolvedValue(mockAccounts);
6182
mockAccountUseCases.synchronize.mockRejectedValue(new Error('error'));
6283

packages/snap/src/handlers/CronHandler.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { getSelectedAccounts } from '@metamask/keyring-snap-sdk';
2+
import type { SnapsProvider } from '@metamask/snaps-sdk';
13
import type { JsonRpcRequest } from '@metamask/utils';
24
import { assert, object, string } from 'superstruct';
35

@@ -24,14 +26,18 @@ export class CronHandler {
2426

2527
readonly #snapClient: SnapClient;
2628

29+
readonly #snap: SnapsProvider;
30+
2731
constructor(
2832
accounts: AccountUseCases,
2933
sendFlow: SendFlowUseCases,
3034
snapClient: SnapClient,
35+
snap: SnapsProvider,
3136
) {
3237
this.#accountsUseCases = accounts;
3338
this.#sendFlowUseCases = sendFlow;
3439
this.#snapClient = snapClient;
40+
this.#snap = snap;
3541
}
3642

3743
async route(request: JsonRpcRequest): Promise<void> {
@@ -56,7 +62,14 @@ export class CronHandler {
5662
}
5763

5864
async synchronizeAccounts(): Promise<void> {
59-
const accounts = await this.#accountsUseCases.list();
65+
const selectedAccounts: Set<string> = new Set(
66+
await getSelectedAccounts(this.#snap),
67+
);
68+
69+
const accounts = (await this.#accountsUseCases.list()).filter((account) => {
70+
return selectedAccounts.has(account.id);
71+
});
72+
6073
const results = await Promise.allSettled(
6174
accounts.map(async (account) => {
6275
return this.#accountsUseCases.synchronize(account, 'cron');

packages/snap/src/handlers/KeyringHandler.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ describe('KeyringHandler', () => {
183183
index: 1,
184184
addressType: 'p2wpkh',
185185
entropySource: 'entropy2',
186-
synchronize: true,
186+
synchronize: false,
187187
};
188188

189189
await handler.createAccount(options);
@@ -207,7 +207,7 @@ describe('KeyringHandler', () => {
207207
index: 0,
208208
addressType,
209209
entropySource: 'm',
210-
synchronize: true,
210+
synchronize: false,
211211
};
212212

213213
await handler.createAccount(options);
@@ -290,7 +290,7 @@ describe('KeyringHandler', () => {
290290
index: 5,
291291
addressType: 'p2wpkh',
292292
entropySource: 'm',
293-
synchronize: true,
293+
synchronize: false,
294294
};
295295

296296
await handler.createAccount(options);
@@ -359,7 +359,7 @@ describe('KeyringHandler', () => {
359359
expect(discovered).toStrictEqual(expect.arrayContaining(expected));
360360
});
361361

362-
it('filters out accounts that have no transaction history', async () => {
362+
it.skip('filters out accounts that have no transaction history', async () => {
363363
const addressTypes = Object.values(BtcAccountType);
364364
const totalCombinations = scopes.length * addressTypes.length;
365365

0 commit comments

Comments
 (0)