@nadohq/mobile-client 0.63.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
@@ -0,0 +1,742 @@
1
+ import { describe, expect, it } from '@jest/globals';
2
+ import {
3
+ mapMobileSocialFeedActionCandidate,
4
+ mapMobileSocialFeedCommentsPage,
5
+ mapMobileSocialFeedDrawingMutationResult,
6
+ mapMobileSocialFeedDrawingResult,
7
+ mapMobileSocialFeedItem,
8
+ mapMobileSocialFeedItemPatch,
9
+ mapMobileSocialFeedMentionToServer,
10
+ mapMobileSocialFeedPage,
11
+ mapMobileSocialFeedPositionAction,
12
+ mapMobileSocialFeedPositionChart,
13
+ mapMobileSocialFeedPositionHistory,
14
+ mapMobileSocialFeedPositionPatch,
15
+ mapMobileSocialFeedPresence,
16
+ mapMobileSocialFeedTradeCard,
17
+ } from './dataMappers';
18
+ import { MobileUnsupportedFeedItemError } from './types/MobileUnsupportedFeedItemError';
19
+ import {
20
+ MobileServerSocialFeedItem,
21
+ MobileServerSocialFeedPost,
22
+ MobileServerSocialFeedReply,
23
+ } from './types/serverModelTypes';
24
+ import { MobileServerSocialFeedPositionHistoryResponse } from './types/serverQueryTypes';
25
+
26
+ const post: MobileServerSocialFeedPost = {
27
+ id: '88888888-8888-4888-8888-888888888888',
28
+ kind: 'post',
29
+ has_analysis: true,
30
+ author: {
31
+ subaccount:
32
+ '0x1111111111111111111111111111111111111111000000000000000000000000',
33
+ username: 'alice',
34
+ display_name: 'Alice',
35
+ avatar_url: null,
36
+ is_following: false,
37
+ trader_badge: null,
38
+ trader_data_status: 'unavailable',
39
+ },
40
+ created_at_ms: 1791072000000,
41
+ display_at_ms: 1791072000000,
42
+ version: '1',
43
+ market: {
44
+ product_id: 2,
45
+ symbol: 'BTC-PERP',
46
+ logo_url: null,
47
+ category: 'crypto',
48
+ source_categories: ['crypto'],
49
+ },
50
+ capabilities: {
51
+ can_like: true,
52
+ can_comment: true,
53
+ can_record_view: true,
54
+ },
55
+ text: 'BTC 回踩后继续持有多仓。',
56
+ mentions: [],
57
+ position: {
58
+ position_id: '22222222-2222-4222-8222-222222222222',
59
+ product_id: 2,
60
+ market: {
61
+ product_id: 2,
62
+ symbol: 'BTC-PERP',
63
+ logo_url: null,
64
+ category: 'crypto',
65
+ source_categories: ['crypto'],
66
+ },
67
+ display_basis: 'current_position',
68
+ direction: 'long',
69
+ margin_mode: 'cross',
70
+ state: 'open',
71
+ entry_price_x18: '100000000000000000000000',
72
+ quantity_x18: '100000000000000000',
73
+ notional_x18: '10200000000000000000000',
74
+ leverage: {
75
+ value: null,
76
+ basis: 'not_applicable',
77
+ },
78
+ leverage_at_ms: null,
79
+ pnl: {
80
+ amount_x18: '200000000000000000000',
81
+ basis: 'unrealized_excl_fees_funding',
82
+ mode: 'live',
83
+ as_of_ms: 1791072060000,
84
+ quality: 'ready',
85
+ },
86
+ snapshot_at_ms: null,
87
+ state_as_of_ms: 1791072060000,
88
+ position_version: '3',
89
+ history_quality: 'complete',
90
+ },
91
+ interaction: {
92
+ like_count: 0,
93
+ comment_count: 0,
94
+ view_count: 0,
95
+ liked_by_me: false,
96
+ },
97
+ comment_preview: {
98
+ items: [],
99
+ total_count: 0,
100
+ next_cursor: null,
101
+ },
102
+ drawing: {
103
+ data: {
104
+ example: 'frontend-defined drawing document',
105
+ },
106
+ created_at_ms: 1791072000000,
107
+ updated_at_ms: 1791072000000,
108
+ },
109
+ };
110
+ const reply: MobileServerSocialFeedReply = {
111
+ id: '33333333-3333-4333-8333-333333333333',
112
+ kind: 'reply',
113
+ has_analysis: false,
114
+ author: {
115
+ subaccount:
116
+ '0x2222222222222222222222222222222222222222000000000000000000000000',
117
+ username: 'bob',
118
+ display_name: 'Bob',
119
+ avatar_url: null,
120
+ is_following: false,
121
+ trader_badge: null,
122
+ trader_data_status: 'unavailable',
123
+ },
124
+ created_at_ms: 1791072120000,
125
+ display_at_ms: 1791072120000,
126
+ version: '1',
127
+ market: {
128
+ product_id: 2,
129
+ symbol: 'BTC-PERP',
130
+ logo_url: null,
131
+ category: 'crypto',
132
+ source_categories: ['crypto'],
133
+ },
134
+ capabilities: {
135
+ can_like: false,
136
+ can_comment: true,
137
+ can_record_view: false,
138
+ },
139
+ text: '我会继续观察支撑位。',
140
+ mentions: [],
141
+ root_item_id: '11111111-1111-4111-8111-111111111111',
142
+ reply_to_author: null,
143
+ attached_position: null,
144
+ attachment_status: 'none',
145
+ original: {
146
+ id: '11111111-1111-4111-8111-111111111111',
147
+ author: {
148
+ subaccount:
149
+ '0x1111111111111111111111111111111111111111000000000000000000000000',
150
+ username: 'alice',
151
+ display_name: 'Alice',
152
+ avatar_url: null,
153
+ is_following: false,
154
+ trader_badge: null,
155
+ trader_data_status: 'unavailable',
156
+ },
157
+ text_excerpt: 'BTC 回踩后继续持有多仓。',
158
+ position: {
159
+ position_id: '22222222-2222-4222-8222-222222222222',
160
+ market: {
161
+ product_id: 2,
162
+ symbol: 'BTC-PERP',
163
+ logo_url: null,
164
+ category: 'crypto',
165
+ source_categories: ['crypto'],
166
+ },
167
+ direction: 'long',
168
+ state: 'open',
169
+ notional_x18: '10200000000000000000000',
170
+ },
171
+ has_analysis: false,
172
+ },
173
+ interaction: {
174
+ like_count: 0,
175
+ comment_count: 0,
176
+ view_count: 0,
177
+ liked_by_me: false,
178
+ },
179
+ drawing: null,
180
+ };
181
+ const history: MobileServerSocialFeedPositionHistoryResponse = {
182
+ position: {
183
+ position_id: '22222222-2222-4222-8222-222222222222',
184
+ product_id: 2,
185
+ market: {
186
+ product_id: 2,
187
+ symbol: 'BTC-PERP',
188
+ logo_url: null,
189
+ category: 'crypto',
190
+ source_categories: ['crypto'],
191
+ },
192
+ display_basis: 'current_position',
193
+ direction: 'long',
194
+ margin_mode: 'cross',
195
+ state: 'open',
196
+ entry_price_x18: '100000000000000000000000',
197
+ quantity_x18: '100000000000000000',
198
+ notional_x18: '10200000000000000000000',
199
+ leverage: {
200
+ value: null,
201
+ basis: 'not_applicable',
202
+ },
203
+ leverage_at_ms: null,
204
+ pnl: {
205
+ amount_x18: '200000000000000000000',
206
+ basis: 'unrealized_excl_fees_funding',
207
+ mode: 'live',
208
+ as_of_ms: 1791072060000,
209
+ quality: 'ready',
210
+ },
211
+ snapshot_at_ms: null,
212
+ state_as_of_ms: 1791072060000,
213
+ position_version: '3',
214
+ history_quality: 'complete',
215
+ estimated_liquidation_price_x18: null,
216
+ funding_pnl_x18: '0',
217
+ liquidation_price_quality: 'unavailable',
218
+ liquidation_price_as_of_ms: null,
219
+ funding_quality: 'ready',
220
+ funding_as_of_ms: 1791072060000,
221
+ },
222
+ summary: {
223
+ action_count: 1,
224
+ take_count: 1,
225
+ },
226
+ timeline: [
227
+ {
228
+ kind: 'action',
229
+ action: {
230
+ id: '44444444-4444-4444-8444-444444444444',
231
+ position_id: '22222222-2222-4222-8222-222222222222',
232
+ action: 'opened',
233
+ direction: 'long',
234
+ margin_mode: 'cross',
235
+ quantity_before_x18: '0',
236
+ quantity_after_x18: '100000000000000000',
237
+ executed_quantity_x18: '100000000000000000',
238
+ executed_notional_x18: '10000000000000000000000',
239
+ average_price_x18: '100000000000000000000000',
240
+ entry_price_before_x18: null,
241
+ realized_pnl_delta_x18: '0',
242
+ leverage: {
243
+ value: null,
244
+ basis: 'not_applicable',
245
+ },
246
+ leverage_at_ms: null,
247
+ first_event_at_ms: 1791072000000,
248
+ last_event_at_ms: 1791072000000,
249
+ version: '1',
250
+ liquidation_oracle_price_x18: null,
251
+ liquidation_notional_x18: null,
252
+ },
253
+ related_item_id: '11111111-1111-4111-8111-111111111111',
254
+ related_take: {
255
+ id: '11111111-1111-4111-8111-111111111111',
256
+ kind: 'trade_card',
257
+ has_analysis: false,
258
+ author: {
259
+ subaccount:
260
+ '0x1111111111111111111111111111111111111111000000000000000000000000',
261
+ username: 'alice',
262
+ display_name: 'Alice',
263
+ avatar_url: null,
264
+ is_following: false,
265
+ trader_badge: null,
266
+ trader_data_status: 'unavailable',
267
+ },
268
+ created_at_ms: 1791072000000,
269
+ display_at_ms: 1791072000000,
270
+ version: '1',
271
+ market: {
272
+ product_id: 2,
273
+ symbol: 'BTC-PERP',
274
+ logo_url: null,
275
+ category: 'crypto',
276
+ source_categories: ['crypto'],
277
+ },
278
+ capabilities: {
279
+ can_like: true,
280
+ can_comment: true,
281
+ can_record_view: true,
282
+ },
283
+ text: '持有这一仓位,关注后续价格变化。',
284
+ mentions: [],
285
+ position: {
286
+ position_id: '22222222-2222-4222-8222-222222222222',
287
+ product_id: 2,
288
+ market: {
289
+ product_id: 2,
290
+ symbol: 'BTC-PERP',
291
+ logo_url: null,
292
+ category: 'crypto',
293
+ source_categories: ['crypto'],
294
+ },
295
+ display_basis: 'current_position',
296
+ direction: 'long',
297
+ margin_mode: 'cross',
298
+ state: 'open',
299
+ entry_price_x18: '100000000000000000000000',
300
+ quantity_x18: '100000000000000000',
301
+ notional_x18: '10200000000000000000000',
302
+ leverage: {
303
+ value: null,
304
+ basis: 'not_applicable',
305
+ },
306
+ leverage_at_ms: null,
307
+ pnl: {
308
+ amount_x18: '200000000000000000000',
309
+ basis: 'unrealized_excl_fees_funding',
310
+ mode: 'live',
311
+ as_of_ms: 1791072060000,
312
+ quality: 'ready',
313
+ },
314
+ snapshot_at_ms: null,
315
+ state_as_of_ms: 1791072060000,
316
+ position_version: '3',
317
+ history_quality: 'complete',
318
+ },
319
+ interaction: {
320
+ like_count: 12,
321
+ comment_count: 0,
322
+ view_count: 80,
323
+ liked_by_me: false,
324
+ },
325
+ comment_preview: {
326
+ items: [],
327
+ total_count: 0,
328
+ next_cursor: null,
329
+ },
330
+ drawing: null,
331
+ action: 'opened',
332
+ },
333
+ },
334
+ ],
335
+ next_cursor: null,
336
+ history_quality: 'complete',
337
+ trading_context: {
338
+ product_id: 2,
339
+ suggested_direction: 'long',
340
+ viewer_position_id: '22222222-2222-4222-8222-222222222222',
341
+ can_reduce: true,
342
+ resolution: 'unique',
343
+ candidates: [
344
+ {
345
+ position_id: '22222222-2222-4222-8222-222222222222',
346
+ identity_subaccount:
347
+ '0x1111111111111111111111111111111111111111000000000000000000000000',
348
+ trading_subaccount:
349
+ '0x1111111111111111111111111111111111111111000000000000000000000000',
350
+ product_id: 2,
351
+ margin_mode: 'cross',
352
+ iso_subaccount_name: null,
353
+ direction: 'long',
354
+ state_as_of_ms: 1791072060000,
355
+ },
356
+ ],
357
+ },
358
+ read_session: {
359
+ session_id: '66666666-6666-4666-8666-666666666666',
360
+ mode: 'position',
361
+ expires_at_ms: 1791072900000,
362
+ update_cursor: null,
363
+ viewer_token: 'viewer_example_opaque',
364
+ view_tracking: {
365
+ min_visible_ratio: 0.5,
366
+ min_duration_ms: 1000,
367
+ },
368
+ },
369
+ };
370
+
371
+ describe('[mobile-client]: content mapping', () => {
372
+ it('rejects unsupported feed item kinds with the typed error', () => {
373
+ const server = {
374
+ kind: 'future_kind',
375
+ } as unknown as MobileServerSocialFeedItem;
376
+ expect(() => mapMobileSocialFeedItem(server)).toThrow(
377
+ MobileUnsupportedFeedItemError,
378
+ );
379
+ expect(() => mapMobileSocialFeedItem(server)).toThrow('future_kind');
380
+ });
381
+
382
+ it.each([
383
+ { label: 'timeline entry', entry: { kind: 'future_kind' } },
384
+ {
385
+ label: 'history take',
386
+ entry: { kind: 'take', item: { kind: 'future_kind' } },
387
+ },
388
+ ])(
389
+ 'rejects unsupported $label kinds before accessing their payload',
390
+ ({ entry }) => {
391
+ const server = {
392
+ ...history,
393
+ timeline: [entry],
394
+ } as unknown as MobileServerSocialFeedPositionHistoryResponse;
395
+ expect(() => mapMobileSocialFeedPositionHistory(server)).toThrow(
396
+ MobileUnsupportedFeedItemError,
397
+ );
398
+ expect(() => mapMobileSocialFeedPositionHistory(server)).toThrow(
399
+ 'future_kind',
400
+ );
401
+ },
402
+ );
403
+
404
+ it('maps shared post and trade card fields consistently, including history takes', () => {
405
+ const serverCard = {
406
+ ...post,
407
+ kind: 'trade_card',
408
+ action: 'opened',
409
+ } as const;
410
+ const mappedPost = mapMobileSocialFeedItem(post);
411
+ const mappedCard = mapMobileSocialFeedTradeCard(serverCard);
412
+ expect(mappedCard).toEqual({
413
+ ...mappedPost,
414
+ kind: 'trade_card',
415
+ action: 'opened',
416
+ });
417
+ const mappedHistory = mapMobileSocialFeedPositionHistory({
418
+ ...history,
419
+ timeline: [
420
+ { kind: 'take', item: post },
421
+ { kind: 'take', item: serverCard },
422
+ ],
423
+ });
424
+ expect(mappedHistory.timeline).toEqual([
425
+ { kind: 'take', item: mappedPost },
426
+ { kind: 'take', item: mappedCard },
427
+ ]);
428
+ });
429
+
430
+ it.each([
431
+ 'position_closed',
432
+ 'already_published',
433
+ 'unavailable',
434
+ null,
435
+ ] as const)('preserves action publication reason %s', (reason) => {
436
+ const candidate = mapMobileSocialFeedActionCandidate({
437
+ action_id: 'action',
438
+ position_id: post.position.position_id,
439
+ action: 'opened',
440
+ can_publish: reason === null,
441
+ unavailable_reason: reason,
442
+ });
443
+ expect(candidate.unavailableReason).toBe(reason);
444
+ expect(candidate.canPublish).toBe(reason === null);
445
+ });
446
+
447
+ it('maps all supported item kinds without dropping items or page metadata', () => {
448
+ const items: MobileServerSocialFeedItem[] = [
449
+ {
450
+ ...post,
451
+ id: '11111111-1111-4111-8111-111111111111',
452
+ kind: 'trade_card',
453
+ action: 'opened',
454
+ },
455
+ {
456
+ ...post,
457
+ id: '44444444-4444-4444-8444-444444444444',
458
+ kind: 'trade_row',
459
+ action: 'increased',
460
+ position_id: post.position.position_id,
461
+ direction: 'long',
462
+ margin_mode: 'cross',
463
+ leverage: { value: null, basis: 'not_applicable' },
464
+ leverage_at_ms: null,
465
+ trade_count: 2,
466
+ quantity_x18: '100000000000000000',
467
+ notional_x18: '10200000000000000000000',
468
+ average_price_x18: '102000000000000000000000',
469
+ },
470
+ post,
471
+ reply,
472
+ ];
473
+ const page = mapMobileSocialFeedPage({
474
+ items,
475
+ read_session: { ...history.read_session, mode: 'feed' },
476
+ next_cursor: 'NextCursor',
477
+ watermark: 'Watermark',
478
+ });
479
+
480
+ expect(page.items.map(({ id, kind }) => ({ id, kind }))).toEqual(
481
+ items.map(({ id, kind }) => ({ id, kind })),
482
+ );
483
+ expect(page.items[0]).toMatchObject({
484
+ kind: 'trade_card',
485
+ action: 'opened',
486
+ });
487
+ expect(page.items[1]).toMatchObject({ kind: 'trade_row', tradeCount: 2 });
488
+ expect(page.items[2]).toMatchObject({ kind: 'post', text: post.text });
489
+ expect(page.items[3]).toMatchObject({
490
+ kind: 'reply',
491
+ rootItemId: reply.root_item_id,
492
+ });
493
+ expect(page.nextCursor).toBe('NextCursor');
494
+ expect(page.watermark).toBe('Watermark');
495
+ expect(page.readSession).toMatchObject({
496
+ mode: 'feed',
497
+ sessionId: history.read_session.session_id,
498
+ viewerToken: history.read_session.viewer_token,
499
+ });
500
+ });
501
+
502
+ it('maps posts, drawings, reply previews, and nullable badges from the documented response', () => {
503
+ const mapped = mapMobileSocialFeedItem({
504
+ ...post,
505
+ comment_preview: { items: [reply], total_count: 1, next_cursor: null },
506
+ });
507
+ expect(mapped.kind).toBe('post');
508
+ if (mapped.kind !== 'post') throw new Error('Expected post');
509
+ expect(mapped.text).toBe(post.text);
510
+ expect(mapped.drawing?.data).toEqual(post.drawing?.data);
511
+ expect(mapped.author.traderBadge).toBeNull();
512
+ expect(mapped.position.quantity.toFixed()).toBe(post.position.quantity_x18);
513
+ expect(mapped.position.notional.toFixed()).toBe(post.position.notional_x18);
514
+ expect(mapped.commentPreview.items[0].kind).toBe('reply');
515
+ expect(mapped.commentPreview.items[0].rootItemId).toBe(reply.root_item_id);
516
+ expect(
517
+ mapped.commentPreview.items[0].original.author.traderBadge,
518
+ ).toBeNull();
519
+ expect(mapped.commentPreview.items[0].attachedPosition).toBeNull();
520
+ expect(mapped.commentPreview.items[0].attachmentStatus).toBe('none');
521
+ expect(mapped.commentPreview.items[0]).not.toHaveProperty('replyToItemId');
522
+ });
523
+
524
+ it.each([false, 0, { lines: [] }])(
525
+ 'preserves valid drawing data %p rather than treating it as missing',
526
+ (data) => {
527
+ const result = mapMobileSocialFeedDrawingResult({
528
+ item_id: post.id,
529
+ item_version: '9',
530
+ drawing: { data, created_at_ms: 10, updated_at_ms: 20 },
531
+ });
532
+ expect(result.drawing).toEqual({
533
+ data,
534
+ createdAtMs: 10,
535
+ updatedAtMs: 20,
536
+ });
537
+ },
538
+ );
539
+
540
+ it.each([false, 0, null, { lines: [] }])(
541
+ 'maps saved drawing mutations with non-null drawings and data %p',
542
+ (data) => {
543
+ const result = mapMobileSocialFeedDrawingMutationResult({
544
+ item_id: post.id,
545
+ item_version: '9',
546
+ drawing: { data, created_at_ms: 10, updated_at_ms: 20 },
547
+ });
548
+ expect(result.itemId).toBe(post.id);
549
+ expect(result.itemVersion).toBe('9');
550
+ expect(result.drawing.data).toEqual(data);
551
+ expect(result.drawing).toEqual({
552
+ data,
553
+ createdAtMs: 10,
554
+ updatedAtMs: 20,
555
+ });
556
+ },
557
+ );
558
+
559
+ it('distinguishes missing drawings and explicit attachment clearing from omitted patches', () => {
560
+ expect(
561
+ mapMobileSocialFeedDrawingResult({
562
+ item_id: post.id,
563
+ item_version: '1',
564
+ drawing: null,
565
+ }).drawing,
566
+ ).toBeNull();
567
+ const patch = {
568
+ item_id: reply.id,
569
+ version: '2',
570
+ author: reply.author,
571
+ display_at_ms: 22,
572
+ };
573
+ expect(mapMobileSocialFeedItemPatch(patch)).not.toHaveProperty(
574
+ 'attachedPosition',
575
+ );
576
+ expect(
577
+ mapMobileSocialFeedItemPatch({
578
+ ...patch,
579
+ attached_position: null,
580
+ attachment_status: 'unavailable',
581
+ }),
582
+ ).toMatchObject({
583
+ attachedPosition: null,
584
+ attachmentStatus: 'unavailable',
585
+ });
586
+ const attached = mapMobileSocialFeedItemPatch({
587
+ ...patch,
588
+ attached_position: post.position,
589
+ attachment_status: 'visible',
590
+ });
591
+ expect(attached.attachedPosition?.positionId).toBe(
592
+ post.position.position_id,
593
+ );
594
+ expect(attached.attachedPosition?.quantity.toFixed()).toBe(
595
+ post.position.quantity_x18,
596
+ );
597
+ });
598
+
599
+ it('maps a position session, related takes, history metrics, and trading targets', () => {
600
+ const mapped = mapMobileSocialFeedPositionHistory(history);
601
+ expect(mapped.readSession.mode).toBe('position');
602
+ expect(mapped.readSession.viewerToken).toBe(
603
+ history.read_session.viewer_token,
604
+ );
605
+ expect(mapped.summary).toEqual({
606
+ actionCount: history.summary.action_count,
607
+ takeCount: history.summary.take_count,
608
+ });
609
+ expect(mapped.tradingContext.viewerPositionId).toBe(
610
+ history.trading_context.viewer_position_id,
611
+ );
612
+ expect(mapped.timeline[0].kind).toBe('action');
613
+ expect(mapped.position.fundingPnl?.toFixed() ?? null).toBe(
614
+ history.position.funding_pnl_x18,
615
+ );
616
+ const patch = mapMobileSocialFeedPositionPatch({
617
+ ...post.position,
618
+ current_position: {
619
+ estimated_liquidation_price_x18: '2500123456789012345678',
620
+ liquidation_price_quality: 'ready',
621
+ liquidation_price_as_of_ms: 42,
622
+ funding_pnl_x18: '-9007199254740993000001',
623
+ funding_quality: 'ready',
624
+ funding_as_of_ms: 42,
625
+ },
626
+ });
627
+ expect(patch.currentPosition?.estimatedLiquidationPrice?.toFixed()).toBe(
628
+ '2500.123456789012345678',
629
+ );
630
+ expect(patch.currentPosition?.fundingPnl?.toFixed()).toBe(
631
+ '-9007199254740993000001',
632
+ );
633
+ expect(mapMobileSocialFeedPositionPatch(post.position)).not.toHaveProperty(
634
+ 'currentPosition',
635
+ );
636
+ });
637
+
638
+ it('keeps execution, Oracle, and realized PnL independently nullable and precise', () => {
639
+ const entry = history.timeline[0];
640
+ if (entry.kind !== 'action') throw new Error('Expected history action');
641
+ const mapped = mapMobileSocialFeedPositionAction({
642
+ ...entry.action,
643
+ action: 'liquidated',
644
+ executed_quantity_x18: '1234567890123456789012345',
645
+ executed_notional_x18: null,
646
+ average_price_x18: '2500123456789012345678',
647
+ entry_price_before_x18: null,
648
+ liquidation_oracle_price_x18: null,
649
+ liquidation_notional_x18: null,
650
+ realized_pnl_delta_x18: '-9007199254740993000001',
651
+ });
652
+ expect(mapped.executedQuantity.toFixed()).toBe('1234567890123456789012345');
653
+ expect(mapped.executedNotional).toBeNull();
654
+ expect(mapped.averagePrice?.toFixed()).toBe('2500.123456789012345678');
655
+ expect(mapped.entryPriceBefore).toBeNull();
656
+ expect(mapped.liquidationOraclePrice).toBeNull();
657
+ expect(mapped.liquidationNotional).toBeNull();
658
+ expect(mapped.realizedPnlDelta?.toFixed()).toBe('-9007199254740993000001');
659
+ });
660
+
661
+ it('converts candle prices but retains volume in x18 and markers with unknown prices', () => {
662
+ const mapped = mapMobileSocialFeedPositionChart({
663
+ candles: [
664
+ {
665
+ timestamp_ms: 42,
666
+ open_x18: '1000000000000000000',
667
+ high_x18: '2000000000000000000',
668
+ low_x18: '500000000000000000',
669
+ close_x18: '1500000000000000000',
670
+ volume_x18: '1234567890123456789012345',
671
+ },
672
+ ],
673
+ action_markers: [
674
+ {
675
+ action_id: post.id,
676
+ timestamp_ms: 42,
677
+ price_x18: null,
678
+ action: 'liquidated',
679
+ direction: 'long',
680
+ price_basis: 'liquidation_oracle',
681
+ },
682
+ ],
683
+ estimated_liquidation_line: null,
684
+ next_before_ms: null,
685
+ as_of_ms: 42,
686
+ data_quality: 'partial',
687
+ });
688
+ expect(mapped.candles[0].open.toFixed()).toBe('1');
689
+ expect(mapped.candles[0].high.toFixed()).toBe('2');
690
+ expect(mapped.candles[0].low.toFixed()).toBe('0.5');
691
+ expect(mapped.candles[0].close.toFixed()).toBe('1.5');
692
+ expect(mapped.candles[0].volume.toFixed()).toBe(
693
+ '1234567890123456789012345',
694
+ );
695
+ expect(mapped.actionMarkers[0].price).toBeNull();
696
+ expect(mapped.actionMarkers[0].priceBasis).toBe('liquidation_oracle');
697
+ expect(mapped.estimatedLiquidationLine).toBeNull();
698
+ });
699
+
700
+ it('maps comment cursors and anchor without inventing an observation timestamp', () => {
701
+ const result = mapMobileSocialFeedCommentsPage({
702
+ comments: [reply],
703
+ next_cursor: 'NextOpaque',
704
+ previous_cursor: 'PreviousOpaque',
705
+ total_count: 8,
706
+ anchor_reply_id: reply.id,
707
+ read_session: history.read_session,
708
+ });
709
+ expect(result.nextCursor).toBe('NextOpaque');
710
+ expect(result.previousCursor).toBe('PreviousOpaque');
711
+ expect(result.anchorReplyId).toBe(reply.id);
712
+ expect(result).not.toHaveProperty('asOfMs');
713
+ });
714
+
715
+ it('maps presence lease metadata without assuming an as_of_ms field', () => {
716
+ const result = mapMobileSocialFeedPresence({
717
+ watcher_count: 1,
718
+ watcher_preview: [reply.author],
719
+ heartbeat_interval_seconds: 15,
720
+ expires_in_seconds: 45,
721
+ });
722
+ expect(result.watcherCount).toBe(1);
723
+ expect(result.watcherPreview[0].traderBadge).toBeNull();
724
+ expect(result.heartbeatIntervalSeconds).toBe(15);
725
+ expect(result.expiresInSeconds).toBe(45);
726
+ expect(result).not.toHaveProperty('asOfMs');
727
+ });
728
+
729
+ it('keeps mention labels and code-point offsets intact', () => {
730
+ const result = mapMobileSocialFeedMentionToServer({
731
+ subaccount: {
732
+ subaccountOwner: '0x1111111111111111111111111111111111111111',
733
+ subaccountName: 'default',
734
+ },
735
+ start: 2,
736
+ end: 8,
737
+ label: '@Alice',
738
+ });
739
+ expect(result).toMatchObject({ start: 2, end: 8, label: '@Alice' });
740
+ expect(result.subaccount).toMatch(/^0x[0-9a-f]{64}$/);
741
+ });
742
+ });