Skip to content

Commit a9bf413

Browse files
committed
create shield-controller package
1 parent caa125c commit a9bf413

24 files changed

+942
-1
lines changed

.github/CODEOWNERS

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@
7474
/packages/announcement-controller @MetaMask/wallet-ux
7575

7676
## Web3Auth Team
77-
/packages/seedless-onboarding-controller @MetaMask/web3auth
77+
/packages/seedless-onboarding-controller @MetaMask/web3auth
78+
/packages/shield-controller @MetaMask/web3auth
7879

7980
## Joint team ownership
8081
/packages/eth-json-rpc-provider @MetaMask/wallet-api-platform-engineers @MetaMask/core-platform
@@ -158,5 +159,7 @@
158159
/packages/foundryup/CHANGELOG.md @MetaMask/mobile-platform @MetaMask/extension-platform @MetaMask/core-platform
159160
/packages/seedless-onboarding-controller/package.json @MetaMask/web3auth @MetaMask/core-platform
160161
/packages/seedless-onboarding-controller/CHANGELOG.md @MetaMask/web3auth @MetaMask/core-platform
162+
/packages/shield-controller/package.json @MetaMask/web3auth @MetaMask/core-platform
163+
/packages/shield-controller/CHANGELOG.md @MetaMask/web3auth @MetaMask/core-platform
161164
/packages/network-enablement-controller/package.json @MetaMask/metamask-assets @MetaMask/core-platform
162165
/packages/network-enablement-controller/CHANGELOG.md @MetaMask/metamask-assets @MetaMask/core-platform

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ Each package in this repository has its own README where you can find installati
6767
- [`@metamask/sample-controllers`](packages/sample-controllers)
6868
- [`@metamask/seedless-onboarding-controller`](packages/seedless-onboarding-controller)
6969
- [`@metamask/selected-network-controller`](packages/selected-network-controller)
70+
- [`@metamask/shield-controller`](packages/shield-controller)
7071
- [`@metamask/signature-controller`](packages/signature-controller)
7172
- [`@metamask/token-search-discovery-controller`](packages/token-search-discovery-controller)
7273
- [`@metamask/transaction-controller`](packages/transaction-controller)
@@ -127,6 +128,7 @@ linkStyle default opacity:0.5
127128
sample_controllers(["@metamask/sample-controllers"]);
128129
seedless_onboarding_controller(["@metamask/seedless-onboarding-controller"]);
129130
selected_network_controller(["@metamask/selected-network-controller"]);
131+
shield_controller(["@metamask/shield-controller"]);
130132
signature_controller(["@metamask/signature-controller"]);
131133
token_search_discovery_controller(["@metamask/token-search-discovery-controller"]);
132134
transaction_controller(["@metamask/transaction-controller"]);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [Unreleased]
9+
10+
[Unreleased]: https://github.com/MetaMask/core/

packages/shield-controller/LICENSE

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
MIT License
2+
3+
Copyright (c) 2025 MetaMask
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# `@metamask/shield-controller`
2+
3+
Controller handling shield transaction coverage logic.
4+
5+
## Installation
6+
7+
`yarn add @metamask/shield-controller`
8+
9+
or
10+
11+
`npm install @metamask/shield-controller`
12+
13+
## Contributing
14+
15+
This package is part of a monorepo. Instructions for contributing can be found in the [monorepo README](https://github.com/MetaMask/core#readme).
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* For a detailed explanation regarding each configuration property and type check, visit:
3+
* https://jestjs.io/docs/configuration
4+
*/
5+
6+
const merge = require('deepmerge');
7+
const path = require('path');
8+
9+
const baseConfig = require('../../jest.config.packages');
10+
11+
const displayName = path.basename(__dirname);
12+
13+
module.exports = merge(baseConfig, {
14+
// The display name when running multiple projects
15+
displayName,
16+
17+
// An object that configures minimum threshold enforcement for coverage results
18+
coverageThreshold: {
19+
global: {
20+
branches: 100,
21+
functions: 100,
22+
lines: 100,
23+
statements: 100,
24+
},
25+
},
26+
});
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
{
2+
"name": "@metamask/shield-controller",
3+
"version": "0.0.0",
4+
"description": "Controller handling shield transaction coverage logic",
5+
"keywords": [
6+
"MetaMask",
7+
"Ethereum"
8+
],
9+
"homepage": "https://github.com/MetaMask/core/tree/main/packages/shield-controller#readme",
10+
"bugs": {
11+
"url": "https://github.com/MetaMask/core/issues"
12+
},
13+
"repository": {
14+
"type": "git",
15+
"url": "https://github.com/MetaMask/core.git"
16+
},
17+
"license": "MIT",
18+
"sideEffects": false,
19+
"exports": {
20+
".": {
21+
"import": {
22+
"types": "./dist/index.d.mts",
23+
"default": "./dist/index.mjs"
24+
},
25+
"require": {
26+
"types": "./dist/index.d.cts",
27+
"default": "./dist/index.cjs"
28+
}
29+
},
30+
"./package.json": "./package.json"
31+
},
32+
"main": "./dist/index.cjs",
33+
"types": "./dist/index.d.cts",
34+
"files": [
35+
"dist/"
36+
],
37+
"scripts": {
38+
"build": "ts-bridge --project tsconfig.build.json --verbose --clean --no-references",
39+
"build:docs": "typedoc",
40+
"changelog:update": "../../scripts/update-changelog.sh @metamask/shield-controller",
41+
"changelog:validate": "../../scripts/validate-changelog.sh @metamask/shield-controller",
42+
"publish:preview": "yarn npm publish --tag preview",
43+
"test": "NODE_OPTIONS=--experimental-vm-modules jest --reporters=jest-silent-reporter",
44+
"test:clean": "NODE_OPTIONS=--experimental-vm-modules jest --clearCache",
45+
"test:verbose": "NODE_OPTIONS=--experimental-vm-modules jest --verbose",
46+
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch",
47+
"since-latest-release": "../../scripts/since-latest-release.sh"
48+
},
49+
"dependencies": {
50+
"@metamask/base-controller": "^8.0.1",
51+
"@metamask/utils": "^11.4.2"
52+
},
53+
"devDependencies": {
54+
"@babel/runtime": "^7.23.9",
55+
"@lavamoat/allow-scripts": "^3.0.4",
56+
"@lavamoat/preinstall-always-fail": "^2.1.0",
57+
"@metamask/accounts-controller": "^32.0.1",
58+
"@metamask/approval-controller": "^7.1.3",
59+
"@metamask/auto-changelog": "^3.4.4",
60+
"@metamask/eth-block-tracker": "^12.0.1",
61+
"@metamask/gas-fee-controller": "^24.0.0",
62+
"@metamask/network-controller": "^24.0.1",
63+
"@metamask/remote-feature-flag-controller": "^1.7.0",
64+
"@metamask/transaction-controller": "^59.1.0",
65+
"@ts-bridge/cli": "^0.6.1",
66+
"@types/jest": "^27.4.1",
67+
"deepmerge": "^4.2.2",
68+
"jest": "^27.5.1",
69+
"ts-jest": "^27.1.4",
70+
"typedoc": "^0.24.8",
71+
"typedoc-plugin-missing-exports": "^2.0.0",
72+
"typescript": "~5.2.2",
73+
"uuid": "^8.3.2"
74+
},
75+
"peerDependencies": {
76+
"@metamask/accounts-controller": "^32.0.1",
77+
"@metamask/approval-controller": "^7.1.3",
78+
"@metamask/gas-fee-controller": "^24.0.0",
79+
"@metamask/network-controller": "^24.0.1",
80+
"@metamask/remote-feature-flag-controller": "^1.7.0",
81+
"@metamask/transaction-controller": "^59.0.0"
82+
},
83+
"engines": {
84+
"node": "^18.18 || >=20"
85+
},
86+
"publishConfig": {
87+
"access": "public",
88+
"registry": "https://registry.npmjs.org/"
89+
},
90+
"lavamoat": {
91+
"allowScripts": {
92+
"@lavamoat/allow-scripts>@lavamoat/preinstall-always-fail": false
93+
}
94+
}
95+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import type { TransactionControllerState } from '@metamask/transaction-controller';
2+
3+
import { ShieldController } from './ShieldController';
4+
import { createMockBackend } from '../tests/mocks/backend';
5+
import { createMockMessenger } from '../tests/mocks/messenger';
6+
import { generateMockTxMeta } from '../tests/utils';
7+
8+
/**
9+
*
10+
* @param options - The options for setup.
11+
* @param options.coverageHistoryLimit - The coverage history limit.
12+
* @returns Objects that have been created for testing.
13+
*/
14+
function setup({
15+
coverageHistoryLimit,
16+
}: {
17+
coverageHistoryLimit?: number;
18+
} = {}) {
19+
const backend = createMockBackend();
20+
const { messenger, baseMessenger } = createMockMessenger();
21+
22+
const controller = new ShieldController({
23+
backend,
24+
coverageHistoryLimit,
25+
messenger,
26+
});
27+
controller.start();
28+
return {
29+
controller,
30+
messenger,
31+
baseMessenger,
32+
backend,
33+
};
34+
}
35+
36+
describe('ShieldController', () => {
37+
it('should trigger checkCoverage when a new transaction is added', async () => {
38+
const { baseMessenger, backend } = setup();
39+
const txMeta = generateMockTxMeta();
40+
const coverageResultReceived = new Promise<void>((resolve) => {
41+
baseMessenger.subscribe(
42+
'ShieldController:coverageResultReceived',
43+
(_coverageResult) => resolve(),
44+
);
45+
});
46+
baseMessenger.publish(
47+
'TransactionController:stateChange',
48+
{ transactions: [txMeta] } as TransactionControllerState,
49+
undefined as never,
50+
);
51+
expect(await coverageResultReceived).toBeUndefined();
52+
expect(backend.checkCoverage).toHaveBeenCalledWith(txMeta);
53+
});
54+
55+
it('should no longer trigger checkCoverage when controller is stopped', async () => {
56+
const { controller, baseMessenger, backend } = setup();
57+
controller.stop();
58+
const txMeta = generateMockTxMeta();
59+
const coverageResultReceived = new Promise<void>((resolve, reject) => {
60+
baseMessenger.subscribe(
61+
'ShieldController:coverageResultReceived',
62+
(_coverageResult) => resolve(),
63+
);
64+
setTimeout(() => reject(new Error('Coverage result not received')), 100);
65+
});
66+
baseMessenger.publish(
67+
'TransactionController:stateChange',
68+
{ transactions: [txMeta] } as TransactionControllerState,
69+
undefined as never,
70+
);
71+
await expect(coverageResultReceived).rejects.toThrow(
72+
'Coverage result not received',
73+
);
74+
expect(backend.checkCoverage).not.toHaveBeenCalled();
75+
});
76+
77+
it('should purge coverage history when the limit is exceeded', async () => {
78+
const { controller } = setup({
79+
coverageHistoryLimit: 1,
80+
});
81+
const txMeta = generateMockTxMeta();
82+
await controller.checkCoverage(txMeta);
83+
await controller.checkCoverage(txMeta);
84+
expect(controller.state.coverageResults).toHaveProperty(txMeta.id);
85+
expect(controller.state.coverageResults[txMeta.id].results).toHaveLength(1);
86+
});
87+
88+
it('should check coverage when a transaction is simulated', async () => {
89+
const { baseMessenger, backend } = setup();
90+
const txMeta = generateMockTxMeta();
91+
const coverageResultReceived = new Promise<void>((resolve) => {
92+
baseMessenger.subscribe(
93+
'ShieldController:coverageResultReceived',
94+
(_coverageResult) => resolve(),
95+
);
96+
});
97+
98+
// Add transaction.
99+
baseMessenger.publish(
100+
'TransactionController:stateChange',
101+
{ transactions: [txMeta] } as TransactionControllerState,
102+
undefined as never,
103+
);
104+
expect(await coverageResultReceived).toBeUndefined();
105+
expect(backend.checkCoverage).toHaveBeenCalledWith(txMeta);
106+
107+
// Simulate transaction.
108+
txMeta.simulationData = {
109+
tokenBalanceChanges: [],
110+
};
111+
baseMessenger.publish(
112+
'TransactionController:stateChange',
113+
{ transactions: [txMeta] } as TransactionControllerState,
114+
undefined as never,
115+
);
116+
expect(await coverageResultReceived).toBeUndefined();
117+
expect(backend.checkCoverage).toHaveBeenCalledWith(txMeta);
118+
});
119+
});

0 commit comments

Comments
 (0)