Skip to content
This repository was archived by the owner on Oct 7, 2024. It is now read-only.

Commit 16d62dd

Browse files
authored
feat!: add methods to support ERC-4337 accounts (#315)
* feat: add UserOperation methods * build: ignore errors from Snap packages * fix: fix return type of `prepareUserOperation` * chore: enable lcov coverage * test: add missing unit tests * chore: update `yarn.lock` * chore: apply linter * chore: update @metamask/keyring-api version to 2.0.0
1 parent ad96120 commit 16d62dd

File tree

10 files changed

+1895
-231
lines changed

10 files changed

+1895
-231
lines changed

jest.config.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,15 @@ module.exports = {
3636
coverageProvider: 'babel',
3737

3838
// A list of reporter names that Jest uses when writing coverage reports
39-
coverageReporters: ['html', 'json-summary', 'text'],
39+
coverageReporters: ['html', 'json-summary', 'text', 'lcov'],
4040

4141
// An object that configures minimum threshold enforcement for coverage results
4242
coverageThreshold: {
4343
global: {
44-
branches: 79.8,
45-
functions: 93.22,
46-
lines: 91.5,
47-
statements: 91.69,
44+
branches: 80.35,
45+
functions: 93.65,
46+
lines: 91.9,
47+
statements: 92.07,
4848
},
4949
},
5050
preset: 'ts-jest',

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"@metamask/eth-hd-keyring": "^7.0.1",
4949
"@metamask/eth-sig-util": "^7.0.0",
5050
"@metamask/eth-simple-keyring": "^6.0.1",
51+
"@metamask/keyring-api": "^2.0.0",
5152
"@metamask/obs-store": "^9.0.0",
5253
"@metamask/utils": "^8.2.0"
5354
},
@@ -72,7 +73,7 @@
7273
"depcheck": "^1.4.7",
7374
"eslint": "^8.48.0",
7475
"eslint-config-prettier": "^8.7.0",
75-
"eslint-plugin-import": "^2.27.5",
76+
"eslint-plugin-import": "~2.26.0",
7677
"eslint-plugin-jest": "^27.2.1",
7778
"eslint-plugin-jsdoc": "^41",
7879
"eslint-plugin-n": "^15.7.0",
@@ -100,8 +101,8 @@
100101
"lavamoat": {
101102
"allowScripts": {
102103
"@lavamoat/preinstall-always-fail": false,
103-
"@metamask/eth-hd-keyring>eth-simple-keyring>eth-sig-util>ethereumjs-util>keccak": false,
104-
"@metamask/eth-hd-keyring>eth-simple-keyring>eth-sig-util>ethereumjs-util>secp256k1": false
104+
"@metamask/keyring-api>@metamask/snaps-utils>@metamask/permission-controller>@metamask/controller-utils>ethereumjs-util>ethereum-cryptography>keccak": false,
105+
"@metamask/keyring-api>@metamask/snaps-utils>@metamask/permission-controller>@metamask/controller-utils>ethereumjs-util>ethereum-cryptography>secp256k1": false
105106
}
106107
}
107108
}

src/KeyringController.test.ts

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1303,6 +1303,207 @@ describe('KeyringController', () => {
13031303
);
13041304
});
13051305

1306+
it('prepares a base UserOperation', async () => {
1307+
const keyringController = await initializeKeyringController({
1308+
password: PASSWORD,
1309+
});
1310+
1311+
await keyringController.addNewKeyring('Keyring Mock With Init');
1312+
const sender = '0x998B3FBB8159aF51a827DBf43A8054A5A3A28c95';
1313+
const baseTxs = [
1314+
{
1315+
to: '0x8cBC0EA145491fe83104abA9ef916f8632367227',
1316+
value: '0x0',
1317+
data: '0x',
1318+
},
1319+
];
1320+
1321+
const baseUserOp = {
1322+
callData: '0x7064',
1323+
initCode: '0x22ff',
1324+
nonce: '0x1',
1325+
gasLimits: {
1326+
callGasLimit: '0x58a83',
1327+
verificationGasLimit: '0xe8c4',
1328+
preVerificationGas: '0xc57c',
1329+
},
1330+
dummySignature: '0x0000',
1331+
dummyPaymasterAndData: '0x',
1332+
bundlerUrl: 'https://bundler.example.com/rpc',
1333+
};
1334+
1335+
jest
1336+
.spyOn(KeyringMockWithInit.prototype, 'getAccounts')
1337+
.mockResolvedValueOnce([sender]);
1338+
1339+
jest
1340+
.spyOn(KeyringMockWithInit.prototype, 'prepareUserOperation')
1341+
.mockResolvedValueOnce(baseUserOp);
1342+
1343+
const result = await keyringController.prepareUserOperation(
1344+
sender,
1345+
baseTxs,
1346+
);
1347+
1348+
expect(result).toStrictEqual(baseUserOp);
1349+
});
1350+
1351+
it('patches an UserOperation', async () => {
1352+
const keyringController = await initializeKeyringController({
1353+
password: PASSWORD,
1354+
});
1355+
1356+
await keyringController.addNewKeyring('Keyring Mock With Init');
1357+
const sender = '0x998B3FBB8159aF51a827DBf43A8054A5A3A28c95';
1358+
const userOp = {
1359+
sender: '0x4584d2B4905087A100420AFfCe1b2d73fC69B8E4',
1360+
nonce: '0x1',
1361+
initCode: '0x',
1362+
callData: '0x7064',
1363+
callGasLimit: '0x58a83',
1364+
verificationGasLimit: '0xe8c4',
1365+
preVerificationGas: '0xc57c',
1366+
maxFeePerGas: '0x87f0878c0',
1367+
maxPriorityFeePerGas: '0x1dcd6500',
1368+
paymasterAndData: '0x',
1369+
signature: '0x',
1370+
};
1371+
1372+
const patch = {
1373+
paymasterAndData: '0x1234',
1374+
};
1375+
1376+
jest
1377+
.spyOn(KeyringMockWithInit.prototype, 'getAccounts')
1378+
.mockResolvedValueOnce([sender]);
1379+
1380+
jest
1381+
.spyOn(KeyringMockWithInit.prototype, 'patchUserOperation')
1382+
.mockResolvedValueOnce(patch);
1383+
1384+
const result = await keyringController.patchUserOperation(sender, userOp);
1385+
expect(result).toStrictEqual(patch);
1386+
});
1387+
1388+
it('signs an UserOperation', async () => {
1389+
const keyringController = await initializeKeyringController({
1390+
password: PASSWORD,
1391+
});
1392+
1393+
await keyringController.addNewKeyring('Keyring Mock With Init');
1394+
const sender = '0x998B3FBB8159aF51a827DBf43A8054A5A3A28c95';
1395+
const userOp = {
1396+
sender: '0x4584d2B4905087A100420AFfCe1b2d73fC69B8E4',
1397+
nonce: '0x1',
1398+
initCode: '0x',
1399+
callData: '0x7064',
1400+
callGasLimit: '0x58a83',
1401+
verificationGasLimit: '0xe8c4',
1402+
preVerificationGas: '0xc57c',
1403+
maxFeePerGas: '0x87f0878c0',
1404+
maxPriorityFeePerGas: '0x1dcd6500',
1405+
paymasterAndData: '0x',
1406+
signature: '0x',
1407+
};
1408+
1409+
const signature = '0x1234';
1410+
1411+
jest
1412+
.spyOn(KeyringMockWithInit.prototype, 'getAccounts')
1413+
.mockResolvedValueOnce([sender]);
1414+
1415+
jest
1416+
.spyOn(KeyringMockWithInit.prototype, 'signUserOperation')
1417+
.mockResolvedValueOnce(signature);
1418+
1419+
const result = await keyringController.signUserOperation(sender, userOp);
1420+
expect(result).toStrictEqual(signature);
1421+
});
1422+
1423+
it("throws when the keyring doesn't implement prepareUserOperation", async () => {
1424+
const keyringController = await initializeKeyringController({
1425+
password: PASSWORD,
1426+
seedPhrase: walletOneSeedWords,
1427+
});
1428+
1429+
const txs = [
1430+
{
1431+
to: '0x8cBC0EA145491fe83104abA9ef916f8632367227',
1432+
value: '0x0',
1433+
data: '0x',
1434+
},
1435+
];
1436+
1437+
const result = keyringController.prepareUserOperation(
1438+
walletOneAddresses[0] as string,
1439+
txs,
1440+
);
1441+
1442+
await expect(result).rejects.toThrow(
1443+
'KeyringController - The keyring for the current address does not support the method prepareUserOperation.',
1444+
);
1445+
});
1446+
1447+
it("throws when the keyring doesn't implement patchUserOperation", async () => {
1448+
const keyringController = await initializeKeyringController({
1449+
password: PASSWORD,
1450+
seedPhrase: walletOneSeedWords,
1451+
});
1452+
1453+
const userOp = {
1454+
sender: '0x4584d2B4905087A100420AFfCe1b2d73fC69B8E4',
1455+
nonce: '0x1',
1456+
initCode: '0x',
1457+
callData: '0x7064',
1458+
callGasLimit: '0x58a83',
1459+
verificationGasLimit: '0xe8c4',
1460+
preVerificationGas: '0xc57c',
1461+
maxFeePerGas: '0x87f0878c0',
1462+
maxPriorityFeePerGas: '0x1dcd6500',
1463+
paymasterAndData: '0x',
1464+
signature: '0x',
1465+
};
1466+
1467+
const result = keyringController.patchUserOperation(
1468+
walletOneAddresses[0] as string,
1469+
userOp,
1470+
);
1471+
1472+
await expect(result).rejects.toThrow(
1473+
'KeyringController - The keyring for the current address does not support the method patchUserOperation.',
1474+
);
1475+
});
1476+
1477+
it("throws when the keyring doesn't implement signUserOperation", async () => {
1478+
const keyringController = await initializeKeyringController({
1479+
password: PASSWORD,
1480+
seedPhrase: walletOneSeedWords,
1481+
});
1482+
1483+
const userOp = {
1484+
sender: '0x4584d2B4905087A100420AFfCe1b2d73fC69B8E4',
1485+
nonce: '0x1',
1486+
initCode: '0x',
1487+
callData: '0x7064',
1488+
callGasLimit: '0x58a83',
1489+
verificationGasLimit: '0xe8c4',
1490+
preVerificationGas: '0xc57c',
1491+
maxFeePerGas: '0x87f0878c0',
1492+
maxPriorityFeePerGas: '0x1dcd6500',
1493+
paymasterAndData: '0x',
1494+
signature: '0x',
1495+
};
1496+
1497+
const result = keyringController.signUserOperation(
1498+
walletOneAddresses[0] as string,
1499+
userOp,
1500+
);
1501+
1502+
await expect(result).rejects.toThrow(
1503+
'KeyringController - The keyring for the current address does not support the method signUserOperation.',
1504+
);
1505+
});
1506+
13061507
it('signPersonalMessage', async () => {
13071508
const keyringController = await initializeKeyringController({
13081509
password: PASSWORD,

0 commit comments

Comments
 (0)