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

### 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
- Add support for custom capabilities and entropy types in `KeyringV2` ([#415](https://github.com/MetaMask/accounts/pull/415))
- Add `custom` capability to `KeyringCapabilities` for keyrings with non-standard `createAccounts` method.
- Add `KeyringAccountEntropyTypeOption.Custom` for custom/opaque entropy sources.
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);
},
);
});
});
73 changes: 72 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',
* }
* ```
*/
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,6 +380,15 @@ 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),
});

/**
Expand Down
Loading