Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
7 changes: 7 additions & 0 deletions packages/keyring-api/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Add optional `details` field to `Transaction` type ([#445](https://github.com/MetaMask/accounts/pull/445))
- Add `SecurityAlertResponse` enum with values: `benign`, `warning`, `malicious`
- Add optional `origin` field (string) to track transaction request source
- Add optional `securityAlertResponse` field for Security Alert API responses

## [21.5.0]

### Added
Expand Down
99 changes: 99 additions & 0 deletions packages/keyring-api/src/api/transaction.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,3 +240,102 @@ expectNotAssignable<Transaction>({
},
],
});

// Transaction with full details (valid)
expectAssignable<Transaction>({
id: 'f5d8ee39a430901c91a5917b9f2dc19d6d1a0e9cea205b009ca73dd04470b9a6',
timestamp: null,
chain: 'eip155:1',
status: 'submitted',
type: 'send',
account: '5cd17616-ea18-4d72-974f-6dbaa3c56d15',
from: [],
to: [],
fees: [],
events: [],
details: {
origin: 'https://dapp.test',
securityAlertResponse: 'benign',
},
});

// Transaction with empty details object (valid)
expectAssignable<Transaction>({
id: 'f5d8ee39a430901c91a5917b9f2dc19d6d1a0e9cea205b009ca73dd04470b9a6',
timestamp: null,
chain: 'eip155:1',
status: 'submitted',
type: 'send',
account: '5cd17616-ea18-4d72-974f-6dbaa3c56d15',
from: [],
to: [],
fees: [],
events: [],
details: {},
});

// Transaction with only origin in details (valid)
expectAssignable<Transaction>({
id: 'f5d8ee39a430901c91a5917b9f2dc19d6d1a0e9cea205b009ca73dd04470b9a6',
timestamp: null,
chain: 'eip155:1',
status: 'submitted',
type: 'send',
account: '5cd17616-ea18-4d72-974f-6dbaa3c56d15',
from: [],
to: [],
fees: [],
events: [],
details: {
origin: 'metamask',
},
});

// Transaction with only securityAlertResponse in details (valid)
expectAssignable<Transaction>({
id: 'f5d8ee39a430901c91a5917b9f2dc19d6d1a0e9cea205b009ca73dd04470b9a6',
timestamp: null,
chain: 'eip155:1',
status: 'submitted',
type: 'send',
account: '5cd17616-ea18-4d72-974f-6dbaa3c56d15',
from: [],
to: [],
fees: [],
events: [],
details: {
securityAlertResponse: 'warning',
},
});

// Transaction with undefined details (invalid - exactOptional doesn't allow undefined)
expectNotAssignable<Transaction>({
id: 'f5d8ee39a430901c91a5917b9f2dc19d6d1a0e9cea205b009ca73dd04470b9a6',
timestamp: null,
chain: 'eip155:1',
status: 'submitted',
type: 'send',
account: '5cd17616-ea18-4d72-974f-6dbaa3c56d15',
from: [],
to: [],
fees: [],
events: [],
details: undefined,
});

// Transaction with invalid securityAlertResponse (invalid - must be 'benign', 'warning', or 'malicious')
expectNotAssignable<Transaction>({
id: 'f5d8ee39a430901c91a5917b9f2dc19d6d1a0e9cea205b009ca73dd04470b9a6',
timestamp: null,
chain: 'eip155:1',
status: 'submitted',
type: 'send',
account: '5cd17616-ea18-4d72-974f-6dbaa3c56d15',
from: [],
to: [],
fees: [],
events: [],
details: {
securityAlertResponse: 'invalid',
},
});
86 changes: 86 additions & 0 deletions packages/keyring-api/src/api/transaction.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { is } from '@metamask/superstruct';

import { TransactionStruct } from './transaction';

describe('TransactionStruct', () => {
const baseTransaction = {
id: 'f5d8ee39a430901c91a5917b9f2dc19d6d1a0e9cea205b009ca73dd04470b9a6',
chain: 'eip155:1',
account: '5cd17616-ea18-4d72-974f-6dbaa3c56d15',
status: 'confirmed',
timestamp: 1716367781,
type: 'send',
from: [],
to: [],
fees: [],
events: [],
};

describe('details field', () => {
it.each([
// Without details field
{ transaction: baseTransaction, expected: true },
// With empty details
{ transaction: { ...baseTransaction, details: {} }, expected: true },
// With only origin
{
transaction: {
...baseTransaction,
details: { origin: 'https://dapp.test' },
},
expected: true,
},
// With only securityAlertResponse
{
transaction: {
...baseTransaction,
details: { securityAlertResponse: 'benign' },
},
expected: true,
},
// With both fields
{
transaction: {
...baseTransaction,
details: { origin: 'metamask', securityAlertResponse: 'warning' },
},
expected: true,
},
// All valid securityAlertResponse values
{
transaction: {
...baseTransaction,
details: { securityAlertResponse: 'benign' },
},
expected: true,
},
{
transaction: {
...baseTransaction,
details: { securityAlertResponse: 'warning' },
},
expected: true,
},
{
transaction: {
...baseTransaction,
details: { securityAlertResponse: 'malicious' },
},
expected: true,
},
// Invalid securityAlertResponse
{
transaction: {
...baseTransaction,
details: { securityAlertResponse: 'invalid' },
},
expected: false,
},
])(
'returns $expected for is($transaction, TransactionStruct)',
({ transaction, expected }) => {
expect(is(transaction, TransactionStruct)).toBe(expected);
},
);
});
});
80 changes: 79 additions & 1 deletion packages/keyring-api/src/api/transaction.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { InferEquals } from '@metamask/keyring-utils';
import { object, UuidStruct } from '@metamask/keyring-utils';
import { exactOptional, object, UuidStruct } from '@metamask/keyring-utils';
import type { Infer } from '@metamask/superstruct';
import { array, enums, nullable, number, string } from '@metamask/superstruct';

Expand Down Expand Up @@ -164,13 +164,75 @@ export enum TransactionType {
* Represents a stake withdrawal transaction.
*/
StakeWithdraw = 'stake:withdraw',

/**
* The transaction type is unknown. It's not possible to determine the
* transaction type based on the information available.
*/
Unknown = 'unknown',
}

/**
* Security alert response values from the Security Alert API.
*/
export enum SecurityAlertResponse {
/**
* The transaction is considered safe with no detected security issues.
*/
Benign = 'Benign',

/**
* The transaction has potential security concerns that warrant user attention.
*/
Warning = 'Warning',

/**
* The transaction has been identified as malicious and should be avoided.
*/
Malicious = 'Malicious',
}

/**
* This struct represents additional transaction details.
*
* @example
* ```ts
* {
* origin: 'https://dapp.example.com',
* securityAlertResponse: 'benign',
* }
* ```
*
* @example
* ```ts
* {
* origin: 'metamask',
* securityAlertResponse: 'warning',
* }
* ```
*/
export const TransactionDetailsStruct = object({
/**
* Origin of the original transaction request.
*
* This can be either 'metamask' for internally initiated transactions, or a URL
* (e.g., 'https://dapp.example.com') for dapp-initiated transactions.
*/
origin: exactOptional(string()),

/**
* Response from the Security Alert API indicating the security assessment of the
* transaction.
*/
securityAlertResponse: exactOptional(
enums([
`${SecurityAlertResponse.Benign}`,
`${SecurityAlertResponse.Warning}`,
`${SecurityAlertResponse.Malicious}`,
]),
),
});

/**
* This struct represents a transaction event.
*/
Expand Down Expand Up @@ -318,8 +380,24 @@ export const TransactionStruct = object({
* all transactions.
*/
events: array(TransactionEventStruct),

/**
* Additional transaction details {@see TransactionDetailsStruct}.
*
* Contains contextual information about the transaction such as its origin and
* security assessment. This field is optional and may not be present for all
* transactions.
*/
details: exactOptional(TransactionDetailsStruct),
});

/**
* Transaction details object.
*
* See {@link TransactionDetailsStruct}.
*/
export type TransactionDetails = Infer<typeof TransactionDetailsStruct>;

/**
* Transaction object.
*
Expand Down
Loading