Skip to content
Draft
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
19 changes: 19 additions & 0 deletions packages/notification-services-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Export `isOnChainNotification` and `isPlatformNotification` type guards for discriminating v4 API notification shapes ([#9384](https://github.com/MetaMask/core/pull/9384))
- Export `PlatformNotification` and `OnChainNotification` types derived from the v4 Notification API schema ([#9384](https://github.com/MetaMask/core/pull/9384))

### Changed

- **BREAKING:** Moved Notification API from v3 to v4 ([#9384](https://github.com/MetaMask/core/pull/9384))
- API Endpoint Changes: Updated from `/api/v3/notifications` to `/api/v4/notifications` for listing notifications and marking as read
- Response Structure: `notification_type` and `notification_subtype` now reflect producer-set database fields instead of fixed enum values
- On-chain notifications: `notification_type` is now `"wallet_activity"` (was `"on-chain"`), with `notification_subtype` set to the on-chain kind (e.g. `"metamask_swap_completed"`)
- Platform notifications: `notification_type` is now a producer-set value (e.g. `"perps"`, was `"platform"`), with `notification_subtype` set to the platform subtype (e.g. `"position_liquidated"`)
- Clients should use the `isOnChainNotification` / `isPlatformNotification` type guards to distinguish on-chain vs platform notifications
- Type System:
- `UnprocessedRawNotification` now uses `NotificationOutputV4` shapes (`PlatformNotificationV4` | `OnChainNotificationV4`)
- `toRawAPINotification()` now normalises v4 notifications, mapping on-chain `notification_subtype` to the `type` field
- Regenerated `schema.ts` from the latest Notification API OpenAPI spec, including v4 paths and legacy v1–v3 schemas
- `AppPlatform` now includes `"portfolio"` in addition to `"extension"` and `"mobile"`

## [24.3.0]

### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import type { NormalisedAPINotification } from '../types/notification-api/notifi
export function createMockNotificationEthSent(): NormalisedAPINotification {
const mockNotification: NormalisedAPINotification = {
type: TRIGGER_TYPES.ETH_SENT,
notification_type: 'on-chain',
notification_type: 'wallet_activity',
notification_subtype: 'eth_sent',
id: '3fa85f64-5717-4562-b3fc-2c963f66afa7',
unread: true,
created_at: '2022-03-01T00:00:00Z',
Expand Down Expand Up @@ -55,7 +56,8 @@ export function createMockNotificationEthSent(): NormalisedAPINotification {
export function createMockNotificationEthReceived(): NormalisedAPINotification {
const mockNotification: NormalisedAPINotification = {
type: TRIGGER_TYPES.ETH_RECEIVED,
notification_type: 'on-chain',
notification_type: 'wallet_activity',
notification_subtype: 'eth_received',
id: '3fa85f64-5717-4562-b3fc-2c963f66afa8',
unread: true,
created_at: '2022-03-01T00:00:00Z',
Expand Down Expand Up @@ -101,7 +103,8 @@ export function createMockNotificationEthReceived(): NormalisedAPINotification {
export function createMockNotificationERC20Sent(): NormalisedAPINotification {
const mockNotification: NormalisedAPINotification = {
type: TRIGGER_TYPES.ERC20_SENT,
notification_type: 'on-chain',
notification_type: 'wallet_activity',
notification_subtype: 'erc20_sent',
id: '3fa85f64-5717-4562-b3fc-2c963f66afa9',
unread: true,
created_at: '2022-03-01T00:00:00Z',
Expand Down Expand Up @@ -153,7 +156,8 @@ export function createMockNotificationERC20Sent(): NormalisedAPINotification {
export function createMockNotificationERC20Received(): NormalisedAPINotification {
const mockNotification: NormalisedAPINotification = {
type: TRIGGER_TYPES.ERC20_RECEIVED,
notification_type: 'on-chain',
notification_type: 'wallet_activity',
notification_subtype: 'erc20_received',
id: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
unread: true,
created_at: '2022-03-01T00:00:00Z',
Expand Down Expand Up @@ -205,7 +209,8 @@ export function createMockNotificationERC20Received(): NormalisedAPINotification
export function createMockNotificationERC721Sent(): NormalisedAPINotification {
const mockNotification: NormalisedAPINotification = {
type: TRIGGER_TYPES.ERC721_SENT,
notification_type: 'on-chain',
notification_type: 'wallet_activity',
notification_subtype: 'erc721_sent',
id: 'a4193058-9814-537e-9df4-79dcac727fb6',
created_at: '2023-11-15T11:08:17.895407Z',
unread: true,
Expand Down Expand Up @@ -260,7 +265,8 @@ export function createMockNotificationERC721Sent(): NormalisedAPINotification {
export function createMockNotificationERC721Received(): NormalisedAPINotification {
const mockNotification: NormalisedAPINotification = {
type: TRIGGER_TYPES.ERC721_RECEIVED,
notification_type: 'on-chain',
notification_type: 'wallet_activity',
notification_subtype: 'erc721_received',
id: '00a79d24-befa-57ed-a55a-9eb8696e1654',
created_at: '2023-11-14T17:40:52.319281Z',
unread: true,
Expand Down Expand Up @@ -315,7 +321,8 @@ export function createMockNotificationERC721Received(): NormalisedAPINotificatio
export function createMockNotificationERC1155Sent(): NormalisedAPINotification {
const mockNotification: NormalisedAPINotification = {
type: TRIGGER_TYPES.ERC1155_SENT,
notification_type: 'on-chain',
notification_type: 'wallet_activity',
notification_subtype: 'erc1155_sent',
id: 'a09ff9d1-623a-52ab-a3d4-c7c8c9a58362',
created_at: '2023-11-20T20:44:10.110706Z',
unread: true,
Expand Down Expand Up @@ -370,7 +377,8 @@ export function createMockNotificationERC1155Sent(): NormalisedAPINotification {
export function createMockNotificationERC1155Received(): NormalisedAPINotification {
const mockNotification: NormalisedAPINotification = {
type: TRIGGER_TYPES.ERC1155_RECEIVED,
notification_type: 'on-chain',
notification_type: 'wallet_activity',
notification_subtype: 'erc1155_received',
id: 'b6b93c84-e8dc-54ed-9396-7ea50474843a',
created_at: '2023-11-20T20:44:10.110706Z',
unread: true,
Expand Down Expand Up @@ -425,7 +433,8 @@ export function createMockNotificationERC1155Received(): NormalisedAPINotificati
export function createMockNotificationMetaMaskSwapsCompleted(): NormalisedAPINotification {
const mockNotification: NormalisedAPINotification = {
type: TRIGGER_TYPES.METAMASK_SWAP_COMPLETED,
notification_type: 'on-chain',
notification_type: 'wallet_activity',
notification_subtype: 'metamask_swap_completed',
id: '7ddfe6a1-ac52-5ffe-aa40-f04242db4b8b',
created_at: '2023-10-18T13:58:49.854596Z',
unread: true,
Expand Down Expand Up @@ -486,7 +495,8 @@ export function createMockNotificationMetaMaskSwapsCompleted(): NormalisedAPINot
export function createMockNotificationRocketPoolStakeCompleted(): NormalisedAPINotification {
const mockNotification: NormalisedAPINotification = {
type: TRIGGER_TYPES.ROCKETPOOL_STAKE_COMPLETED,
notification_type: 'on-chain',
notification_type: 'wallet_activity',
notification_subtype: 'rocketpool_stake_completed',
id: 'c2a2f225-b2fb-5d6c-ba56-e27a5c71ffb9',
created_at: '2023-11-20T12:02:48.796824Z',
unread: true,
Expand Down Expand Up @@ -546,7 +556,8 @@ export function createMockNotificationRocketPoolStakeCompleted(): NormalisedAPIN
export function createMockNotificationRocketPoolUnStakeCompleted(): NormalisedAPINotification {
const mockNotification: NormalisedAPINotification = {
type: TRIGGER_TYPES.ROCKETPOOL_UNSTAKE_COMPLETED,
notification_type: 'on-chain',
notification_type: 'wallet_activity',
notification_subtype: 'rocketpool_unstake_completed',
id: '291ec897-f569-4837-b6c0-21001b198dff',
created_at: '2023-10-19T13:11:10.623042Z',
unread: true,
Expand Down Expand Up @@ -606,7 +617,8 @@ export function createMockNotificationRocketPoolUnStakeCompleted(): NormalisedAP
export function createMockNotificationLidoStakeCompleted(): NormalisedAPINotification {
const mockNotification: NormalisedAPINotification = {
type: TRIGGER_TYPES.LIDO_STAKE_COMPLETED,
notification_type: 'on-chain',
notification_type: 'wallet_activity',
notification_subtype: 'lido_stake_completed',
id: 'ec10d66a-f78f-461f-83c9-609aada8cc50',
created_at: '2023-11-02T22:28:49.970865Z',
unread: true,
Expand Down Expand Up @@ -666,7 +678,8 @@ export function createMockNotificationLidoStakeCompleted(): NormalisedAPINotific
export function createMockNotificationLidoWithdrawalRequested(): NormalisedAPINotification {
const mockNotification: NormalisedAPINotification = {
type: TRIGGER_TYPES.LIDO_WITHDRAWAL_REQUESTED,
notification_type: 'on-chain',
notification_type: 'wallet_activity',
notification_subtype: 'lido_withdrawal_requested',
id: 'ef003925-3379-4ba7-9e2d-8218690cadc9',
created_at: '2023-10-18T15:04:02.482526Z',
unread: true,
Expand Down Expand Up @@ -726,7 +739,8 @@ export function createMockNotificationLidoWithdrawalRequested(): NormalisedAPINo
export function createMockNotificationLidoWithdrawalCompleted(): NormalisedAPINotification {
const mockNotification: NormalisedAPINotification = {
type: TRIGGER_TYPES.LIDO_WITHDRAWAL_COMPLETED,
notification_type: 'on-chain',
notification_type: 'wallet_activity',
notification_subtype: 'lido_withdrawal_completed',
id: 'd73df14d-ce73-4f38-bad3-ab028154042f',
created_at: '2023-10-18T16:35:03.147606Z',
unread: true,
Expand Down Expand Up @@ -786,7 +800,8 @@ export function createMockNotificationLidoWithdrawalCompleted(): NormalisedAPINo
export function createMockNotificationLidoReadyToBeWithdrawn(): NormalisedAPINotification {
const mockNotification: NormalisedAPINotification = {
type: TRIGGER_TYPES.LIDO_STAKE_READY_TO_BE_WITHDRAWN,
notification_type: 'on-chain',
notification_type: 'wallet_activity',
notification_subtype: 'lido_stake_ready_to_be_withdrawn',
id: 'd73df14d-ce73-4f38-bad3-ab028154042e',
created_at: '2023-10-18T16:35:03.147606Z',
unread: true,
Expand Down Expand Up @@ -833,7 +848,8 @@ export function createMockNotificationLidoReadyToBeWithdrawn(): NormalisedAPINot
export function createMockPlatformNotification(): NormalisedAPINotification {
const mockNotification: NormalisedAPINotification = {
type: TRIGGER_TYPES.PLATFORM,
notification_type: 'platform',
notification_type: 'perps',
notification_subtype: 'position_liquidated',
id: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
unread: true,
created_at: '2025-10-09T09:45:34.202Z',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ export const TRIGGER_API_NOTIFICATIONS_QUERY_ENDPOINT = (

// Lists notifications for each address provided
export const NOTIFICATION_API_LIST_ENDPOINT = (env: ENV = 'prd'): string =>
`${NOTIFICATION_API(env)}/api/v3/notifications`;
`${NOTIFICATION_API(env)}/api/v4/notifications`;

// Marks notifications as read
export const NOTIFICATION_API_MARK_ALL_AS_READ_ENDPOINT = (
env: ENV = 'prd',
): string => `${NOTIFICATION_API(env)}/api/v3/notifications/mark-as-read`;
): string => `${NOTIFICATION_API(env)}/api/v4/notifications/mark-as-read`;

/**
* fetches notification config (accounts enabled vs disabled)
Expand Down Expand Up @@ -112,9 +112,9 @@ export async function getAPINotifications(
}

type RequestBody =
Schema.paths['/api/v3/notifications']['post']['requestBody']['content']['application/json'];
Schema.paths['/api/v4/notifications']['post']['requestBody']['content']['application/json'];
type APIResponse =
Schema.paths['/api/v3/notifications']['post']['responses']['200']['content']['application/json'];
Schema.paths['/api/v4/notifications']['post']['responses']['200']['content']['application/json'];

const body: RequestBody = {
addresses: addresses.map((addr) => addr.toLowerCase()),
Expand Down Expand Up @@ -170,7 +170,7 @@ export async function markNotificationsAsRead(
}

type ResponseBody =
Schema.paths['/api/v3/notifications/mark-as-read']['post']['requestBody']['content']['application/json'];
Schema.paths['/api/v4/notifications/mark-as-read']['post']['requestBody']['content']['application/json'];
const body: ResponseBody = {
ids: notificationIds,
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { TRIGGER_TYPES } from '../../constants/notification-schema';
import { TRIGGER_TYPES } from '../../constants/notification-schema';
import type { Compute } from '../type-utils';
// Types derived from external Notification API schema - naming follows API conventions
/* eslint-disable @typescript-eslint/naming-convention */
Expand Down Expand Up @@ -27,15 +27,12 @@ export type Data_ERC721Received = components['schemas']['Data_ERC721Received'];
export type NetworkMetadata = components['schemas']['NetworkMetadata'];
export type BlockExplorer = components['schemas']['BlockExplorer'];

type Notification = components['schemas']['NotificationOutputV3'][number];
type PlatformNotification = Extract<
Notification,
{ notification_type: 'platform' }
>;
type OnChainNotification = Extract<
Notification,
{ notification_type: 'on-chain' }
>;
export type UnprocessedRawNotification =
components['schemas']['NotificationOutputV4'][number];
export type PlatformNotification =
components['schemas']['PlatformNotificationV4'];
export type OnChainNotification =
components['schemas']['OnChainNotificationV4'];

type ConvertToEnum<Kind> = {
[K in TRIGGER_TYPES]: Kind extends `${K}` ? K : never;
Expand Down Expand Up @@ -71,14 +68,11 @@ type NormalizeOnChainNotification<
*/
type NormalizePlatformNotification<
N extends PlatformNotification = PlatformNotification,
NotificationKind extends string = N['notification_type'],
> = {
[K in NotificationKind]: Compute<
N & {
type: ConvertToEnum<K>;
}
>;
}[NotificationKind];
> = Compute<
N & {
type: TRIGGER_TYPES.PLATFORM;
}
>;

export type OnChainRawNotification = Compute<
NormalizeOnChainNotification<OnChainNotification>
Expand All @@ -88,8 +82,6 @@ export type PlatformRawNotification = Compute<
NormalizePlatformNotification<PlatformNotification>
>;

export type UnprocessedRawNotification = Notification;

export type NormalisedAPINotification =
| OnChainRawNotification
| PlatformRawNotification;
Expand Down
Loading