@nadohq/mobile-client 0.57.0 → 0.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/README.md +6 -1
  2. package/dist/{MobileClient-DjNpN7Dw.cjs → MobileClient-aDxiM_Kf.cjs} +47 -1
  3. package/dist/MobileClient-aDxiM_Kf.cjs.map +1 -0
  4. package/dist/MobileClient.cjs +1 -1
  5. package/dist/MobileClient.d.cts +40 -6
  6. package/dist/MobileClient.d.cts.map +1 -1
  7. package/dist/MobileClient.d.ts +40 -6
  8. package/dist/MobileClient.d.ts.map +1 -1
  9. package/dist/MobileClient.js +47 -1
  10. package/dist/MobileClient.js.map +1 -1
  11. package/dist/{MobileServerFailureError-kbMi1Ntc.d.cts → MobileServerFailureError-Bpa_WA9f.d.cts} +3 -3
  12. package/dist/{MobileServerFailureError-kbMi1Ntc.d.cts.map → MobileServerFailureError-Bpa_WA9f.d.cts.map} +1 -1
  13. package/dist/{MobileServerFailureError-DvtkMAVX.d.ts → MobileServerFailureError-Dd3c6P_Z.d.ts} +3 -3
  14. package/dist/{MobileServerFailureError-DvtkMAVX.d.ts.map → MobileServerFailureError-Dd3c6P_Z.d.ts.map} +1 -1
  15. package/dist/{buildSignedMobileRequest-lSIJmG6D.d.ts → buildSignedMobileRequest-Cb1T74xV.d.ts} +2 -2
  16. package/dist/{buildSignedMobileRequest-lSIJmG6D.d.ts.map → buildSignedMobileRequest-Cb1T74xV.d.ts.map} +1 -1
  17. package/dist/{buildSignedMobileRequest-DBWN0z0u.d.cts → buildSignedMobileRequest-NHbMAPzd.d.cts} +2 -2
  18. package/dist/{buildSignedMobileRequest-DBWN0z0u.d.cts.map → buildSignedMobileRequest-NHbMAPzd.d.cts.map} +1 -1
  19. package/dist/{canonicalize-Cx3lCVpm.d.ts → canonicalize-BBYGh17w.d.ts} +2 -2
  20. package/dist/canonicalize-BBYGh17w.d.ts.map +1 -0
  21. package/dist/{canonicalize-DNgB8Jsm.d.cts → canonicalize-BF_SiNvO.d.cts} +2 -2
  22. package/dist/canonicalize-BF_SiNvO.d.cts.map +1 -0
  23. package/dist/{clientTypes-DnPE1zZO.d.cts → clientTypes-BWFKo9oE.d.cts} +63 -2
  24. package/dist/clientTypes-BWFKo9oE.d.cts.map +1 -0
  25. package/dist/{clientTypes-ws9CDHw4.d.ts → clientTypes-DCTiUV0H.d.ts} +63 -2
  26. package/dist/clientTypes-DCTiUV0H.d.ts.map +1 -0
  27. package/dist/dataMappers.cjs +34 -0
  28. package/dist/dataMappers.cjs.map +1 -1
  29. package/dist/dataMappers.d.cts +18 -5
  30. package/dist/dataMappers.d.cts.map +1 -1
  31. package/dist/dataMappers.d.ts +18 -5
  32. package/dist/dataMappers.d.ts.map +1 -1
  33. package/dist/dataMappers.js +32 -1
  34. package/dist/dataMappers.js.map +1 -1
  35. package/dist/{eip712MethodByType-T7dfjmC_.d.cts → eip712MethodByType-BSx11xG-.d.cts} +2 -2
  36. package/dist/eip712MethodByType-BSx11xG-.d.cts.map +1 -0
  37. package/dist/{eip712MethodByType-Bmu9PYx6.d.ts → eip712MethodByType-DkiAjCLs.d.ts} +2 -2
  38. package/dist/eip712MethodByType-DkiAjCLs.d.ts.map +1 -0
  39. package/dist/{follow-D6Mg8GHx.d.ts → follow-B3CZ04c0.d.cts} +7 -2
  40. package/dist/{follow-D6Mg8GHx.d.cts.map → follow-B3CZ04c0.d.cts.map} +1 -1
  41. package/dist/{follow-D6Mg8GHx.d.cts → follow-B3CZ04c0.d.ts} +7 -2
  42. package/dist/{follow-D6Mg8GHx.d.ts.map → follow-B3CZ04c0.d.ts.map} +1 -1
  43. package/dist/index.cjs +6 -1
  44. package/dist/index.d.cts +17 -16
  45. package/dist/index.d.ts +17 -16
  46. package/dist/index.js +4 -3
  47. package/dist/{mobileErrorCodes-B3kTLit_.d.ts → mobileErrorCodes-3LtreeZM.d.cts} +14 -3
  48. package/dist/mobileErrorCodes-3LtreeZM.d.cts.map +1 -0
  49. package/dist/{mobileErrorCodes-B3kTLit_.d.cts → mobileErrorCodes-3LtreeZM.d.ts} +14 -3
  50. package/dist/mobileErrorCodes-3LtreeZM.d.ts.map +1 -0
  51. package/dist/patterns-3UKIG5IS.d.cts +13 -0
  52. package/dist/patterns-3UKIG5IS.d.cts.map +1 -0
  53. package/dist/patterns-3UKIG5IS.d.ts +13 -0
  54. package/dist/patterns-3UKIG5IS.d.ts.map +1 -0
  55. package/dist/{payloadHash-Brc5ceFq.d.cts → payloadHash-BOWGjqdb.d.cts} +2 -2
  56. package/dist/payloadHash-BOWGjqdb.d.cts.map +1 -0
  57. package/dist/{payloadHash-cZgv7NSo.d.ts → payloadHash-Bzf4CtjV.d.ts} +2 -2
  58. package/dist/payloadHash-Bzf4CtjV.d.ts.map +1 -0
  59. package/dist/profileSearch-DCuB-IKD.d.cts +14 -0
  60. package/dist/profileSearch-DCuB-IKD.d.cts.map +1 -0
  61. package/dist/profileSearch-DCuB-IKD.d.ts +14 -0
  62. package/dist/profileSearch-DCuB-IKD.d.ts.map +1 -0
  63. package/dist/{serverBaseTypes-CWBdZomv.d.ts → serverBaseTypes-DWf3iGWy.d.ts} +2 -2
  64. package/dist/{serverBaseTypes-CWBdZomv.d.ts.map → serverBaseTypes-DWf3iGWy.d.ts.map} +1 -1
  65. package/dist/{serverBaseTypes-CcWT_ZBe.d.cts → serverBaseTypes-KREDe2M9.d.cts} +2 -2
  66. package/dist/{serverBaseTypes-CcWT_ZBe.d.cts.map → serverBaseTypes-KREDe2M9.d.cts.map} +1 -1
  67. package/dist/{serverExecuteTypes-CgjzwBaA.d.cts → serverExecuteTypes-DVRa6ifu.d.cts} +26 -6
  68. package/dist/serverExecuteTypes-DVRa6ifu.d.cts.map +1 -0
  69. package/dist/{serverExecuteTypes-jyr5aO5q.d.ts → serverExecuteTypes-DlYZCp1f.d.ts} +26 -6
  70. package/dist/serverExecuteTypes-DlYZCp1f.d.ts.map +1 -0
  71. package/dist/{serverQueryTypes-DPRP61yA.d.ts → serverQueryTypes-BFdwq0Z2.d.ts} +14 -4
  72. package/dist/serverQueryTypes-BFdwq0Z2.d.ts.map +1 -0
  73. package/dist/{serverQueryTypes-H_mI6fPl.d.cts → serverQueryTypes-DNdMpmDr.d.cts} +14 -4
  74. package/dist/serverQueryTypes-DNdMpmDr.d.cts.map +1 -0
  75. package/dist/{serverRequestTypes-BY0f5woc.d.ts → serverRequestTypes-BNA7FCY6.d.ts} +20 -3
  76. package/dist/serverRequestTypes-BNA7FCY6.d.ts.map +1 -0
  77. package/dist/{serverRequestTypes-DGGqQMK3.d.cts → serverRequestTypes-CxDRFKfv.d.cts} +20 -3
  78. package/dist/serverRequestTypes-CxDRFKfv.d.cts.map +1 -0
  79. package/dist/{serverResponseGuards-BGHTBbtz.d.ts → serverResponseGuards-BU1Xv_tG.d.ts} +2 -2
  80. package/dist/{serverResponseGuards-BGHTBbtz.d.ts.map → serverResponseGuards-BU1Xv_tG.d.ts.map} +1 -1
  81. package/dist/{serverResponseGuards-DGhrUL_c.d.cts → serverResponseGuards-Dkdok0Sm.d.cts} +2 -2
  82. package/dist/{serverResponseGuards-DGhrUL_c.d.cts.map → serverResponseGuards-Dkdok0Sm.d.cts.map} +1 -1
  83. package/dist/signing/buildSignedMobileRequest.d.cts +1 -1
  84. package/dist/signing/buildSignedMobileRequest.d.ts +1 -1
  85. package/dist/signing/canonicalize.cjs +5 -0
  86. package/dist/signing/canonicalize.cjs.map +1 -1
  87. package/dist/signing/canonicalize.d.cts +1 -1
  88. package/dist/signing/canonicalize.d.ts +1 -1
  89. package/dist/signing/canonicalize.js +5 -0
  90. package/dist/signing/canonicalize.js.map +1 -1
  91. package/dist/signing/eip712MethodByType.cjs +2 -1
  92. package/dist/signing/eip712MethodByType.cjs.map +1 -1
  93. package/dist/signing/eip712MethodByType.d.cts +1 -1
  94. package/dist/signing/eip712MethodByType.d.ts +1 -1
  95. package/dist/signing/eip712MethodByType.js +2 -1
  96. package/dist/signing/eip712MethodByType.js.map +1 -1
  97. package/dist/signing/index.d.cts +5 -5
  98. package/dist/signing/index.d.ts +5 -5
  99. package/dist/signing/payloadHash.d.cts +1 -1
  100. package/dist/signing/payloadHash.d.ts +1 -1
  101. package/dist/signing/types.d.cts +1 -1
  102. package/dist/signing/types.d.ts +1 -1
  103. package/dist/types/MobileServerFailureError.d.cts +1 -1
  104. package/dist/types/MobileServerFailureError.d.ts +1 -1
  105. package/dist/types/clientTypes.d.cts +2 -2
  106. package/dist/types/clientTypes.d.ts +2 -2
  107. package/dist/types/index.d.cts +8 -8
  108. package/dist/types/index.d.ts +8 -8
  109. package/dist/types/mobileErrorCodes.cjs +12 -0
  110. package/dist/types/mobileErrorCodes.cjs.map +1 -1
  111. package/dist/types/mobileErrorCodes.d.cts +1 -1
  112. package/dist/types/mobileErrorCodes.d.ts +1 -1
  113. package/dist/types/mobileErrorCodes.js +12 -0
  114. package/dist/types/mobileErrorCodes.js.map +1 -1
  115. package/dist/types/serverBaseTypes.d.cts +1 -1
  116. package/dist/types/serverBaseTypes.d.ts +1 -1
  117. package/dist/types/serverExecuteTypes.d.cts +2 -2
  118. package/dist/types/serverExecuteTypes.d.ts +2 -2
  119. package/dist/types/serverQueryTypes.d.cts +2 -2
  120. package/dist/types/serverQueryTypes.d.ts +2 -2
  121. package/dist/types/serverRequestTypes.d.cts +2 -2
  122. package/dist/types/serverRequestTypes.d.ts +2 -2
  123. package/dist/{types-l47aSWtA.d.cts → types-BCr2G5gS.d.cts} +6 -2
  124. package/dist/types-BCr2G5gS.d.cts.map +1 -0
  125. package/dist/{types-B021odcS.d.ts → types-C18k-9Vy.d.ts} +6 -2
  126. package/dist/types-C18k-9Vy.d.ts.map +1 -0
  127. package/dist/utils/follow.cjs +6 -0
  128. package/dist/utils/follow.cjs.map +1 -1
  129. package/dist/utils/follow.d.cts +2 -2
  130. package/dist/utils/follow.d.ts +2 -2
  131. package/dist/utils/follow.js +6 -1
  132. package/dist/utils/follow.js.map +1 -1
  133. package/dist/utils/index.cjs +5 -0
  134. package/dist/utils/index.d.cts +5 -4
  135. package/dist/utils/index.d.ts +5 -4
  136. package/dist/utils/index.js +4 -3
  137. package/dist/utils/patterns.cjs +6 -0
  138. package/dist/utils/patterns.cjs.map +1 -1
  139. package/dist/utils/patterns.d.cts +2 -2
  140. package/dist/utils/patterns.d.ts +2 -2
  141. package/dist/utils/patterns.js +6 -1
  142. package/dist/utils/patterns.js.map +1 -1
  143. package/dist/utils/profileSearch.cjs +17 -0
  144. package/dist/utils/profileSearch.cjs.map +1 -0
  145. package/dist/utils/profileSearch.d.cts +2 -0
  146. package/dist/utils/profileSearch.d.ts +2 -0
  147. package/dist/utils/profileSearch.js +15 -0
  148. package/dist/utils/profileSearch.js.map +1 -0
  149. package/dist/utils/serverResponseGuards.d.cts +1 -1
  150. package/dist/utils/serverResponseGuards.d.ts +1 -1
  151. package/package.json +3 -3
  152. package/src/MobileClient.ts +65 -0
  153. package/src/dataMappers.ts +48 -0
  154. package/src/signing/canonicalize.ts +6 -0
  155. package/src/signing/eip712MethodByType.ts +1 -0
  156. package/src/signing/signing.test.ts +34 -0
  157. package/src/signing/types.ts +7 -0
  158. package/src/types/clientTypes.ts +65 -0
  159. package/src/types/mobileErrorCodes.ts +12 -0
  160. package/src/types/serverExecuteTypes.ts +24 -2
  161. package/src/types/serverQueryTypes.ts +11 -0
  162. package/src/types/serverRequestTypes.ts +18 -0
  163. package/src/utils/follow.ts +6 -0
  164. package/src/utils/index.ts +1 -0
  165. package/src/utils/patterns.ts +7 -0
  166. package/src/utils/profileSearch.ts +11 -0
  167. package/dist/MobileClient-DjNpN7Dw.cjs.map +0 -1
  168. package/dist/canonicalize-Cx3lCVpm.d.ts.map +0 -1
  169. package/dist/canonicalize-DNgB8Jsm.d.cts.map +0 -1
  170. package/dist/clientTypes-DnPE1zZO.d.cts.map +0 -1
  171. package/dist/clientTypes-ws9CDHw4.d.ts.map +0 -1
  172. package/dist/eip712MethodByType-Bmu9PYx6.d.ts.map +0 -1
  173. package/dist/eip712MethodByType-T7dfjmC_.d.cts.map +0 -1
  174. package/dist/mobileErrorCodes-B3kTLit_.d.cts.map +0 -1
  175. package/dist/mobileErrorCodes-B3kTLit_.d.ts.map +0 -1
  176. package/dist/patterns-BE0Audp5.d.cts +0 -8
  177. package/dist/patterns-BE0Audp5.d.cts.map +0 -1
  178. package/dist/patterns-BE0Audp5.d.ts +0 -8
  179. package/dist/patterns-BE0Audp5.d.ts.map +0 -1
  180. package/dist/payloadHash-Brc5ceFq.d.cts.map +0 -1
  181. package/dist/payloadHash-cZgv7NSo.d.ts.map +0 -1
  182. package/dist/serverExecuteTypes-CgjzwBaA.d.cts.map +0 -1
  183. package/dist/serverExecuteTypes-jyr5aO5q.d.ts.map +0 -1
  184. package/dist/serverQueryTypes-DPRP61yA.d.ts.map +0 -1
  185. package/dist/serverQueryTypes-H_mI6fPl.d.cts.map +0 -1
  186. package/dist/serverRequestTypes-BY0f5woc.d.ts.map +0 -1
  187. package/dist/serverRequestTypes-DGGqQMK3.d.cts.map +0 -1
  188. package/dist/types-B021odcS.d.ts.map +0 -1
  189. package/dist/types-l47aSWtA.d.cts.map +0 -1
@@ -0,0 +1,2 @@
1
+ import { n as MOBILE_PROFILE_SEARCH_MAX_PAGE_SIZE, t as MOBILE_PROFILE_SEARCH_MAX_ENRICHED_PAGE_SIZE } from "../profileSearch-DCuB-IKD.js";
2
+ export { MOBILE_PROFILE_SEARCH_MAX_ENRICHED_PAGE_SIZE, MOBILE_PROFILE_SEARCH_MAX_PAGE_SIZE };
@@ -0,0 +1,15 @@
1
+ //#region src/utils/profileSearch.ts
2
+ /**
3
+ * Maximum profile search page size without an `include`. Requests outside 1-50 fail with
4
+ * `INVALID_PROFILE_SEARCH_REQUEST`.
5
+ */
6
+ const MOBILE_PROFILE_SEARCH_MAX_PAGE_SIZE = 50;
7
+ /**
8
+ * Maximum profile search page size when `include` asks for follow data, matching
9
+ * `MOBILE_PROFILES_MAX_BATCH_SIZE`. Requests outside 1-25 fail with `INVALID_PROFILE_SEARCH_REQUEST`.
10
+ */
11
+ const MOBILE_PROFILE_SEARCH_MAX_ENRICHED_PAGE_SIZE = 25;
12
+ //#endregion
13
+ export { MOBILE_PROFILE_SEARCH_MAX_ENRICHED_PAGE_SIZE, MOBILE_PROFILE_SEARCH_MAX_PAGE_SIZE };
14
+
15
+ //# sourceMappingURL=profileSearch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profileSearch.js","names":[],"sources":["../../src/utils/profileSearch.ts"],"sourcesContent":["/**\n * Maximum profile search page size without an `include`. Requests outside 1-50 fail with\n * `INVALID_PROFILE_SEARCH_REQUEST`.\n */\nexport const MOBILE_PROFILE_SEARCH_MAX_PAGE_SIZE = 50;\n\n/**\n * Maximum profile search page size when `include` asks for follow data, matching\n * `MOBILE_PROFILES_MAX_BATCH_SIZE`. Requests outside 1-25 fail with `INVALID_PROFILE_SEARCH_REQUEST`.\n */\nexport const MOBILE_PROFILE_SEARCH_MAX_ENRICHED_PAGE_SIZE = 25;\n"],"mappings":";;;;;AAIA,MAAa,sCAAsC;;;;;AAMnD,MAAa,+CAA+C"}
@@ -1,2 +1,2 @@
1
- import { n as isMobileServerSuccessResponse, t as isMobileServerFailureResponse } from "../serverResponseGuards-DGhrUL_c.cjs";
1
+ import { n as isMobileServerSuccessResponse, t as isMobileServerFailureResponse } from "../serverResponseGuards-Dkdok0Sm.cjs";
2
2
  export { isMobileServerFailureResponse, isMobileServerSuccessResponse };
@@ -1,2 +1,2 @@
1
- import { n as isMobileServerSuccessResponse, t as isMobileServerFailureResponse } from "../serverResponseGuards-BGHTBbtz.js";
1
+ import { n as isMobileServerSuccessResponse, t as isMobileServerFailureResponse } from "../serverResponseGuards-BU1Xv_tG.js";
2
2
  export { isMobileServerFailureResponse, isMobileServerSuccessResponse };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nadohq/mobile-client",
3
- "version": "0.57.0",
3
+ "version": "0.59.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "HTTP client for the Nado mobile service API — username claims, profiles, privacy settings, and push notification devices & preferences",
@@ -42,7 +42,7 @@
42
42
  "types": "./dist/index.d.ts",
43
43
  "dependencies": {
44
44
  "@msgpack/msgpack": "3.1.3",
45
- "@nadohq/shared": "^0.57.0",
45
+ "@nadohq/shared": "^0.59.0",
46
46
  "axios": "1.16.1"
47
47
  },
48
48
  "peerDependencies": {
@@ -54,5 +54,5 @@
54
54
  "bignumber.js": "11.1.1",
55
55
  "viem": "2.52.0"
56
56
  },
57
- "gitHead": "7cb8102ccb2b3632cad3a8c44c3bbe6c7a081ae6"
57
+ "gitHead": "48af19e135f200eb240e406fdbdb26632970492b"
58
58
  }
@@ -10,11 +10,14 @@ import {
10
10
  mapMobileAvatarSelectionToServer,
11
11
  mapMobileAvatarUpload,
12
12
  mapMobileFeedPage,
13
+ mapMobileFollowBatchResult,
13
14
  mapMobileFollowListPage,
14
15
  mapMobileFollowListParamsToServer,
15
16
  mapMobileFollowMutationResult,
16
17
  mapMobileNotificationPreferences,
17
18
  mapMobileNotificationPreferencesToServer,
19
+ mapMobileProfileSearchPage,
20
+ mapMobileProfileSearchParamsToServer,
18
21
  mapMobileProfilesIncludeToServer,
19
22
  mapMobilePublicProfile,
20
23
  mapMobileRegisteredWallet,
@@ -53,14 +56,17 @@ import {
53
56
  MobileAvatarUpload,
54
57
  MobileCreateAvatarUploadParams,
55
58
  MobileFeedPage,
59
+ MobileFollowBatchResultItem,
56
60
  MobileFollowListPage,
57
61
  MobileFollowMutationResult,
58
62
  MobileNotificationPreferences,
63
+ MobileProfileSearchPage,
59
64
  MobilePublicProfile,
60
65
  MobileRegisteredWallet,
61
66
  MobileRegisterExpoTokenParams,
62
67
  MobileSetAvatarParams,
63
68
  MobileSetBioParams,
69
+ MobileSetFollowBatchParams,
64
70
  MobileSetFollowParams,
65
71
  MobileSetPrivateModeParams,
66
72
  MobileSetUsernameParams,
@@ -73,6 +79,7 @@ import {
73
79
  MobileUnregisterExpoTokenParams,
74
80
  MobileUpdateNotificationPreferencesParams,
75
81
  MobileUsernameAvailability,
82
+ SearchMobileProfilesParams,
76
83
  } from './types/clientTypes';
77
84
  import { MobileServerFailureError } from './types/MobileServerFailureError';
78
85
  import { MobileServerSuccessResponse } from './types/serverBaseTypes';
@@ -206,6 +213,33 @@ export class MobileClient {
206
213
  return data.profiles.map(mapMobilePublicProfile);
207
214
  }
208
215
 
216
+ /**
217
+ * Searches claimed profiles by the start of their username, in ascending username order with an exact
218
+ * match first. Only current claimed identities match: an unnamed profile never does, and Private Mode does
219
+ * not hide one. Takes the same `include` as {@link MobileClient.getProfiles}, so a page can carry follower
220
+ * totals and a follow summary without a second request; as there, `include.followSummary.viewAs` is an
221
+ * unauthenticated claim on an unsigned route.
222
+ *
223
+ * Pagination is live rather than snapshot, so deduplicate matches by subaccount across pages. A cursor is
224
+ * bound to the query alone, so `include` and `limit` may change between pages.
225
+ *
226
+ * @throws {MobileServerFailureError} With error code `INVALID_PROFILE_SEARCH_REQUEST` if `query` is outside
227
+ * the username alphabet or length or `limit` is out of range (1-50, or 1-25 with an include),
228
+ * `INVALID_PROFILE_SEARCH_CURSOR` if the cursor is malformed or was issued for a different query (discard
229
+ * it and restart from the first page), or `PROFILE_NOT_FOUND` if `include.followSummary.viewAs` is in the
230
+ * engine-created isolated namespace.
231
+ */
232
+ async searchProfiles(
233
+ params: SearchMobileProfilesParams,
234
+ ): Promise<MobileProfileSearchPage> {
235
+ const body: MobileServerPublicQueryRequest<'search_profiles'> = {
236
+ type: 'search_profiles',
237
+ ...mapMobileProfileSearchParamsToServer(params),
238
+ };
239
+ const data = await this.publicQuery(body);
240
+ return mapMobileProfileSearchPage(data);
241
+ }
242
+
209
243
  /**
210
244
  * Fetches a page of the accounts that follow the given Profile.
211
245
  *
@@ -675,6 +709,37 @@ export class MobileClient {
675
709
  return mapMobileFollowMutationResult(data);
676
710
  }
677
711
 
712
+ /**
713
+ * Sets whether the signing subaccount follows each of up to `MOBILE_FOLLOW_BATCH_MAX_SIZE` (25) Profiles in
714
+ * one signed request, which costs far less of the shared IP rate limit than one {@link MobileClient.setFollow}
715
+ * per target. The server validates the whole list before writing and commits every target in one database
716
+ * transaction, so the batch either fully applies or changes nothing. Each target has the same idempotent
717
+ * semantics as `setFollow`, so resending a batch is safe.
718
+ *
719
+ * Targets are lowercased before signing but not deduplicated, and two spellings of one subaccount are a
720
+ * duplicate. Longer lists must be split by the caller; each request commits on its own, so earlier chunks
721
+ * stay applied when a later one fails.
722
+ *
723
+ * @returns One result per target, in request order, carrying each target's post-commit follower count.
724
+ * @throws {MobileServerFailureError} With error code `INVALID_FOLLOW_BATCH` if `targets` is empty, holds
725
+ * duplicates, or exceeds the maximum, or `INVALID_FOLLOW_TARGET` if any target is the sender or either party
726
+ * is isolated. Either rejects the whole batch before any write, so do not retry it unchanged.
727
+ */
728
+ async setFollowBatch(
729
+ params: MobileSetFollowBatchParams,
730
+ ): Promise<MobileFollowBatchResultItem[]> {
731
+ const signedRequest = await this.getSignedRequest(
732
+ 'set_follow_batch',
733
+ params,
734
+ {
735
+ subaccounts: params.targets.map(subaccountToHex),
736
+ is_following: params.isFollowing,
737
+ },
738
+ );
739
+ const data = await this.execute(signedRequest);
740
+ return mapMobileFollowBatchResult(data);
741
+ }
742
+
678
743
  /*
679
744
  Base Fns
680
745
  */
@@ -6,12 +6,14 @@ import {
6
6
  MobileAvatarUpload,
7
7
  MobileFeedPage,
8
8
  MobileFeedTrade,
9
+ MobileFollowBatchResultItem,
9
10
  MobileFollowListPage,
10
11
  MobileFollowMutationResult,
11
12
  MobileFollowSummary,
12
13
  MobileIdentitySummary,
13
14
  MobileNotificationPreferenceScope,
14
15
  MobileNotificationPreferences,
16
+ MobileProfileSearchPage,
15
17
  MobileProfilesInclude,
16
18
  MobilePublicProfile,
17
19
  MobileRegisteredWallet,
@@ -36,9 +38,11 @@ import {
36
38
  MobileSocialFeedTradeRowBase,
37
39
  MobileSocialFeedTraderBadge,
38
40
  MobileSocialFeedUpdates,
41
+ SearchMobileProfilesParams,
39
42
  } from './types/clientTypes';
40
43
  import {
41
44
  MobileServerAvatarUploadResponse,
45
+ MobileServerFollowBatchResponse,
42
46
  MobileServerFollowMutationResponse,
43
47
  } from './types/serverExecuteTypes';
44
48
  import {
@@ -69,6 +73,7 @@ import {
69
73
  MobileServerFeedResponse,
70
74
  MobileServerFollowListResponse,
71
75
  MobileServerRegisteredWalletResponse,
76
+ MobileServerSearchProfilesResponse,
72
77
  MobileServerSocialFeedItemResponse,
73
78
  MobileServerSocialFeedRankedResponse,
74
79
  MobileServerSocialFeedResponse,
@@ -78,6 +83,7 @@ import {
78
83
  import {
79
84
  MobileServerFollowListRequest,
80
85
  MobileServerProfilesInclude,
86
+ MobileServerSearchProfilesRequest,
81
87
  MobileServerSocialFeedListParams,
82
88
  MobileServerSocialFeedReadParams,
83
89
  } from './types/serverRequestTypes';
@@ -184,6 +190,19 @@ export function mapMobileFollowListPage(
184
190
  };
185
191
  }
186
192
 
193
+ /**
194
+ * Maps a server-side profile search page (snake_case) to its client-side (camelCase) representation. Each
195
+ * match goes through {@link mapMobilePublicProfile}, so include-gated fields stay `undefined` when omitted.
196
+ */
197
+ export function mapMobileProfileSearchPage(
198
+ server: MobileServerSearchProfilesResponse,
199
+ ): MobileProfileSearchPage {
200
+ return {
201
+ profiles: server.profiles.map(mapMobilePublicProfile),
202
+ nextCursor: server.next_cursor,
203
+ };
204
+ }
205
+
187
206
  /**
188
207
  * Maps a server-side follow or unfollow response to its client-side representation.
189
208
  */
@@ -196,6 +215,19 @@ export function mapMobileFollowMutationResult(
196
215
  };
197
216
  }
198
217
 
218
+ /**
219
+ * Maps a server-side batch follow or unfollow response to its client-side representation.
220
+ */
221
+ export function mapMobileFollowBatchResult(
222
+ server: MobileServerFollowBatchResponse,
223
+ ): MobileFollowBatchResultItem[] {
224
+ return server.results.map((result) => ({
225
+ subaccount: result.subaccount,
226
+ isFollowing: result.is_following,
227
+ followerCount: result.follower_count,
228
+ }));
229
+ }
230
+
199
231
  /**
200
232
  * Maps server-side notification preferences (snake_case) to their client-side (camelCase) representation.
201
233
  */
@@ -250,6 +282,22 @@ export function mapMobileFollowListParamsToServer(
250
282
  };
251
283
  }
252
284
 
285
+ /**
286
+ * Maps the client-side params of a profile search page to the server-side wire shape.
287
+ */
288
+ export function mapMobileProfileSearchParamsToServer(
289
+ params: SearchMobileProfilesParams,
290
+ ): MobileServerSearchProfilesRequest {
291
+ return {
292
+ query: params.query,
293
+ include: params.include
294
+ ? mapMobileProfilesIncludeToServer(params.include)
295
+ : undefined,
296
+ cursor: params.cursor,
297
+ limit: params.limit,
298
+ };
299
+ }
300
+
253
301
  /**
254
302
  * Maps client-side notification preferences (camelCase) to the server-side (snake_case) wire shape.
255
303
  */
@@ -49,6 +49,12 @@ export function canonicalizeMobileInner(
49
49
  subaccount: inner.subaccount,
50
50
  is_following: inner.is_following,
51
51
  };
52
+ case 'set_follow_batch':
53
+ return {
54
+ type: 'set_follow_batch',
55
+ subaccounts: [...inner.subaccounts],
56
+ is_following: inner.is_following,
57
+ };
52
58
  default: {
53
59
  throw new Error(
54
60
  `canonicalizeMobileInner: unhandled inner payload type: ${JSON.stringify(inner)}`,
@@ -14,4 +14,5 @@ export const MOBILE_EIP712_METHOD_BY_TYPE: Record<
14
14
  set_private_mode: 'mobile:execute_set_private_mode',
15
15
  register_expo_token: 'mobile:execute_register_expo_token',
16
16
  set_follow: 'mobile:execute_set_follow',
17
+ set_follow_batch: 'mobile:execute_set_follow_batch',
17
18
  };
@@ -41,12 +41,20 @@ const PINNED_HASHES = {
41
41
  '0x1ff8529b7c1a0111313eed98536e591555a3bcab51133776ce30c6ef32b559b3',
42
42
  set_follow_false:
43
43
  '0xec9e9e8f32ac4fcf3015f8b7f20c338623ff85db0488703c7fb688519a46e2e5',
44
+ set_follow_batch_true:
45
+ '0x0d86ef133ad5db8968911d102c48599691dcc40094ace985484c3f7e2cb3a87d',
46
+ set_follow_batch_false:
47
+ '0x02272ef5f6c7f9b315a892240af614f9cbbcb1edc69538db37ae1409549ff2fa',
44
48
  } as const;
45
49
 
46
50
  // Every follow fixture on the backend targets this subaccount.
47
51
  const FIXTURE_TARGET_SUBACCOUNT =
48
52
  '0x1111111111111111111111111111111111111111111111111111111111111111' as const;
49
53
 
54
+ // Batch follow fixtures target the subaccount above, then this one.
55
+ const FIXTURE_SECOND_TARGET_SUBACCOUNT =
56
+ '0x2222222222222222222222222222222222222222222222222222222222222222' as const;
57
+
50
58
  describe('[mobile-client]: signing (offline)', () => {
51
59
  describe('pinned payload hashes', () => {
52
60
  it('set_bio', () => {
@@ -145,6 +153,32 @@ describe('[mobile-client]: signing (offline)', () => {
145
153
  const hash = getMobilePayloadHash(canonicalizeMobileInner(inner));
146
154
  expect(hash).toBe(PINNED_HASHES.set_follow_false);
147
155
  });
156
+
157
+ it('set_follow_batch (follow)', () => {
158
+ const inner: MobileSignedInner = {
159
+ type: 'set_follow_batch',
160
+ subaccounts: [
161
+ FIXTURE_TARGET_SUBACCOUNT,
162
+ FIXTURE_SECOND_TARGET_SUBACCOUNT,
163
+ ],
164
+ is_following: true,
165
+ };
166
+ const hash = getMobilePayloadHash(canonicalizeMobileInner(inner));
167
+ expect(hash).toBe(PINNED_HASHES.set_follow_batch_true);
168
+ });
169
+
170
+ it('set_follow_batch (unfollow)', () => {
171
+ const inner: MobileSignedInner = {
172
+ type: 'set_follow_batch',
173
+ subaccounts: [
174
+ FIXTURE_TARGET_SUBACCOUNT,
175
+ FIXTURE_SECOND_TARGET_SUBACCOUNT,
176
+ ],
177
+ is_following: false,
178
+ };
179
+ const hash = getMobilePayloadHash(canonicalizeMobileInner(inner));
180
+ expect(hash).toBe(PINNED_HASHES.set_follow_batch_false);
181
+ });
148
182
  });
149
183
 
150
184
  it('canonicalizes nested avatar fields before hashing', () => {
@@ -43,6 +43,13 @@ export type MobileSignedInner =
43
43
  subaccount: Hex;
44
44
  /** `true` follows the target, `false` unfollows it. Both directions are idempotent. */
45
45
  is_following: boolean;
46
+ }
47
+ | {
48
+ type: 'set_follow_batch';
49
+ /** Distinct targets in request order; the response keeps this order. */
50
+ subaccounts: Hex[];
51
+ /** `true` follows every target, `false` unfollows every target. Both directions are idempotent. */
52
+ is_following: boolean;
46
53
  };
47
54
 
48
55
  /**
@@ -149,6 +149,18 @@ export interface MobileFollowListPage {
149
149
  nextCursor: string | null;
150
150
  }
151
151
 
152
+ /**
153
+ * A page of profiles whose username starts with the searched prefix. Pagination is live rather than snapshot:
154
+ * a claim or rename between pages can surface a profile twice or skip it, so deduplicate by
155
+ * {@link MobilePublicProfile.subaccount} and restart from `cursor: undefined` on refresh.
156
+ */
157
+ export interface MobileProfileSearchPage {
158
+ /** Matches in ascending bytewise username order, with an exact match ahead of longer ones. */
159
+ profiles: MobilePublicProfile[];
160
+ /** `null` means there are no further matches. */
161
+ nextCursor: string | null;
162
+ }
163
+
152
164
  /**
153
165
  * Post-commit state of a follow or unfollow. The mutation and its count share one database transaction, so
154
166
  * these values are authoritative — apply them rather than re-reading.
@@ -159,6 +171,18 @@ export interface MobileFollowMutationResult {
159
171
  followerCount: number;
160
172
  }
161
173
 
174
+ /**
175
+ * Post-commit state of one target in a batch follow or unfollow. The whole batch commits in one database
176
+ * transaction, so these values are authoritative — apply them rather than re-reading.
177
+ */
178
+ export interface MobileFollowBatchResultItem {
179
+ /** The requested target, as lowercase hex. */
180
+ subaccount: Hex;
181
+ isFollowing: boolean;
182
+ /** The *target's* follower count after the batch, not the sender's. */
183
+ followerCount: number;
184
+ }
185
+
162
186
  /**
163
187
  * Result of a username availability check.
164
188
  */
@@ -312,6 +336,33 @@ export interface GetMobileProfilesParams {
312
336
  include?: MobileProfilesInclude;
313
337
  }
314
338
 
339
+ /**
340
+ * Params for {@link MobileClient.searchProfiles}.
341
+ */
342
+ export interface SearchMobileProfilesParams {
343
+ /**
344
+ * Username prefix to match, 1-20 ASCII characters matching `MOBILE_PROFILE_SEARCH_QUERY_PATTERN`: a letter
345
+ * or digit first, then letters, digits, `_` or `.`. Letters match case-insensitively; whitespace is not
346
+ * trimmed, so normalize the input before sending it.
347
+ */
348
+ query: string;
349
+ /** Same opt-in extras as {@link GetMobileProfilesParams.include}, applied to every match on the page. */
350
+ include?: MobileProfilesInclude;
351
+ /**
352
+ * Opaque cursor from the previous page's {@link MobileProfileSearchPage.nextCursor}. Omit for the first
353
+ * page. A cursor is bound to its normalized query but not to `include` or `limit`, so an enriched page
354
+ * can continue an unenriched search; one issued for another query fails with
355
+ * `INVALID_PROFILE_SEARCH_CURSOR`.
356
+ */
357
+ cursor?: string;
358
+ /**
359
+ * Page size, 1-`MOBILE_PROFILE_SEARCH_MAX_PAGE_SIZE` (50), or
360
+ * 1-`MOBILE_PROFILE_SEARCH_MAX_ENRICHED_PAGE_SIZE` (25) when `include` asks for follow data; the backend
361
+ * defaults to 25 when omitted.
362
+ */
363
+ limit?: number;
364
+ }
365
+
315
366
  /**
316
367
  * Params for {@link MobileClient.getFeed}.
317
368
  */
@@ -380,6 +431,20 @@ export interface MobileSetFollowParams extends MobileSignedRequestParams {
380
431
  isFollowing: boolean;
381
432
  }
382
433
 
434
+ /**
435
+ * Params for {@link MobileClient.setFollowBatch}. The subaccount carried by {@link MobileSignedRequestParams}
436
+ * is the signing Follower; `targets` are the Profiles on the other end of each relationship.
437
+ */
438
+ export interface MobileSetFollowBatchParams extends MobileSignedRequestParams {
439
+ /**
440
+ * 1 to `MOBILE_FOLLOW_BATCH_MAX_SIZE` (25) distinct Profiles, none of them the signing subaccount or an
441
+ * isolated subaccount. The results keep this order.
442
+ */
443
+ targets: Subaccount[];
444
+ /** `true` follows every target, `false` unfollows every target. Both directions are idempotent. */
445
+ isFollowing: boolean;
446
+ }
447
+
383
448
  /**
384
449
  * Common params for a Followers or Following page.
385
450
  */
@@ -15,6 +15,16 @@ export const MOBILE_ERROR_CODES = {
15
15
  PROFILE_NOT_FOUND: 6106,
16
16
  /** Profiles lookup is empty, holds duplicates, or exceeds the batch maximum. Do not retry unchanged. */
17
17
  INVALID_PROFILES_REQUEST: 6107,
18
+ /**
19
+ * Profile search query is outside the username alphabet or length, or its limit is out of range. Do not
20
+ * retry unchanged.
21
+ */
22
+ INVALID_PROFILE_SEARCH_REQUEST: 6108,
23
+ /**
24
+ * Profile search cursor is malformed, oversized, or was issued for a different query. Restart from
25
+ * `cursor: undefined`.
26
+ */
27
+ INVALID_PROFILE_SEARCH_CURSOR: 6109,
18
28
  /** Follow mutation names the same source and target, or an isolated participant. Do not retry unchanged. */
19
29
  INVALID_FOLLOW_TARGET: 6110,
20
30
  /** Signed follower is not yet in the canonical Query DB subaccount index. Retry after index catch-up. */
@@ -29,6 +39,8 @@ export const MOBILE_ERROR_CODES = {
29
39
  INVALID_BIO: 6115,
30
40
  /** Invalid preset, upload size, staging key, or image. Correct the selection before retrying. */
31
41
  INVALID_AVATAR: 6116,
42
+ /** Follow batch is empty, holds duplicates, or exceeds the batch maximum. Do not retry unchanged. */
43
+ INVALID_FOLLOW_BATCH: 6117,
32
44
  INVALID_EXPO_TOKEN: 6200,
33
45
  INVALID_DEVICE_METADATA: 6201,
34
46
  INVALID_PREFERENCES: 6202,
@@ -1,3 +1,4 @@
1
+ import { Hex } from 'viem';
1
2
  import {
2
3
  MobileServerFailureResponse,
3
4
  MobileServerSuccessResponse,
@@ -32,6 +33,26 @@ export interface MobileServerFollowMutationResponse {
32
33
  follower_count: number;
33
34
  }
34
35
 
36
+ /**
37
+ * One target's post-commit state in a `set_follow_batch` response.
38
+ */
39
+ export interface MobileServerFollowBatchResultItem {
40
+ /** The requested target, as lowercase hex. */
41
+ subaccount: Hex;
42
+ is_following: boolean;
43
+ /** The *target's* follower count after the batch, not the sender's. */
44
+ follower_count: number;
45
+ }
46
+
47
+ /**
48
+ * Payload the `set_follow_batch` execute returns alongside the success envelope: one result per target, in
49
+ * request order. Every write and count runs in one database transaction, so the batch either fully applies or
50
+ * changes nothing, and these values are authoritative post-commit state.
51
+ */
52
+ export interface MobileServerFollowBatchResponse {
53
+ results: MobileServerFollowBatchResultItem[];
54
+ }
55
+
35
56
  /**
36
57
  * Payload of an execute route that returns nothing beyond the success discriminant. Spelled `Record<never,
37
58
  * never>` rather than `Record<string, never>` because the latter's index signature would collapse `status` to
@@ -55,8 +76,8 @@ export interface MobileServerAvatarUploadResponse {
55
76
 
56
77
  /**
57
78
  * Success payloads for each signed `execute`, keyed by request `type`. Most execute routes carry no payload
58
- * beyond the success discriminant and so map to an empty object; `set_follow` returns its post-commit
59
- * relationship state, and `create_avatar_upload` returns a temporary upload grant.
79
+ * beyond the success discriminant and so map to an empty object; `set_follow` and `set_follow_batch` return
80
+ * their post-commit relationship state, and `create_avatar_upload` returns a temporary upload grant.
60
81
  */
61
82
  export interface MobileServerExecuteResponseByType {
62
83
  /** Bio writes return only the success envelope. */
@@ -69,6 +90,7 @@ export interface MobileServerExecuteResponseByType {
69
90
  set_private_mode: MobileServerEmptyExecuteResponse;
70
91
  register_expo_token: MobileServerEmptyExecuteResponse;
71
92
  set_follow: MobileServerFollowMutationResponse;
93
+ set_follow_batch: MobileServerFollowBatchResponse;
72
94
  }
73
95
 
74
96
  /**
@@ -55,6 +55,16 @@ export interface MobileServerProfilesResponse {
55
55
  profiles: MobileServerProfile[];
56
56
  }
57
57
 
58
+ /**
59
+ * Payload of the `search_profiles` public query success response. Matches come in ascending bytewise
60
+ * username order, an exact match first, and carry the same include-gated fields as a `profiles` entry.
61
+ */
62
+ export interface MobileServerSearchProfilesResponse {
63
+ profiles: MobileServerProfile[];
64
+ /** `null` means there are no further matches. */
65
+ next_cursor: string | null;
66
+ }
67
+
58
68
  /**
59
69
  * Payload of the `followers` and `following` public query success responses. Both list directions share one
60
70
  * shape; only the relationship that selected the rows differs.
@@ -177,6 +187,7 @@ export interface MobileServerPublicQueryResponseByType {
177
187
  feed_updates: MobileServerSocialFeedUpdatesResponse;
178
188
  username_availability: MobileServerUsernameAvailabilityResponse;
179
189
  profiles: MobileServerProfilesResponse;
190
+ search_profiles: MobileServerSearchProfilesResponse;
180
191
  followers: MobileServerFollowListResponse;
181
192
  following: MobileServerFollowListResponse;
182
193
  feed: MobileServerFeedResponse;
@@ -40,6 +40,23 @@ export interface MobileServerFollowListRequest {
40
40
  limit?: number;
41
41
  }
42
42
 
43
+ /**
44
+ * Params of the `search_profiles` public query.
45
+ */
46
+ export interface MobileServerSearchProfilesRequest {
47
+ /**
48
+ * Username prefix, 1-20 ASCII bytes: a letter or digit first, then letters, digits, `_` or `.`. The
49
+ * backend lowercases letters and trims nothing.
50
+ */
51
+ query: string;
52
+ /** Same opt-in extras as the `profiles` query, applied to every match on the page. */
53
+ include?: MobileServerProfilesInclude;
54
+ /** Opaque keyset cursor from a prior page's `next_cursor`. Omitted requests the first page. */
55
+ cursor?: string;
56
+ /** Page size, 1-50, or 1-25 when `include` asks for follow data; the backend defaults to 25 when omitted. */
57
+ limit?: number;
58
+ }
59
+
43
60
  /** Common filters and pagination for social feed lists. */
44
61
  export interface MobileServerSocialFeedListParams {
45
62
  /** OR-ed content filters; combined with other filters using AND. */
@@ -144,6 +161,7 @@ export interface MobileServerPublicQueryRequestByType {
144
161
  subaccounts: Hex[];
145
162
  include?: MobileServerProfilesInclude;
146
163
  };
164
+ search_profiles: MobileServerSearchProfilesRequest;
147
165
  followers: MobileServerFollowListRequest;
148
166
  following: MobileServerFollowListRequest;
149
167
  feed: {
@@ -20,3 +20,9 @@ export const MOBILE_FOLLOWED_BY_PREVIEW_LIMIT = 2;
20
20
  * this many fail with `INVALID_PROFILES_REQUEST`.
21
21
  */
22
22
  export const MOBILE_PROFILES_MAX_BATCH_SIZE = 25;
23
+
24
+ /**
25
+ * Maximum number of targets a single follow batch accepts. An empty list, a duplicate, or more than this many
26
+ * fail with `INVALID_FOLLOW_BATCH`.
27
+ */
28
+ export const MOBILE_FOLLOW_BATCH_MAX_SIZE = 25;
@@ -2,4 +2,5 @@ export * from './feed';
2
2
  export * from './follow';
3
3
  export * from './patterns';
4
4
  export * from './profile';
5
+ export * from './profileSearch';
5
6
  export * from './serverResponseGuards';
@@ -2,3 +2,10 @@
2
2
  * Validates a display name: 3-20 ASCII characters from `[A-Za-z0-9_.]`, allowed in any position.
3
3
  */
4
4
  export const MOBILE_DISPLAY_NAME_PATTERN = /^[A-Za-z0-9_.]{3,20}$/;
5
+
6
+ /**
7
+ * Validates a profile search query: 1-20 ASCII characters, a letter or digit first, then letters, digits, `_`
8
+ * or `.`. The backend lowercases letters and trims nothing, so a prefix shorter than a full username matches.
9
+ */
10
+ export const MOBILE_PROFILE_SEARCH_QUERY_PATTERN =
11
+ /^[A-Za-z0-9][A-Za-z0-9_.]{0,19}$/;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Maximum profile search page size without an `include`. Requests outside 1-50 fail with
3
+ * `INVALID_PROFILE_SEARCH_REQUEST`.
4
+ */
5
+ export const MOBILE_PROFILE_SEARCH_MAX_PAGE_SIZE = 50;
6
+
7
+ /**
8
+ * Maximum profile search page size when `include` asks for follow data, matching
9
+ * `MOBILE_PROFILES_MAX_BATCH_SIZE`. Requests outside 1-25 fail with `INVALID_PROFILE_SEARCH_REQUEST`.
10
+ */
11
+ export const MOBILE_PROFILE_SEARCH_MAX_ENRICHED_PAGE_SIZE = 25;