Skip to content

Commit 80c2910

Browse files
committed
feat: add optional details field to Transaction type
Add optional details field containing transaction origin and security alert response. The details field includes: - origin: transaction request source (e.g., 'metamask' or dapp URL) - securityAlertResponse: Security Alert API response enum (benign, warning, malicious) The implementation uses exactOptional for full backward compatibility.
1 parent 3fac128 commit 80c2910

4 files changed

Lines changed: 260 additions & 1 deletion

File tree

packages/keyring-api/CHANGELOG.md

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

1010
### Added
1111

12+
- Add optional `details` field to `Transaction` type
13+
- Add `SecurityAlertResponse` enum with values: `benign`, `warning`, `malicious`
14+
- Add optional `origin` field (string) to track transaction request source
15+
- Add optional `securityAlertResponse` field for Security Alert API responses
1216
- Add support for custom capabilities and entropy types in `KeyringV2` ([#415](https://github.com/MetaMask/accounts/pull/415))
1317
- Add `custom` capability to `KeyringCapabilities` for keyrings with non-standard `createAccounts` method.
1418
- Add `KeyringAccountEntropyTypeOption.Custom` for custom/opaque entropy sources.

packages/keyring-api/src/api/transaction.test-d.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,3 +240,102 @@ expectNotAssignable<Transaction>({
240240
},
241241
],
242242
});
243+
244+
// Transaction with full details (valid)
245+
expectAssignable<Transaction>({
246+
id: 'f5d8ee39a430901c91a5917b9f2dc19d6d1a0e9cea205b009ca73dd04470b9a6',
247+
timestamp: null,
248+
chain: 'eip155:1',
249+
status: 'submitted',
250+
type: 'send',
251+
account: '5cd17616-ea18-4d72-974f-6dbaa3c56d15',
252+
from: [],
253+
to: [],
254+
fees: [],
255+
events: [],
256+
details: {
257+
origin: 'https://dapp.test',
258+
securityAlertResponse: 'benign',
259+
},
260+
});
261+
262+
// Transaction with empty details object (valid)
263+
expectAssignable<Transaction>({
264+
id: 'f5d8ee39a430901c91a5917b9f2dc19d6d1a0e9cea205b009ca73dd04470b9a6',
265+
timestamp: null,
266+
chain: 'eip155:1',
267+
status: 'submitted',
268+
type: 'send',
269+
account: '5cd17616-ea18-4d72-974f-6dbaa3c56d15',
270+
from: [],
271+
to: [],
272+
fees: [],
273+
events: [],
274+
details: {},
275+
});
276+
277+
// Transaction with only origin in details (valid)
278+
expectAssignable<Transaction>({
279+
id: 'f5d8ee39a430901c91a5917b9f2dc19d6d1a0e9cea205b009ca73dd04470b9a6',
280+
timestamp: null,
281+
chain: 'eip155:1',
282+
status: 'submitted',
283+
type: 'send',
284+
account: '5cd17616-ea18-4d72-974f-6dbaa3c56d15',
285+
from: [],
286+
to: [],
287+
fees: [],
288+
events: [],
289+
details: {
290+
origin: 'metamask',
291+
},
292+
});
293+
294+
// Transaction with only securityAlertResponse in details (valid)
295+
expectAssignable<Transaction>({
296+
id: 'f5d8ee39a430901c91a5917b9f2dc19d6d1a0e9cea205b009ca73dd04470b9a6',
297+
timestamp: null,
298+
chain: 'eip155:1',
299+
status: 'submitted',
300+
type: 'send',
301+
account: '5cd17616-ea18-4d72-974f-6dbaa3c56d15',
302+
from: [],
303+
to: [],
304+
fees: [],
305+
events: [],
306+
details: {
307+
securityAlertResponse: 'warning',
308+
},
309+
});
310+
311+
// Transaction with undefined details (invalid - exactOptional doesn't allow undefined)
312+
expectNotAssignable<Transaction>({
313+
id: 'f5d8ee39a430901c91a5917b9f2dc19d6d1a0e9cea205b009ca73dd04470b9a6',
314+
timestamp: null,
315+
chain: 'eip155:1',
316+
status: 'submitted',
317+
type: 'send',
318+
account: '5cd17616-ea18-4d72-974f-6dbaa3c56d15',
319+
from: [],
320+
to: [],
321+
fees: [],
322+
events: [],
323+
details: undefined,
324+
});
325+
326+
// Transaction with invalid securityAlertResponse (invalid - must be 'benign', 'warning', or 'malicious')
327+
expectNotAssignable<Transaction>({
328+
id: 'f5d8ee39a430901c91a5917b9f2dc19d6d1a0e9cea205b009ca73dd04470b9a6',
329+
timestamp: null,
330+
chain: 'eip155:1',
331+
status: 'submitted',
332+
type: 'send',
333+
account: '5cd17616-ea18-4d72-974f-6dbaa3c56d15',
334+
from: [],
335+
to: [],
336+
fees: [],
337+
events: [],
338+
details: {
339+
securityAlertResponse: 'invalid',
340+
},
341+
});
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { is } from '@metamask/superstruct';
2+
3+
import { TransactionStruct } from './transaction';
4+
5+
describe('TransactionStruct', () => {
6+
const baseTransaction = {
7+
id: 'f5d8ee39a430901c91a5917b9f2dc19d6d1a0e9cea205b009ca73dd04470b9a6',
8+
chain: 'eip155:1',
9+
account: '5cd17616-ea18-4d72-974f-6dbaa3c56d15',
10+
status: 'confirmed',
11+
timestamp: 1716367781,
12+
type: 'send',
13+
from: [],
14+
to: [],
15+
fees: [],
16+
events: [],
17+
};
18+
19+
describe('details field', () => {
20+
it.each([
21+
// Without details field
22+
{ transaction: baseTransaction, expected: true },
23+
// With empty details
24+
{ transaction: { ...baseTransaction, details: {} }, expected: true },
25+
// With only origin
26+
{
27+
transaction: {
28+
...baseTransaction,
29+
details: { origin: 'https://dapp.test' },
30+
},
31+
expected: true,
32+
},
33+
// With only securityAlertResponse
34+
{
35+
transaction: {
36+
...baseTransaction,
37+
details: { securityAlertResponse: 'benign' },
38+
},
39+
expected: true,
40+
},
41+
// With both fields
42+
{
43+
transaction: {
44+
...baseTransaction,
45+
details: { origin: 'metamask', securityAlertResponse: 'warning' },
46+
},
47+
expected: true,
48+
},
49+
// All valid securityAlertResponse values
50+
{
51+
transaction: {
52+
...baseTransaction,
53+
details: { securityAlertResponse: 'benign' },
54+
},
55+
expected: true,
56+
},
57+
{
58+
transaction: {
59+
...baseTransaction,
60+
details: { securityAlertResponse: 'warning' },
61+
},
62+
expected: true,
63+
},
64+
{
65+
transaction: {
66+
...baseTransaction,
67+
details: { securityAlertResponse: 'malicious' },
68+
},
69+
expected: true,
70+
},
71+
// Invalid securityAlertResponse
72+
{
73+
transaction: {
74+
...baseTransaction,
75+
details: { securityAlertResponse: 'invalid' },
76+
},
77+
expected: false,
78+
},
79+
])(
80+
'returns $expected for is($transaction, TransactionStruct)',
81+
({ transaction, expected }) => {
82+
expect(is(transaction, TransactionStruct)).toBe(expected);
83+
},
84+
);
85+
});
86+
});

packages/keyring-api/src/api/transaction.ts

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { InferEquals } from '@metamask/keyring-utils';
2-
import { object, UuidStruct } from '@metamask/keyring-utils';
2+
import { exactOptional, object, UuidStruct } from '@metamask/keyring-utils';
33
import type { Infer } from '@metamask/superstruct';
44
import { array, enums, nullable, number, string } from '@metamask/superstruct';
55

@@ -171,6 +171,67 @@ export enum TransactionType {
171171
Unknown = 'unknown',
172172
}
173173

174+
/**
175+
* Security alert response values from the Security Alert API.
176+
*/
177+
export enum SecurityAlertResponse {
178+
/**
179+
* The transaction is considered safe with no detected security issues.
180+
*/
181+
Benign = 'benign',
182+
183+
/**
184+
* The transaction has potential security concerns that warrant user attention.
185+
*/
186+
Warning = 'warning',
187+
188+
/**
189+
* The transaction has been identified as malicious and should be avoided.
190+
*/
191+
Malicious = 'malicious',
192+
}
193+
194+
/**
195+
* This struct represents additional transaction details.
196+
*
197+
* @example
198+
* ```ts
199+
* {
200+
* origin: 'https://dapp.example.com',
201+
* securityAlertResponse: 'benign',
202+
* }
203+
* ```
204+
*
205+
* @example
206+
* ```ts
207+
* {
208+
* origin: 'metamask',
209+
* securityAlertResponse: 'warning',
210+
* }
211+
* ```
212+
*/
213+
const TransactionDetailsStruct = object({
214+
/**
215+
* Origin of the original transaction request.
216+
*
217+
* This can be either 'metamask' for internally initiated transactions, or a URL
218+
* (e.g., 'https://dapp.example.com') for dapp-initiated transactions.
219+
*/
220+
origin: exactOptional(string()),
221+
222+
/**
223+
* Response from the Security Alert API indicating the security assessment of the
224+
* transaction.
225+
*/
226+
securityAlertResponse: exactOptional(
227+
enums([
228+
`${SecurityAlertResponse.Benign}`,
229+
`${SecurityAlertResponse.Warning}`,
230+
`${SecurityAlertResponse.Malicious}`,
231+
]),
232+
),
233+
});
234+
174235
/**
175236
* This struct represents a transaction event.
176237
*/
@@ -318,6 +379,15 @@ export const TransactionStruct = object({
318379
* all transactions.
319380
*/
320381
events: array(TransactionEventStruct),
382+
383+
/**
384+
* Additional transaction details {@see TransactionDetailsStruct}.
385+
*
386+
* Contains contextual information about the transaction such as its origin and
387+
* security assessment. This field is optional and may not be present for all
388+
* transactions.
389+
*/
390+
details: exactOptional(TransactionDetailsStruct),
321391
});
322392

323393
/**

0 commit comments

Comments
 (0)