diff --git a/lib/flow-typed/npm/tcomb_v3.x.x.js b/lib/flow-typed/npm/tcomb_v3.x.x.js
index a367c2fb9..d1f62efc5 100644
--- a/lib/flow-typed/npm/tcomb_v3.x.x.js
+++ b/lib/flow-typed/npm/tcomb_v3.x.x.js
@@ -1,139 +1,139 @@
 // flow-typed signature: f712ee1961974c799331608650bc7eb2
 // flow-typed version: <<STUB>>/tcomb_v3.2.29/flow_v0.137.0
 
 declare module 'tcomb' {
   declare class TBaseType<+T> {
     (val: T): this;
     is(val: mixed): boolean;
     displayName: string;
     +t: T;
   }
 
   declare export type TType<+T> =
     | TIrreducible<T>
     | TMaybe<T>
     | TList<T>
     | TDict<T>
     | TUnion<T>
     | TEnums
     | TRefinement<T>
     | TInterface<T>;
 
   declare export class TIrreducible<+T> extends TBaseType<T> {
     meta: {
       +kind: 'irreducible',
       +name: string,
       +identity: boolean,
       +predicate: mixed => boolean,
     };
   }
 
   declare export class TMaybe<+T> extends TBaseType<T> {
     meta: {
       +kind: 'maybe',
       +name: string,
       +identity: boolean,
       +type: TType<T>,
     };
   }
 
   declare export class TList<+T> extends TBaseType<T> {
     meta: {
       +kind: 'list',
       +name: string,
       +identity: boolean,
       +type: TType<T>,
     };
   }
 
   declare export class TDict<+T> extends TBaseType<T> {
     meta: {
       +kind: 'dict',
       +name: string,
       +identity: boolean,
       +domain: TType<string>,
       +codomain: TType<T>,
     };
   }
 
   declare export class TUnion<+T> extends TBaseType<T> {
     meta: {
       +kind: 'union',
       +name: string,
       +identity: boolean,
       +types: Array<TType<T>>,
     };
   }
 
-  declare export class TEnums extends TBaseType<string> {
+  declare export class TEnums extends TBaseType<string | number> {
     meta: {
       +kind: 'enums',
       +name: string,
       +identity: boolean,
       +map: Object,
     };
   }
 
   declare export class TRefinement<+T> extends TBaseType<T> {
     meta: {
       +kind: 'subtype',
       +name: string,
       +identity: boolean,
       +type: TType<T>,
       +predicate: mixed => boolean,
     };
   }
 
   declare type TypeToValidator = <V>(v: V) => TType<V>;
 
   declare export type TStructProps<+T> = $ObjMap<T, TypeToValidator>;
   declare type TStructOptions = {
     name?: string,
     strict?: boolean,
     defaultProps?: Object,
   };
 
   declare export class TInterface<+T> extends TBaseType<T> {
     meta: {
       +kind: 'interface',
       +name: string,
       +identity: boolean,
       +props: TStructProps<T>,
       +strict: boolean,
     };
   }
 
   declare export default {
     +Nil: TIrreducible<void | null>,
     +Bool: TIrreducible<boolean>,
     +Boolean: TIrreducible<boolean>,
     +String: TIrreducible<string>,
     +Number: TIrreducible<number>,
     +Object: TIrreducible<Object>,
     maybe<T>(type: TType<T>, name?: string): TMaybe<void | T>,
     list<T>(type: TType<T>, name?: string): TList<Array<T>>,
     dict<S: string, T>(
       domain: TType<S>,
       codomain: TType<T>,
       name?: string,
     ): TDict<{ [key: S]: T }>,
     union<+T>(types: $ReadOnlyArray<TType<T>>, name?: string): TUnion<T>,
     +enums: {
-      of(enums: $ReadOnlyArray<string>, name?: string): TEnums,
+      of(enums: $ReadOnlyArray<string> | $ReadOnlyArray<number>, name?: string): TEnums,
     },
     irreducible<T>(
       name: string,
       predicate: (mixed) => boolean,
     ): TIrreducible<T>,
     refinement<T>(
       type: TType<T>,
       predicate: (T) => boolean,
       name?: string,
     ): TRefinement<T>,
     interface<T>(
       props: TStructProps<T>,
       options?: string | TStructOptions,
     ): TInterface<T>,
     ...
   };
 }
diff --git a/lib/reducers/aux-user-reducer.js b/lib/reducers/aux-user-reducer.js
index 246b46057..00512b262 100644
--- a/lib/reducers/aux-user-reducer.js
+++ b/lib/reducers/aux-user-reducer.js
@@ -1,150 +1,151 @@
 // @flow
 
 import {
   setAuxUserFIDsActionType,
   addAuxUserFIDsActionType,
   clearAuxUserFIDsActionType,
   setPeerDeviceListsActionType,
 } from '../actions/aux-user-actions.js';
 import { setClientDBStoreActionType } from '../actions/client-db-store-actions.js';
 import {
   auxUserStoreOpsHandlers,
   type AuxUserStoreOperation,
   type ReplaceAuxUserInfoOperation,
 } from '../ops/aux-user-store-ops.js';
 import type { AuxUserStore } from '../types/aux-user-types.js';
 import type { BaseAction } from '../types/redux-types';
 
 const { processStoreOperations: processStoreOps } = auxUserStoreOpsHandlers;
 
 function reduceAuxUserStore(
   state: AuxUserStore,
   action: BaseAction,
 ): {
   +auxUserStore: AuxUserStore,
   +auxUserStoreOperations: $ReadOnlyArray<AuxUserStoreOperation>,
 } {
   if (action.type === setAuxUserFIDsActionType) {
     const toUpdateUserIDs = new Set(
       action.payload.farcasterUsers.map(farcasterUser => farcasterUser.userID),
     );
     const replaceOperations: ReplaceAuxUserInfoOperation[] = [];
     for (const userID in state.auxUserInfos) {
       if (
         state.auxUserInfos[userID].fid !== null &&
         !toUpdateUserIDs.has(userID)
       ) {
         replaceOperations.push({
           type: 'replace_aux_user_info',
           payload: {
             id: userID,
             auxUserInfo: {
               ...state.auxUserInfos[userID],
               fid: null,
             },
           },
         });
       }
     }
     for (const farcasterUser of action.payload.farcasterUsers) {
       replaceOperations.push({
         type: 'replace_aux_user_info',
         payload: {
           id: farcasterUser.userID,
           auxUserInfo: {
             ...state.auxUserInfos[farcasterUser.userID],
             fid: farcasterUser.farcasterID,
           },
         },
       });
     }
     return {
       auxUserStore: processStoreOps(state, replaceOperations),
       auxUserStoreOperations: replaceOperations,
     };
   } else if (action.type === addAuxUserFIDsActionType) {
     const replaceOperations: ReplaceAuxUserInfoOperation[] = [];
     for (const farcasterUser of action.payload.farcasterUsers) {
       replaceOperations.push({
         type: 'replace_aux_user_info',
         payload: {
           id: farcasterUser.userID,
           auxUserInfo: {
             ...state.auxUserInfos[farcasterUser.userID],
             fid: farcasterUser.farcasterID,
           },
         },
       });
     }
     return {
       auxUserStore: processStoreOps(state, replaceOperations),
       auxUserStoreOperations: replaceOperations,
     };
   } else if (action.type === clearAuxUserFIDsActionType) {
     const replaceOperations: ReplaceAuxUserInfoOperation[] = [];
     for (const userID in state.auxUserInfos) {
       if (state.auxUserInfos[userID].fid !== null) {
         replaceOperations.push({
           type: 'replace_aux_user_info',
           payload: {
             id: userID,
             auxUserInfo: {
               ...state.auxUserInfos[userID],
               fid: null,
             },
           },
         });
       }
     }
 
     return {
       auxUserStore: processStoreOps(state, replaceOperations),
       auxUserStoreOperations: replaceOperations,
     };
   } else if (action.type === setClientDBStoreActionType) {
     const newAuxUserInfos = action.payload.auxUserInfos;
 
     if (!newAuxUserInfos) {
       return {
         auxUserStore: state,
         auxUserStoreOperations: [],
       };
     }
 
     const newAuxUserStore: AuxUserStore = {
       ...state,
       auxUserInfos: newAuxUserInfos,
     };
 
     return {
       auxUserStore: newAuxUserStore,
       auxUserStoreOperations: [],
     };
   } else if (action.type === setPeerDeviceListsActionType) {
     const replaceOperations: ReplaceAuxUserInfoOperation[] = [];
     for (const userID in action.payload.deviceLists) {
       replaceOperations.push({
         type: 'replace_aux_user_info',
         payload: {
           id: userID,
           auxUserInfo: {
             ...state.auxUserInfos[userID],
             fid: state.auxUserInfos[userID]?.fid ?? null,
             deviceList: action.payload.deviceLists[userID],
+            devicesPlatformDetails: action.payload.usersPlatformDetails[userID],
           },
         },
       });
     }
     return {
       auxUserStore: processStoreOps(state, replaceOperations),
       auxUserStoreOperations: replaceOperations,
     };
   }
 
   return {
     auxUserStore: state,
     auxUserStoreOperations: [],
   };
 }
 
 export { reduceAuxUserStore };
diff --git a/lib/reducers/aux-user-reducer.test.js b/lib/reducers/aux-user-reducer.test.js
index 7ee2b263b..3af01c10b 100644
--- a/lib/reducers/aux-user-reducer.test.js
+++ b/lib/reducers/aux-user-reducer.test.js
@@ -1,132 +1,165 @@
 // @flow
 
 import { reduceAuxUserStore } from './aux-user-reducer.js';
 import {
   addAuxUserFIDsActionType,
   setAuxUserFIDsActionType,
   setPeerDeviceListsActionType,
 } from '../actions/aux-user-actions.js';
-import type { RawDeviceList } from '../types/identity-service-types.js';
+import {
+  type RawDeviceList,
+  type IdentityPlatformDetails,
+  identityDeviceTypes,
+} from '../types/identity-service-types.js';
 
 jest.mock('../utils/config.js');
 
 describe('reduceAuxUserStore', () => {
   it('should update aux user store with farcaster data for user2', () => {
     const oldAuxUserStore = {
       auxUserInfos: {
         userID_1: {
           fid: 'farcasterID_1',
         },
       },
     };
 
     const updateAuxUserInfosAction = {
       type: addAuxUserFIDsActionType,
       payload: {
         farcasterUsers: [
           {
             userID: 'userID_2',
             username: 'username_2',
             farcasterID: 'farcasterID_2',
           },
         ],
       },
     };
 
     expect(
       reduceAuxUserStore(oldAuxUserStore, updateAuxUserInfosAction)
         .auxUserStore,
     ).toEqual({
       auxUserInfos: {
         userID_1: {
           fid: 'farcasterID_1',
         },
         userID_2: {
           fid: 'farcasterID_2',
         },
       },
     });
   });
 
   it('should set aux user store user1 fid to null and add user2', () => {
     const oldAuxUserStore = {
       auxUserInfos: {
         userID_1: {
           fid: 'farcasterID_1',
         },
       },
     };
 
     const updateAuxUserInfosAction = {
       type: setAuxUserFIDsActionType,
       payload: {
         farcasterUsers: [
           {
             userID: 'userID_2',
             username: 'username_2',
             farcasterID: 'farcasterID_2',
           },
         ],
       },
     };
 
     expect(
       reduceAuxUserStore(oldAuxUserStore, updateAuxUserInfosAction)
         .auxUserStore,
     ).toEqual({
       auxUserInfos: {
         userID_1: {
           fid: null,
         },
         userID_2: {
           fid: 'farcasterID_2',
         },
       },
     });
   });
 
   it('should update aux user store with device lists for users', () => {
     const oldAuxUserStore = {
       auxUserInfos: {
         userID_1: {
           fid: 'farcasterID_1',
         },
       },
     };
 
     const deviceList1: RawDeviceList = {
       devices: ['D1', 'D2'],
       timestamp: 1,
     };
+    const devicesPlatformDetails1: {
+      +[deviceID: string]: IdentityPlatformDetails,
+    } = {
+      D1: {
+        deviceType: identityDeviceTypes.ANDROID,
+        codeVersion: 350,
+        stateVersion: 75,
+      },
+      D2: {
+        deviceType: identityDeviceTypes.WINDOWS,
+        codeVersion: 80,
+        stateVersion: 75,
+      },
+    };
     const deviceList2: RawDeviceList = {
       devices: ['D3'],
       timestamp: 1,
     };
+    const devicesPlatformDetails2: {
+      +[deviceID: string]: IdentityPlatformDetails,
+    } = {
+      D3: {
+        deviceType: identityDeviceTypes.IOS,
+        codeVersion: 350,
+        stateVersion: 75,
+      },
+    };
 
     const updateAuxUserInfosAction = {
       type: setPeerDeviceListsActionType,
       payload: {
         deviceLists: {
           userID_1: deviceList1,
           userID_2: deviceList2,
         },
+        usersPlatformDetails: {
+          userID_1: devicesPlatformDetails1,
+          userID_2: devicesPlatformDetails2,
+        },
       },
     };
 
     expect(
       reduceAuxUserStore(oldAuxUserStore, updateAuxUserInfosAction)
         .auxUserStore,
     ).toEqual({
       auxUserInfos: {
         userID_1: {
           fid: 'farcasterID_1',
           deviceList: deviceList1,
+          devicesPlatformDetails: devicesPlatformDetails1,
         },
         userID_2: {
           fid: null,
           deviceList: deviceList2,
+          devicesPlatformDetails: devicesPlatformDetails2,
         },
       },
     });
   });
 });
diff --git a/lib/types/aux-user-types.js b/lib/types/aux-user-types.js
index 2233b8321..fa0a0b1ed 100644
--- a/lib/types/aux-user-types.js
+++ b/lib/types/aux-user-types.js
@@ -1,27 +1,35 @@
 // @flow
 
 import type {
   FarcasterUser,
   RawDeviceList,
   UsersRawDeviceLists,
+  IdentityPlatformDetails,
 } from './identity-service-types.js';
 
-export type AuxUserInfo = { +fid: ?string, deviceList?: RawDeviceList };
+export type AuxUserInfo = {
+  +fid: ?string,
+  +deviceList?: RawDeviceList,
+  +devicesPlatformDetails?: { +[deviceID: string]: IdentityPlatformDetails },
+};
 
 export type AuxUserInfos = { +[userID: string]: AuxUserInfo };
 
 export type AuxUserStore = {
   +auxUserInfos: AuxUserInfos,
 };
 
 export type SetAuxUserFIDsPayload = {
   +farcasterUsers: $ReadOnlyArray<FarcasterUser>,
 };
 
 export type AddAuxUserFIDsPayload = {
   +farcasterUsers: $ReadOnlyArray<FarcasterUser>,
 };
 
 export type SetPeerDeviceListsPayload = {
   +deviceLists: UsersRawDeviceLists,
+  +usersPlatformDetails: {
+    +[userID: string]: { +[deviceID: string]: IdentityPlatformDetails },
+  },
 };
diff --git a/lib/types/identity-service-types.js b/lib/types/identity-service-types.js
index c797686d6..dc952d030 100644
--- a/lib/types/identity-service-types.js
+++ b/lib/types/identity-service-types.js
@@ -1,324 +1,344 @@
 // @flow
 
-import t, { type TInterface, type TList, type TDict } from 'tcomb';
+import t, { type TInterface, type TList, type TDict, type TEnums } from 'tcomb';
 
 import {
   identityKeysBlobValidator,
   type IdentityKeysBlob,
   signedPrekeysValidator,
   type SignedPrekeys,
   type OneTimeKeysResultValues,
 } from './crypto-types.js';
 import {
   type OlmSessionInitializationInfo,
   olmSessionInitializationInfoValidator,
 } from './request-types.js';
 import {
   currentUserInfoValidator,
   type CurrentUserInfo,
 } from './user-types.js';
+import { values } from '../utils/objects.js';
 import { tUserID, tShape } from '../utils/validation-utils.js';
 
 export type UserAuthMetadata = {
   +userID: string,
   +accessToken: string,
 };
 
 // This type should not be altered without also updating OutboundKeyInfoResponse
 // in native/native_rust_library/src/identity/x3dh.rs
 export type OutboundKeyInfoResponse = {
   +payload: string,
   +payloadSignature: string,
   +contentPrekey: string,
   +contentPrekeySignature: string,
   +notifPrekey: string,
   +notifPrekeySignature: string,
   +oneTimeContentPrekey: ?string,
   +oneTimeNotifPrekey: ?string,
 };
 
 // This type should not be altered without also updating InboundKeyInfoResponse
 // in native/native_rust_library/src/identity/x3dh.rs
 export type InboundKeyInfoResponse = {
   +payload: string,
   +payloadSignature: string,
   +contentPrekey: string,
   +contentPrekeySignature: string,
   +notifPrekey: string,
   +notifPrekeySignature: string,
   +username?: ?string,
   +walletAddress?: ?string,
 };
 
 export type DeviceOlmOutboundKeys = {
   +identityKeysBlob: IdentityKeysBlob,
   +contentInitializationInfo: OlmSessionInitializationInfo,
   +notifInitializationInfo: OlmSessionInitializationInfo,
   +payloadSignature: string,
 };
 export const deviceOlmOutboundKeysValidator: TInterface<DeviceOlmOutboundKeys> =
   tShape<DeviceOlmOutboundKeys>({
     identityKeysBlob: identityKeysBlobValidator,
     contentInitializationInfo: olmSessionInitializationInfoValidator,
     notifInitializationInfo: olmSessionInitializationInfoValidator,
     payloadSignature: t.String,
   });
 
 export type UserDevicesOlmOutboundKeys = {
   +deviceID: string,
   +keys: ?DeviceOlmOutboundKeys,
 };
 
 export type DeviceOlmInboundKeys = {
   +identityKeysBlob: IdentityKeysBlob,
   +signedPrekeys: SignedPrekeys,
   +payloadSignature: string,
 };
 export const deviceOlmInboundKeysValidator: TInterface<DeviceOlmInboundKeys> =
   tShape<DeviceOlmInboundKeys>({
     identityKeysBlob: identityKeysBlobValidator,
     signedPrekeys: signedPrekeysValidator,
     payloadSignature: t.String,
   });
 
 export type UserDevicesOlmInboundKeys = {
   +keys: {
     +[deviceID: string]: ?DeviceOlmInboundKeys,
   },
   +username?: ?string,
   +walletAddress?: ?string,
 };
 
 // This type should not be altered without also updating FarcasterUser in
 // keyserver/addons/rust-node-addon/src/identity_client/get_farcaster_users.rs
 export type FarcasterUser = {
   +userID: string,
   +username: string,
   +farcasterID: string,
 };
 
 export const farcasterUserValidator: TInterface<FarcasterUser> =
   tShape<FarcasterUser>({
     userID: tUserID,
     username: t.String,
     farcasterID: t.String,
   });
 
 export const farcasterUsersValidator: TList<Array<FarcasterUser>> = t.list(
   farcasterUserValidator,
 );
 
 export const userDeviceOlmInboundKeysValidator: TInterface<UserDevicesOlmInboundKeys> =
   tShape<UserDevicesOlmInboundKeys>({
     keys: t.dict(t.String, t.maybe(deviceOlmInboundKeysValidator)),
     username: t.maybe(t.String),
     walletAddress: t.maybe(t.String),
   });
 
 export interface IdentityServiceClient {
   // Only a primary device can initiate account deletion, and web cannot be a
   // primary device
   +deleteWalletUser?: () => Promise<void>;
   // Only a primary device can initiate account deletion, and web cannot be a
   // primary device
   +deletePasswordUser?: (password: string) => Promise<void>;
   +logOut: () => Promise<void>;
   +logOutSecondaryDevice: () => Promise<void>;
   +getKeyserverKeys: string => Promise<DeviceOlmOutboundKeys>;
   // Users cannot register from web
   +registerPasswordUser?: (
     username: string,
     password: string,
     fid: ?string,
   ) => Promise<IdentityAuthResult>;
   // Users cannot register from web
   +registerReservedPasswordUser?: (
     username: string,
     password: string,
     keyserverMessage: string,
     keyserverSignature: string,
   ) => Promise<IdentityAuthResult>;
   +logInPasswordUser: (
     username: string,
     password: string,
   ) => Promise<IdentityAuthResult>;
   +getOutboundKeysForUser: (
     userID: string,
   ) => Promise<UserDevicesOlmOutboundKeys[]>;
   +getInboundKeysForUser: (
     userID: string,
   ) => Promise<UserDevicesOlmInboundKeys>;
   +uploadOneTimeKeys: (oneTimeKeys: OneTimeKeysResultValues) => Promise<void>;
   +generateNonce: () => Promise<string>;
   // Users cannot register from web
   +registerWalletUser?: (
     walletAddress: string,
     siweMessage: string,
     siweSignature: string,
     fid: ?string,
   ) => Promise<IdentityAuthResult>;
   // Users cannot register from web
   +registerReservedWalletUser?: (
     walletAddress: string,
     siweMessage: string,
     siweSignature: string,
     keyserverMessage: string,
     keyserverSignature: string,
   ) => Promise<IdentityAuthResult>;
   +logInWalletUser: (
     walletAddress: string,
     siweMessage: string,
     siweSignature: string,
   ) => Promise<IdentityAuthResult>;
   // on native, publishing prekeys to Identity is called directly from C++,
   // there is no need to expose it to JS
   +publishWebPrekeys?: (prekeys: SignedPrekeys) => Promise<void>;
   +getDeviceListHistoryForUser: (
     userID: string,
     sinceTimestamp?: number,
   ) => Promise<$ReadOnlyArray<SignedDeviceList>>;
   +getDeviceListsForUsers: (
     userIDs: $ReadOnlyArray<string>,
   ) => Promise<UsersSignedDeviceLists>;
   // updating device list is possible only on Native
   // web cannot be a primary device, so there's no need to expose it to JS
   +updateDeviceList?: (newDeviceList: SignedDeviceList) => Promise<void>;
   +uploadKeysForRegisteredDeviceAndLogIn: (
     userID: string,
     signedNonce: SignedNonce,
   ) => Promise<IdentityAuthResult>;
   +getFarcasterUsers: (
     farcasterIDs: $ReadOnlyArray<string>,
   ) => Promise<$ReadOnlyArray<FarcasterUser>>;
   +linkFarcasterAccount: (farcasterID: string) => Promise<void>;
   +unlinkFarcasterAccount: () => Promise<void>;
   +findUserIdentities: (userIDs: $ReadOnlyArray<string>) => Promise<Identities>;
 }
 
 export type IdentityServiceAuthLayer = {
   +userID: string,
   +deviceID: string,
   +commServicesAccessToken: string,
 };
 
 export type IdentityAuthResult = {
   +userID: string,
   +accessToken: string,
   +username: string,
   +preRequestUserState?: ?CurrentUserInfo,
 };
 export const identityAuthResultValidator: TInterface<IdentityAuthResult> =
   tShape<IdentityAuthResult>({
     userID: tUserID,
     accessToken: t.String,
     username: t.String,
     preRequestUserState: t.maybe(currentUserInfoValidator),
   });
 
 export type IdentityNewDeviceKeyUpload = {
   +keyPayload: string,
   +keyPayloadSignature: string,
   +contentPrekey: string,
   +contentPrekeySignature: string,
   +notifPrekey: string,
   +notifPrekeySignature: string,
   +contentOneTimeKeys: $ReadOnlyArray<string>,
   +notifOneTimeKeys: $ReadOnlyArray<string>,
 };
 
 export type IdentityExistingDeviceKeyUpload = {
   +keyPayload: string,
   +keyPayloadSignature: string,
   +contentPrekey: string,
   +contentPrekeySignature: string,
   +notifPrekey: string,
   +notifPrekeySignature: string,
 };
 
 // Device list types
 
 export type RawDeviceList = {
   +devices: $ReadOnlyArray<string>,
   +timestamp: number,
 };
 export const rawDeviceListValidator: TInterface<RawDeviceList> =
   tShape<RawDeviceList>({
     devices: t.list(t.String),
     timestamp: t.Number,
   });
 
 export type UsersRawDeviceLists = {
   +[userID: string]: RawDeviceList,
 };
 
 // User Identity types
 
 export type EthereumIdentity = {
   walletAddress: string,
   siweMessage: string,
   siweSignature: string,
 };
 export type Identity = {
   +username: string,
   +ethIdentity: ?EthereumIdentity,
   +farcasterID: ?string,
 };
 export type Identities = {
   +[userID: string]: Identity,
 };
 export const ethereumIdentityValidator: TInterface<EthereumIdentity> =
   tShape<EthereumIdentity>({
     walletAddress: t.String,
     siweMessage: t.String,
     siweSignature: t.String,
   });
 export const identityValidator: TInterface<Identity> = tShape<Identity>({
   username: t.String,
   ethIdentity: t.maybe(ethereumIdentityValidator),
   farcasterID: t.maybe(t.String),
 });
 export const identitiesValidator: TDict<Identities> = t.dict(
   t.String,
   identityValidator,
 );
 
 export type SignedDeviceList = {
   // JSON-stringified RawDeviceList
   +rawDeviceList: string,
   // Current primary device signature. Absent for Identity Service generated
   // device lists.
   +curPrimarySignature?: string,
   // Previous primary device signature. Present only if primary device
   // has changed since last update.
   +lastPrimarySignature?: string,
 };
 export const signedDeviceListValidator: TInterface<SignedDeviceList> =
   tShape<SignedDeviceList>({
     rawDeviceList: t.String,
     curPrimarySignature: t.maybe(t.String),
     lastPrimarySignature: t.maybe(t.String),
   });
 export const signedDeviceListHistoryValidator: TList<Array<SignedDeviceList>> =
   t.list(signedDeviceListValidator);
 
 export type UsersSignedDeviceLists = {
   +[userID: string]: SignedDeviceList,
 };
 export const usersSignedDeviceListsValidator: TDict<UsersSignedDeviceLists> =
   t.dict(t.String, signedDeviceListValidator);
 
 export type SignedNonce = {
   +nonce: string,
   +nonceSignature: string,
 };
 
 export const ONE_TIME_KEYS_NUMBER = 10;
 
 export const identityDeviceTypes = Object.freeze({
   KEYSERVER: 0,
   WEB: 1,
   IOS: 2,
   ANDROID: 3,
   WINDOWS: 4,
   MAC_OS: 5,
 });
+
+export type IdentityDeviceType = $Values<typeof identityDeviceTypes>;
+export const identityDeviceTypeValidator: TEnums = t.enums.of(
+  values(identityDeviceTypes),
+);
+
+export type IdentityPlatformDetails = {
+  +deviceType: IdentityDeviceType,
+  +codeVersion: number,
+  +stateVersion?: number,
+  +majorDesktopVersion?: number,
+};
+export const identityPlatformDetailsValidator: TInterface<IdentityPlatformDetails> =
+  tShape<IdentityPlatformDetails>({
+    deviceType: identityDeviceTypeValidator,
+    codeVersion: t.Number,
+    stateVersion: t.maybe(t.Number),
+    majorDesktopVersion: t.maybe(t.Number),
+  });