@nadohq/mobile-client 0.64.0 → 0.65.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 (177) hide show
  1. package/README.md +20 -2
  2. package/dist/{MobileClient-aDxiM_Kf.cjs → MobileClient-CfVZXTcz.cjs} +298 -1
  3. package/dist/MobileClient-CfVZXTcz.cjs.map +1 -0
  4. package/dist/MobileClient.cjs +1 -1
  5. package/dist/MobileClient.d.cts +173 -7
  6. package/dist/MobileClient.d.cts.map +1 -1
  7. package/dist/MobileClient.d.ts +173 -7
  8. package/dist/MobileClient.d.ts.map +1 -1
  9. package/dist/MobileClient.js +298 -1
  10. package/dist/MobileClient.js.map +1 -1
  11. package/dist/{MobileServerFailureError-Dd3c6P_Z.d.ts → MobileServerFailureError-BZVwlsMg.d.ts} +3 -3
  12. package/dist/{MobileServerFailureError-Dd3c6P_Z.d.ts.map → MobileServerFailureError-BZVwlsMg.d.ts.map} +1 -1
  13. package/dist/{MobileServerFailureError-Bpa_WA9f.d.cts → MobileServerFailureError-CbtK8kD4.d.cts} +3 -3
  14. package/dist/{MobileServerFailureError-Bpa_WA9f.d.cts.map → MobileServerFailureError-CbtK8kD4.d.cts.map} +1 -1
  15. package/dist/MobileUnsupportedFeedItemError-BsZvW8i2.d.cts +15 -0
  16. package/dist/MobileUnsupportedFeedItemError-BsZvW8i2.d.cts.map +1 -0
  17. package/dist/MobileUnsupportedFeedItemError-BsZvW8i2.d.ts +15 -0
  18. package/dist/MobileUnsupportedFeedItemError-BsZvW8i2.d.ts.map +1 -0
  19. package/dist/{buildSignedMobileRequest-NHbMAPzd.d.cts → buildSignedMobileRequest-C1v0flCR.d.cts} +2 -2
  20. package/dist/{buildSignedMobileRequest-NHbMAPzd.d.cts.map → buildSignedMobileRequest-C1v0flCR.d.cts.map} +1 -1
  21. package/dist/{buildSignedMobileRequest-Cb1T74xV.d.ts → buildSignedMobileRequest-UeeWdYGU.d.ts} +2 -2
  22. package/dist/{buildSignedMobileRequest-Cb1T74xV.d.ts.map → buildSignedMobileRequest-UeeWdYGU.d.ts.map} +1 -1
  23. package/dist/{canonicalize-BBYGh17w.d.ts → canonicalize-5eeZrkzQ.d.ts} +2 -2
  24. package/dist/canonicalize-5eeZrkzQ.d.ts.map +1 -0
  25. package/dist/{canonicalize-BF_SiNvO.d.cts → canonicalize-CBaZprTo.d.cts} +2 -2
  26. package/dist/canonicalize-CBaZprTo.d.cts.map +1 -0
  27. package/dist/{clientTypes-BWFKo9oE.d.cts → clientTypes-C9dJufxk.d.ts} +569 -25
  28. package/dist/clientTypes-C9dJufxk.d.ts.map +1 -0
  29. package/dist/{clientTypes-DCTiUV0H.d.ts → clientTypes-_mEgzmNs.d.cts} +569 -25
  30. package/dist/clientTypes-_mEgzmNs.d.cts.map +1 -0
  31. package/dist/dataMappers.cjs +488 -9
  32. package/dist/dataMappers.cjs.map +1 -1
  33. package/dist/dataMappers.d.cts +190 -7
  34. package/dist/dataMappers.d.cts.map +1 -1
  35. package/dist/dataMappers.d.ts +190 -7
  36. package/dist/dataMappers.d.ts.map +1 -1
  37. package/dist/dataMappers.js +460 -10
  38. package/dist/dataMappers.js.map +1 -1
  39. package/dist/{eip712MethodByType-BSx11xG-.d.cts → eip712MethodByType-D8jLoxDu.d.cts} +2 -2
  40. package/dist/eip712MethodByType-D8jLoxDu.d.cts.map +1 -0
  41. package/dist/{eip712MethodByType-DkiAjCLs.d.ts → eip712MethodByType-DcUh8h-l.d.ts} +2 -2
  42. package/dist/eip712MethodByType-DcUh8h-l.d.ts.map +1 -0
  43. package/dist/index.cjs +3 -1
  44. package/dist/index.d.cts +16 -15
  45. package/dist/index.d.ts +16 -15
  46. package/dist/index.js +2 -1
  47. package/dist/{mobileErrorCodes-3LtreeZM.d.ts → mobileErrorCodes-CmjYEIYm.d.cts} +18 -4
  48. package/dist/mobileErrorCodes-CmjYEIYm.d.cts.map +1 -0
  49. package/dist/{mobileErrorCodes-3LtreeZM.d.cts → mobileErrorCodes-CmjYEIYm.d.ts} +18 -4
  50. package/dist/mobileErrorCodes-CmjYEIYm.d.ts.map +1 -0
  51. package/dist/{payloadHash-BOWGjqdb.d.cts → payloadHash-DhHmgRqL.d.cts} +2 -2
  52. package/dist/payloadHash-DhHmgRqL.d.cts.map +1 -0
  53. package/dist/{payloadHash-Bzf4CtjV.d.ts → payloadHash-E1_8fpLW.d.ts} +2 -2
  54. package/dist/payloadHash-E1_8fpLW.d.ts.map +1 -0
  55. package/dist/{serverBaseTypes-KREDe2M9.d.cts → serverBaseTypes-B5J1_TyC.d.cts} +2 -2
  56. package/dist/{serverBaseTypes-KREDe2M9.d.cts.map → serverBaseTypes-B5J1_TyC.d.cts.map} +1 -1
  57. package/dist/{serverBaseTypes-DWf3iGWy.d.ts → serverBaseTypes-DnLzsdm-.d.ts} +2 -2
  58. package/dist/{serverBaseTypes-DWf3iGWy.d.ts.map → serverBaseTypes-DnLzsdm-.d.ts.map} +1 -1
  59. package/dist/serverExecuteTypes-B-OJok19.d.cts +162 -0
  60. package/dist/serverExecuteTypes-B-OJok19.d.cts.map +1 -0
  61. package/dist/serverExecuteTypes-BwWGspOg.d.ts +162 -0
  62. package/dist/serverExecuteTypes-BwWGspOg.d.ts.map +1 -0
  63. package/dist/{serverModelTypes-DAqEyYtB.d.cts → serverModelTypes-CiA-dy1R.d.cts} +299 -24
  64. package/dist/serverModelTypes-CiA-dy1R.d.cts.map +1 -0
  65. package/dist/{serverModelTypes-DAqEyYtB.d.ts → serverModelTypes-CiA-dy1R.d.ts} +299 -24
  66. package/dist/serverModelTypes-CiA-dy1R.d.ts.map +1 -0
  67. package/dist/{serverQueryTypes-DNdMpmDr.d.cts → serverQueryTypes-CJcjLB7n.d.ts} +117 -5
  68. package/dist/serverQueryTypes-CJcjLB7n.d.ts.map +1 -0
  69. package/dist/{serverQueryTypes-BFdwq0Z2.d.ts → serverQueryTypes-V1f9yE7L.d.cts} +117 -5
  70. package/dist/serverQueryTypes-V1f9yE7L.d.cts.map +1 -0
  71. package/dist/{serverRequestTypes-CxDRFKfv.d.cts → serverRequestTypes-CRlkNSu5.d.cts} +77 -7
  72. package/dist/serverRequestTypes-CRlkNSu5.d.cts.map +1 -0
  73. package/dist/{serverRequestTypes-BNA7FCY6.d.ts → serverRequestTypes-DZoOhV2o.d.ts} +77 -7
  74. package/dist/serverRequestTypes-DZoOhV2o.d.ts.map +1 -0
  75. package/dist/{serverResponseGuards-Dkdok0Sm.d.cts → serverResponseGuards-BW69PS7O.d.cts} +2 -2
  76. package/dist/{serverResponseGuards-Dkdok0Sm.d.cts.map → serverResponseGuards-BW69PS7O.d.cts.map} +1 -1
  77. package/dist/{serverResponseGuards-BU1Xv_tG.d.ts → serverResponseGuards-CShiXpHT.d.ts} +2 -2
  78. package/dist/{serverResponseGuards-BU1Xv_tG.d.ts.map → serverResponseGuards-CShiXpHT.d.ts.map} +1 -1
  79. package/dist/signing/buildSignedMobileRequest.d.cts +1 -1
  80. package/dist/signing/buildSignedMobileRequest.d.ts +1 -1
  81. package/dist/signing/canonicalize.cjs +37 -0
  82. package/dist/signing/canonicalize.cjs.map +1 -1
  83. package/dist/signing/canonicalize.d.cts +1 -1
  84. package/dist/signing/canonicalize.d.ts +1 -1
  85. package/dist/signing/canonicalize.js +37 -0
  86. package/dist/signing/canonicalize.js.map +1 -1
  87. package/dist/signing/eip712MethodByType.cjs +5 -0
  88. package/dist/signing/eip712MethodByType.cjs.map +1 -1
  89. package/dist/signing/eip712MethodByType.d.cts +1 -1
  90. package/dist/signing/eip712MethodByType.d.ts +1 -1
  91. package/dist/signing/eip712MethodByType.js +5 -0
  92. package/dist/signing/eip712MethodByType.js.map +1 -1
  93. package/dist/signing/index.d.cts +5 -5
  94. package/dist/signing/index.d.ts +5 -5
  95. package/dist/signing/payloadHash.d.cts +1 -1
  96. package/dist/signing/payloadHash.d.ts +1 -1
  97. package/dist/signing/types.d.cts +1 -1
  98. package/dist/signing/types.d.ts +1 -1
  99. package/dist/types/MobileServerFailureError.d.cts +1 -1
  100. package/dist/types/MobileServerFailureError.d.ts +1 -1
  101. package/dist/types/MobileUnsupportedFeedItemError.cjs +19 -0
  102. package/dist/types/MobileUnsupportedFeedItemError.cjs.map +1 -0
  103. package/dist/types/MobileUnsupportedFeedItemError.d.cts +2 -0
  104. package/dist/types/MobileUnsupportedFeedItemError.d.ts +2 -0
  105. package/dist/types/MobileUnsupportedFeedItemError.js +18 -0
  106. package/dist/types/MobileUnsupportedFeedItemError.js.map +1 -0
  107. package/dist/types/clientTypes.d.cts +2 -2
  108. package/dist/types/clientTypes.d.ts +2 -2
  109. package/dist/types/index.cjs +2 -0
  110. package/dist/types/index.d.cts +10 -9
  111. package/dist/types/index.d.ts +10 -9
  112. package/dist/types/index.js +2 -1
  113. package/dist/types/mobileErrorCodes.cjs +28 -0
  114. package/dist/types/mobileErrorCodes.cjs.map +1 -1
  115. package/dist/types/mobileErrorCodes.d.cts +1 -1
  116. package/dist/types/mobileErrorCodes.d.ts +1 -1
  117. package/dist/types/mobileErrorCodes.js +28 -0
  118. package/dist/types/mobileErrorCodes.js.map +1 -1
  119. package/dist/types/serverBaseTypes.d.cts +1 -1
  120. package/dist/types/serverBaseTypes.d.ts +1 -1
  121. package/dist/types/serverExecuteTypes.d.cts +2 -2
  122. package/dist/types/serverExecuteTypes.d.ts +2 -2
  123. package/dist/types/serverModelTypes.cjs.map +1 -1
  124. package/dist/types/serverModelTypes.d.cts +2 -2
  125. package/dist/types/serverModelTypes.d.ts +2 -2
  126. package/dist/types/serverModelTypes.js.map +1 -1
  127. package/dist/types/serverQueryTypes.d.cts +2 -2
  128. package/dist/types/serverQueryTypes.d.ts +2 -2
  129. package/dist/types/serverRequestTypes.d.cts +1 -1
  130. package/dist/types/serverRequestTypes.d.ts +1 -1
  131. package/dist/{types-BCr2G5gS.d.cts → types-BiRmH5Iv.d.cts} +31 -7
  132. package/dist/types-BiRmH5Iv.d.cts.map +1 -0
  133. package/dist/{types-C18k-9Vy.d.ts → types-DoJuq0Ij.d.ts} +31 -7
  134. package/dist/types-DoJuq0Ij.d.ts.map +1 -0
  135. package/dist/utils/index.d.cts +1 -1
  136. package/dist/utils/index.d.ts +1 -1
  137. package/dist/utils/serverResponseGuards.d.cts +1 -1
  138. package/dist/utils/serverResponseGuards.d.ts +1 -1
  139. package/package.json +3 -3
  140. package/src/MobileClient.ts +409 -4
  141. package/src/dataMappers.test.ts +742 -0
  142. package/src/dataMappers.ts +671 -12
  143. package/src/signing/canonicalize.ts +53 -0
  144. package/src/signing/eip712MethodByType.ts +5 -0
  145. package/src/signing/signing.test.ts +347 -0
  146. package/src/signing/types.ts +57 -4
  147. package/src/types/MobileUnsupportedFeedItemError.ts +18 -0
  148. package/src/types/clientTypes.ts +645 -23
  149. package/src/types/index.ts +1 -0
  150. package/src/types/mobileErrorCodes.ts +28 -0
  151. package/src/types/serverExecuteTypes.ts +92 -3
  152. package/src/types/serverModelTypes.ts +376 -22
  153. package/src/types/serverQueryTypes.ts +138 -0
  154. package/src/types/serverRequestTypes.ts +115 -4
  155. package/dist/MobileClient-aDxiM_Kf.cjs.map +0 -1
  156. package/dist/canonicalize-BBYGh17w.d.ts.map +0 -1
  157. package/dist/canonicalize-BF_SiNvO.d.cts.map +0 -1
  158. package/dist/clientTypes-BWFKo9oE.d.cts.map +0 -1
  159. package/dist/clientTypes-DCTiUV0H.d.ts.map +0 -1
  160. package/dist/eip712MethodByType-BSx11xG-.d.cts.map +0 -1
  161. package/dist/eip712MethodByType-DkiAjCLs.d.ts.map +0 -1
  162. package/dist/mobileErrorCodes-3LtreeZM.d.cts.map +0 -1
  163. package/dist/mobileErrorCodes-3LtreeZM.d.ts.map +0 -1
  164. package/dist/payloadHash-BOWGjqdb.d.cts.map +0 -1
  165. package/dist/payloadHash-Bzf4CtjV.d.ts.map +0 -1
  166. package/dist/serverExecuteTypes-DVRa6ifu.d.cts +0 -89
  167. package/dist/serverExecuteTypes-DVRa6ifu.d.cts.map +0 -1
  168. package/dist/serverExecuteTypes-DlYZCp1f.d.ts +0 -89
  169. package/dist/serverExecuteTypes-DlYZCp1f.d.ts.map +0 -1
  170. package/dist/serverModelTypes-DAqEyYtB.d.cts.map +0 -1
  171. package/dist/serverModelTypes-DAqEyYtB.d.ts.map +0 -1
  172. package/dist/serverQueryTypes-BFdwq0Z2.d.ts.map +0 -1
  173. package/dist/serverQueryTypes-DNdMpmDr.d.cts.map +0 -1
  174. package/dist/serverRequestTypes-BNA7FCY6.d.ts.map +0 -1
  175. package/dist/serverRequestTypes-CxDRFKfv.d.cts.map +0 -1
  176. package/dist/types-BCr2G5gS.d.cts.map +0 -1
  177. package/dist/types-C18k-9Vy.d.ts.map +0 -1
@@ -1,3 +1,4 @@
1
+ import { MobileSocialFeedMention } from '../types/serverModelTypes';
1
2
  import { MobileSignedInner } from './types';
2
3
 
3
4
  /**
@@ -11,6 +12,47 @@ export function canonicalizeMobileInner(
11
12
  inner: MobileSignedInner,
12
13
  ): MobileSignedInner {
13
14
  switch (inner.type) {
15
+ case 'create_feed_post':
16
+ return {
17
+ type: inner.type,
18
+ client_request_id: inner.client_request_id,
19
+ position_id: inner.position_id,
20
+ ...(inner.action_id === undefined
21
+ ? {}
22
+ : { action_id: inner.action_id }),
23
+ text: inner.text,
24
+ mentions: inner.mentions.map(canonicalizeMention),
25
+ ...(inner.drawing_data_json === undefined
26
+ ? {}
27
+ : { drawing_data_json: inner.drawing_data_json }),
28
+ };
29
+ case 'create_feed_comment':
30
+ return {
31
+ type: inner.type,
32
+ client_request_id: inner.client_request_id,
33
+ root_item_id: inner.root_item_id,
34
+ ...(inner.reply_to_item_id === undefined
35
+ ? {}
36
+ : { reply_to_item_id: inner.reply_to_item_id }),
37
+ text: inner.text,
38
+ mentions: inner.mentions.map(canonicalizeMention),
39
+ ...(inner.attached_position_id === undefined
40
+ ? {}
41
+ : { attached_position_id: inner.attached_position_id }),
42
+ };
43
+ case 'create_feed_chart_drawing':
44
+ case 'update_feed_chart_drawing':
45
+ return {
46
+ type: inner.type,
47
+ item_id: inner.item_id,
48
+ data_json: inner.data_json,
49
+ };
50
+ case 'set_feed_like':
51
+ return {
52
+ type: inner.type,
53
+ item_id: inner.item_id,
54
+ is_liked: inner.is_liked,
55
+ };
14
56
  case 'set_bio':
15
57
  return { type: 'set_bio', bio: inner.bio };
16
58
  case 'create_avatar_upload':
@@ -62,3 +104,14 @@ export function canonicalizeMobileInner(
62
104
  }
63
105
  }
64
106
  }
107
+
108
+ function canonicalizeMention(
109
+ mention: MobileSocialFeedMention,
110
+ ): MobileSocialFeedMention {
111
+ return {
112
+ subaccount: mention.subaccount,
113
+ start: mention.start,
114
+ end: mention.end,
115
+ label: mention.label,
116
+ };
117
+ }
@@ -7,6 +7,11 @@ export const MOBILE_EIP712_METHOD_BY_TYPE: Record<
7
7
  MobileSignedInner['type'],
8
8
  string
9
9
  > = {
10
+ create_feed_post: 'mobile:execute_create_feed_post',
11
+ create_feed_comment: 'mobile:execute_create_feed_comment',
12
+ create_feed_chart_drawing: 'mobile:execute_create_feed_chart_drawing',
13
+ update_feed_chart_drawing: 'mobile:execute_update_feed_chart_drawing',
14
+ set_feed_like: 'mobile:execute_set_feed_like',
10
15
  set_bio: 'mobile:execute_set_bio',
11
16
  create_avatar_upload: 'mobile:execute_create_avatar_upload',
12
17
  set_avatar: 'mobile:execute_set_avatar',
@@ -10,6 +10,7 @@ import { privateKeyToAccount } from 'viem/accounts';
10
10
  import { ink } from 'viem/chains';
11
11
  import { buildSignedMobileRequest } from './buildSignedMobileRequest';
12
12
  import { canonicalizeMobileInner } from './canonicalize';
13
+ import { MOBILE_EIP712_METHOD_BY_TYPE } from './eip712MethodByType';
13
14
  import { getMobileNonce } from './nonce';
14
15
  import { getMobilePayloadHash } from './payloadHash';
15
16
  import { MobileSignedInner } from './types';
@@ -22,6 +23,20 @@ const FIXED_VERIFYING_ADDR = '0x0000000000000000000000000000000000000001';
22
23
  // Every hash here mirrors the backend's pinned fixtures in mobile/src/api, so a divergence is a
23
24
  // real wire-format break rather than a stale expectation.
24
25
  const PINNED_HASHES = {
26
+ create_feed_post:
27
+ '0x5066f6cf7834957d8ecd713cd87d81da4ddaeffe863b86464fbad1f511713c39',
28
+ create_feed_chart_drawing:
29
+ '0x8ad450257f13a75e7632345676ce2da0f55feda3aa1908ea26e8cccb36ce80e2',
30
+ update_feed_chart_drawing:
31
+ '0xe1b04cd83027aaca1e075a59003f70801164f68552abf47a957721d7d5249cf7',
32
+ create_feed_comment:
33
+ '0x2ceb8d696c5f634db823f2e43274bdf565c36819b305315a61964208effc0476',
34
+ create_feed_comment_reply:
35
+ '0x82c728da0f1c11301dba141a346ac065bd840ab61b69e0c3aea2ceafa77137ae',
36
+ set_feed_like_true:
37
+ '0xbf58b6299a44bfe0a7442cb3920e2f1476eb68d00b137a7d63ade73deb4aded0',
38
+ set_feed_like_false:
39
+ '0xed313b08252b37e58f45dff89afe7e2ab2706a5218db15da7e1d77f2eb43aae5',
25
40
  set_bio: '0x96206cad01c9b00d69412721842ca0866bb1a82ec90f3979b8bcc91dc96a1243',
26
41
  create_avatar_upload:
27
42
  '0xb3811340e751e2799d3a24467fa50aeb221e1ecea5aa6599ab09bd2426162495',
@@ -55,8 +70,122 @@ const FIXTURE_TARGET_SUBACCOUNT =
55
70
  const FIXTURE_SECOND_TARGET_SUBACCOUNT =
56
71
  '0x2222222222222222222222222222222222222222222222222222222222222222' as const;
57
72
 
73
+ /**
74
+ * Feed vectors from nado-mobile-be at 77ccc52988cdc871ba30d6188fd21015b4b1d49f:
75
+ * mobile/tests/fixtures/feed-{publishing,comment,interaction}-signing.json.
76
+ * Pinned hashes are derived from the backend payload_msgpack_hex, not SDK serialization.
77
+ */
78
+ const FEED_SIGNING_FIXTURES = [
79
+ {
80
+ name: 'create_feed_post',
81
+ inner: {
82
+ type: 'create_feed_post',
83
+ client_request_id: '11111111-1111-4111-8111-111111111111',
84
+ position_id: '22222222-2222-4222-8222-222222222222',
85
+ action_id: '33333333-3333-4333-8333-333333333333',
86
+ text: '观点 @Bob',
87
+ mentions: [
88
+ {
89
+ subaccount:
90
+ '0x1111111111111111111111111111111111111111111111111111111111111111',
91
+ start: 3,
92
+ end: 7,
93
+ label: '@Bob',
94
+ },
95
+ ],
96
+ drawing_data_json: '{ "lines": [] }',
97
+ },
98
+ method: 'mobile:execute_create_feed_post',
99
+ },
100
+ {
101
+ name: 'create_feed_chart_drawing',
102
+ inner: {
103
+ type: 'create_feed_chart_drawing',
104
+ item_id: '11111111-1111-4111-8111-111111111111',
105
+ data_json: 'false',
106
+ },
107
+ method: 'mobile:execute_create_feed_chart_drawing',
108
+ },
109
+ {
110
+ name: 'update_feed_chart_drawing',
111
+ inner: {
112
+ type: 'update_feed_chart_drawing',
113
+ item_id: '11111111-1111-4111-8111-111111111111',
114
+ data_json: '0',
115
+ },
116
+ method: 'mobile:execute_update_feed_chart_drawing',
117
+ },
118
+ {
119
+ name: 'create_feed_comment',
120
+ inner: {
121
+ type: 'create_feed_comment',
122
+ client_request_id: '11111111-1111-4111-8111-111111111111',
123
+ root_item_id: '22222222-2222-4222-8222-222222222222',
124
+ text: 'comment',
125
+ mentions: [],
126
+ },
127
+ method: 'mobile:execute_create_feed_comment',
128
+ },
129
+ {
130
+ name: 'create_feed_comment_reply',
131
+ inner: {
132
+ type: 'create_feed_comment',
133
+ client_request_id: '11111111-1111-4111-8111-111111111111',
134
+ root_item_id: '22222222-2222-4222-8222-222222222222',
135
+ reply_to_item_id: '33333333-3333-4333-8333-333333333333',
136
+ text: '评论 @Alice',
137
+ mentions: [
138
+ {
139
+ subaccount:
140
+ '0x1111111111111111111111111111111111111111111111111111111111111111',
141
+ start: 3,
142
+ end: 9,
143
+ label: '@Alice',
144
+ },
145
+ ],
146
+ attached_position_id: '44444444-4444-4444-8444-444444444444',
147
+ },
148
+ method: 'mobile:execute_create_feed_comment',
149
+ },
150
+ {
151
+ name: 'set_feed_like_true',
152
+ inner: {
153
+ type: 'set_feed_like',
154
+ item_id: '11111111-1111-4111-8111-111111111111',
155
+ is_liked: true,
156
+ },
157
+ method: 'mobile:execute_set_feed_like',
158
+ },
159
+ {
160
+ name: 'set_feed_like_false',
161
+ inner: {
162
+ type: 'set_feed_like',
163
+ item_id: '11111111-1111-4111-8111-111111111111',
164
+ is_liked: false,
165
+ },
166
+ method: 'mobile:execute_set_feed_like',
167
+ },
168
+ ] satisfies {
169
+ name: keyof typeof PINNED_HASHES;
170
+ inner: MobileSignedInner;
171
+ method: string;
172
+ }[];
173
+
58
174
  describe('[mobile-client]: signing (offline)', () => {
59
175
  describe('pinned payload hashes', () => {
176
+ it.each(FEED_SIGNING_FIXTURES)(
177
+ '$name matches the backend signing vector',
178
+ ({ name, inner, method }) => {
179
+ const reordered = Object.fromEntries(
180
+ Object.entries(inner).reverse(),
181
+ ) as MobileSignedInner;
182
+ expect(getMobilePayloadHash(canonicalizeMobileInner(reordered))).toBe(
183
+ PINNED_HASHES[name],
184
+ );
185
+ expect(MOBILE_EIP712_METHOD_BY_TYPE[inner.type]).toBe(method);
186
+ },
187
+ );
188
+
60
189
  it('set_bio', () => {
61
190
  const inner: MobileSignedInner = {
62
191
  type: 'set_bio',
@@ -291,3 +420,221 @@ describe('[mobile-client]: signing (offline)', () => {
291
420
  expect(recoveredAddress.toLowerCase()).toBe(account.address.toLowerCase());
292
421
  });
293
422
  });
423
+
424
+ describe('[mobile-client]: social content signing', () => {
425
+ const requestId = 'AAAAAAAA-AAAA-4AAA-8AAA-AAAAAAAAAAAA';
426
+ const positionId = 'BBBBBBBB-BBBB-4BBB-8BBB-BBBBBBBBBBBB';
427
+ const itemId = 'CCCCCCCC-CCCC-4CCC-8CCC-CCCCCCCCCCCC';
428
+ const mention = {
429
+ label: '@Alice',
430
+ end: 8,
431
+ start: 2,
432
+ subaccount: FIXTURE_TARGET_SUBACCOUNT,
433
+ };
434
+ const text = '🚀 @Alice ';
435
+ const drawing = ' { "lines" : [], "enabled": false } ';
436
+
437
+ it('canonicalizes every post field and nested mention without changing content', () => {
438
+ const input: MobileSignedInner = {
439
+ drawing_data_json: drawing,
440
+ mentions: [mention],
441
+ text,
442
+ action_id: itemId,
443
+ position_id: positionId,
444
+ client_request_id: requestId,
445
+ type: 'create_feed_post',
446
+ };
447
+ const result = canonicalizeMobileInner(input);
448
+ expect(Object.keys(result)).toEqual([
449
+ 'type',
450
+ 'client_request_id',
451
+ 'position_id',
452
+ 'action_id',
453
+ 'text',
454
+ 'mentions',
455
+ 'drawing_data_json',
456
+ ]);
457
+ if (result.type !== 'create_feed_post') throw new Error('Expected post');
458
+ expect(Object.keys(result.mentions[0])).toEqual([
459
+ 'subaccount',
460
+ 'start',
461
+ 'end',
462
+ 'label',
463
+ ]);
464
+ expect(result.client_request_id).toBe(requestId);
465
+ expect(result.action_id).toBe(itemId);
466
+ expect(result.position_id).toBe(positionId);
467
+ expect(result.text).toBe(text);
468
+ expect(result.drawing_data_json).toBe(drawing);
469
+ expect(result.mentions[0]).toEqual({
470
+ subaccount: FIXTURE_TARGET_SUBACCOUNT,
471
+ start: 2,
472
+ end: 8,
473
+ label: '@Alice',
474
+ });
475
+ expect(input.position_id).toBe(positionId);
476
+ expect(Object.keys(mention)).toEqual([
477
+ 'label',
478
+ 'end',
479
+ 'start',
480
+ 'subaccount',
481
+ ]);
482
+ });
483
+
484
+ it('omits absent optional fields but retains empty text and mention arrays', () => {
485
+ const post = canonicalizeMobileInner({
486
+ type: 'create_feed_post',
487
+ client_request_id: requestId,
488
+ position_id: positionId,
489
+ action_id: undefined,
490
+ text: '',
491
+ mentions: [],
492
+ drawing_data_json: 'false',
493
+ });
494
+ expect(Object.keys(post)).toEqual([
495
+ 'type',
496
+ 'client_request_id',
497
+ 'position_id',
498
+ 'text',
499
+ 'mentions',
500
+ 'drawing_data_json',
501
+ ]);
502
+ const comment = canonicalizeMobileInner({
503
+ type: 'create_feed_comment',
504
+ client_request_id: requestId,
505
+ root_item_id: itemId,
506
+ reply_to_item_id: undefined,
507
+ text,
508
+ mentions: [],
509
+ attached_position_id: undefined,
510
+ });
511
+ expect(Object.keys(comment)).toEqual([
512
+ 'type',
513
+ 'client_request_id',
514
+ 'root_item_id',
515
+ 'text',
516
+ 'mentions',
517
+ ]);
518
+ expect(getMobilePayloadHash(comment)).not.toBe(
519
+ getMobilePayloadHash({
520
+ ...comment,
521
+ reply_to_item_id: null,
522
+ } as unknown as MobileSignedInner),
523
+ );
524
+ });
525
+
526
+ it('canonicalizes comment optionals in backend order and preserves whitespace', () => {
527
+ const comment = canonicalizeMobileInner({
528
+ attached_position_id: positionId,
529
+ mentions: [mention],
530
+ text,
531
+ reply_to_item_id: itemId,
532
+ root_item_id: requestId,
533
+ client_request_id: requestId,
534
+ type: 'create_feed_comment',
535
+ });
536
+ expect(Object.keys(comment)).toEqual([
537
+ 'type',
538
+ 'client_request_id',
539
+ 'root_item_id',
540
+ 'reply_to_item_id',
541
+ 'text',
542
+ 'mentions',
543
+ 'attached_position_id',
544
+ ]);
545
+ if (comment.type !== 'create_feed_comment')
546
+ throw new Error('Expected comment');
547
+ expect(comment.attached_position_id).toBe(positionId);
548
+ expect(comment.text).toBe(text);
549
+ expect(Object.keys(comment.mentions[0])).toEqual([
550
+ 'subaccount',
551
+ 'start',
552
+ 'end',
553
+ 'label',
554
+ ]);
555
+ });
556
+
557
+ it('does not reformat JSON before hashing or normalize falsy drawing values', () => {
558
+ const inner: MobileSignedInner = {
559
+ type: 'update_feed_chart_drawing',
560
+ item_id: itemId,
561
+ data_json: drawing,
562
+ };
563
+ const canonical = canonicalizeMobileInner(inner);
564
+ expect(Object.keys(canonical)).toEqual(['type', 'item_id', 'data_json']);
565
+ expect(getMobilePayloadHash(canonical)).not.toBe(
566
+ getMobilePayloadHash(
567
+ canonicalizeMobileInner({
568
+ ...inner,
569
+ data_json: JSON.stringify(JSON.parse(drawing)),
570
+ }),
571
+ ),
572
+ );
573
+ for (const dataJson of ['false', '0']) {
574
+ expect(
575
+ canonicalizeMobileInner({ ...inner, data_json: dataJson }),
576
+ ).toEqual({
577
+ type: inner.type,
578
+ item_id: itemId,
579
+ data_json: dataJson,
580
+ });
581
+ }
582
+ });
583
+
584
+ const payloads: MobileSignedInner[] = [
585
+ {
586
+ type: 'create_feed_post',
587
+ client_request_id: requestId,
588
+ position_id: positionId,
589
+ text,
590
+ mentions: [mention],
591
+ },
592
+ {
593
+ type: 'create_feed_comment',
594
+ client_request_id: requestId,
595
+ root_item_id: itemId,
596
+ text,
597
+ mentions: [],
598
+ },
599
+ { type: 'create_feed_chart_drawing', item_id: itemId, data_json: 'false' },
600
+ { type: 'update_feed_chart_drawing', item_id: itemId, data_json: '0' },
601
+ { type: 'set_feed_like', item_id: itemId, is_liked: false },
602
+ ];
603
+ it.each(payloads)(
604
+ 'recovers the signer for $type using its execute method domain',
605
+ async (inner) => {
606
+ const account = privateKeyToAccount(FIXED_PRIVATE_KEY);
607
+ const walletClient = createWalletClient({
608
+ account,
609
+ chain: ink,
610
+ transport: http(),
611
+ });
612
+ const nonce = 30_000_000_000_000n;
613
+ const signed = await buildSignedMobileRequest({
614
+ walletClient,
615
+ subaccountOwner: account.address,
616
+ subaccountName: 'default',
617
+ chainId: ink.id,
618
+ verifyingAddr: FIXED_VERIFYING_ADDR,
619
+ nonce,
620
+ inner,
621
+ });
622
+ const canonical = canonicalizeMobileInner(inner);
623
+ const recovered = await recoverTypedDataAddress({
624
+ domain: getNadoEIP712Domain(FIXED_VERIFYING_ADDR, ink.id),
625
+ types: getNadoEIP712Types('nado_authentication'),
626
+ primaryType: getNadoEIP712PrimaryType('nado_authentication'),
627
+ message: {
628
+ method: 'mobile:execute_' + inner.type,
629
+ sender: signed.sender,
630
+ payloadHash: getMobilePayloadHash(canonical),
631
+ nonce,
632
+ },
633
+ signature: signed.signature,
634
+ });
635
+ expect(recovered.toLowerCase()).toBe(account.address.toLowerCase());
636
+ expect(signed).toMatchObject(canonical);
637
+ expect(signed).not.toHaveProperty('payload');
638
+ },
639
+ );
640
+ });
@@ -4,6 +4,7 @@ import { MobileSignedRequestParams } from '../types/clientTypes';
4
4
  import {
5
5
  MobileNotificationPlatform,
6
6
  MobileServerAvatarSelection,
7
+ MobileSocialFeedMention,
7
8
  } from '../types/serverModelTypes';
8
9
 
9
10
  /**
@@ -21,6 +22,62 @@ import {
21
22
  * lowercases; unlike bytes32, string fields such as `display_name` are hashed as-is and keep their casing.
22
23
  */
23
24
  export type MobileSignedInner =
25
+ | {
26
+ /** Atomic post publication. */
27
+ type: 'create_feed_post';
28
+ /** Author-scoped idempotency UUID. */
29
+ client_request_id: string;
30
+ /** Owned position UUID. */
31
+ position_id: string;
32
+ /** Optional trusted action UUID, omitted rather than encoded as nil. */
33
+ action_id?: string;
34
+ /** Unmodified original text. */
35
+ text: string;
36
+ /** Ordered mentions into the original text. */
37
+ mentions: MobileSocialFeedMention[];
38
+ /** Original JSON text; omitted when absent. */
39
+ drawing_data_json?: string;
40
+ }
41
+ | {
42
+ /** Atomic comment publication. */
43
+ type: 'create_feed_comment';
44
+ /** Author-scoped idempotency UUID. */
45
+ client_request_id: string;
46
+ /** Root UUID. */
47
+ root_item_id: string;
48
+ /** Optional same-thread reply UUID. */
49
+ reply_to_item_id?: string;
50
+ /** Unmodified original text. */
51
+ text: string;
52
+ /** Ordered mentions. */
53
+ mentions: MobileSocialFeedMention[];
54
+ /** Optional owned position UUID. */
55
+ attached_position_id?: string;
56
+ }
57
+ | {
58
+ /** First attachment. */
59
+ type: 'create_feed_chart_drawing';
60
+ /** Parent UUID. */
61
+ item_id: string;
62
+ /** Original JSON text, not parsed or reformatted before hashing. */
63
+ data_json: string;
64
+ }
65
+ | {
66
+ /** Full replacement of an existing drawing. */
67
+ type: 'update_feed_chart_drawing';
68
+ /** Parent UUID. */
69
+ item_id: string;
70
+ /** Original JSON text. */
71
+ data_json: string;
72
+ }
73
+ | {
74
+ /** Idempotent relationship mutation. */
75
+ type: 'set_feed_like';
76
+ /** Item UUID. */
77
+ item_id: string;
78
+ /** Desired relationship state. */
79
+ is_liked: boolean;
80
+ }
24
81
  | { type: 'set_bio'; bio: string }
25
82
  | { type: 'create_avatar_upload'; content_length: number }
26
83
  | {
@@ -85,8 +142,4 @@ export type MobileSignedRequest<
85
142
  export interface BuildSignedMobileRequestParams extends MobileSignedRequestParams {
86
143
  walletClient: WalletClientWithAccount;
87
144
  inner: MobileSignedInner;
88
- /**
89
- * Overrides the generated nonce; mainly useful for tests that pin the signed payload.
90
- */
91
- nonce?: bigint;
92
145
  }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Error thrown when the mobile service returns a social feed item kind this SDK does not support.
3
+ */
4
+ export class MobileUnsupportedFeedItemError extends Error {
5
+ /** Unrecognized item kind returned by the service. */
6
+ readonly kind: string;
7
+
8
+ /**
9
+ * @param kind - The unrecognized service item kind.
10
+ */
11
+ constructor(kind: string) {
12
+ super(
13
+ `Unsupported mobile social feed item kind "${kind}". Update the SDK to support this item type.`,
14
+ );
15
+ this.name = 'MobileUnsupportedFeedItemError';
16
+ this.kind = kind;
17
+ }
18
+ }