@onekeyfe/hd-core 1.2.0-alpha.33 → 1.2.0-alpha.35

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 (108) hide show
  1. package/__tests__/DeviceCommands.test.ts +84 -0
  2. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +31 -0
  3. package/__tests__/core-dispose.test.ts +3 -3
  4. package/__tests__/device-lifecycle-events.test.ts +52 -0
  5. package/__tests__/device-pool-state.test.ts +14 -2
  6. package/__tests__/device-settings.test.ts +25 -0
  7. package/__tests__/device-state-mapper.test.ts +13 -2
  8. package/__tests__/device-state-projector.test.ts +2 -2
  9. package/__tests__/device-utils.test.ts +1 -0
  10. package/__tests__/device-wallet-session-store.test.ts +137 -17
  11. package/__tests__/evmSignTransaction.test.ts +69 -0
  12. package/__tests__/get-device-state.test.ts +78 -30
  13. package/__tests__/kaspaSignTransaction.test.ts +9 -0
  14. package/__tests__/known-device-public-types.type-test.ts +3 -0
  15. package/__tests__/logBlockEvent.test.ts +89 -5
  16. package/__tests__/open-wallet-session.test.ts +499 -60
  17. package/__tests__/pro2Wallpaper.test.ts +1 -1
  18. package/__tests__/protocol-v2.test.ts +1180 -169
  19. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  20. package/dist/api/FirmwareUpdateV4.d.ts +2 -0
  21. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  22. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  23. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  24. package/dist/api/device/DeviceSettings.d.ts.map +1 -1
  25. package/dist/api/evm/EVMSignTypedData.d.ts.map +1 -1
  26. package/dist/api/evm/protocol.d.ts +3 -0
  27. package/dist/api/evm/protocol.d.ts.map +1 -0
  28. package/dist/api/kaspa/KaspaSignTransaction.d.ts.map +1 -1
  29. package/dist/api/protocol-v2/DeviceGetOnboardingStatus.d.ts +1 -1
  30. package/dist/core/index.d.ts.map +1 -1
  31. package/dist/device/Device.d.ts +10 -4
  32. package/dist/device/Device.d.ts.map +1 -1
  33. package/dist/device/DeviceCommands.d.ts.map +1 -1
  34. package/dist/device/DeviceConnector.d.ts +1 -1
  35. package/dist/device/DeviceConnector.d.ts.map +1 -1
  36. package/dist/device/DevicePool.d.ts +1 -0
  37. package/dist/device/DevicePool.d.ts.map +1 -1
  38. package/dist/device/DeviceStateMapper.d.ts.map +1 -1
  39. package/dist/device/DeviceStateProjector.d.ts.map +1 -1
  40. package/dist/device/DeviceWalletSessionStore.d.ts +7 -0
  41. package/dist/device/DeviceWalletSessionStore.d.ts.map +1 -1
  42. package/dist/deviceProfile/buildDeviceFeatures.d.ts.map +1 -1
  43. package/dist/deviceProfile/protocolV2DeviceIdentity.d.ts +7 -0
  44. package/dist/deviceProfile/protocolV2DeviceIdentity.d.ts.map +1 -0
  45. package/dist/events/logBlockEvent.d.ts +2 -0
  46. package/dist/events/logBlockEvent.d.ts.map +1 -1
  47. package/dist/index.d.ts +25 -11
  48. package/dist/index.js +1037 -838
  49. package/dist/protocols/protocol-v2/features.d.ts +17 -6
  50. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  51. package/dist/protocols/protocol-v2/index.d.ts +2 -2
  52. package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
  53. package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts +1 -0
  54. package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts.map +1 -1
  55. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -1
  56. package/dist/protocols/protocol-v2/walletSession.d.ts +1 -10
  57. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  58. package/dist/types/api/protocolV2.d.ts +2 -2
  59. package/dist/types/api/protocolV2.d.ts.map +1 -1
  60. package/dist/types/device.d.ts +6 -3
  61. package/dist/types/device.d.ts.map +1 -1
  62. package/dist/types/params.d.ts.map +1 -1
  63. package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
  64. package/dist/utils/deviceFeaturesUtils.d.ts +0 -5
  65. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  66. package/dist/utils/patch.d.ts +1 -1
  67. package/dist/utils/patch.d.ts.map +1 -1
  68. package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
  69. package/package.json +4 -4
  70. package/src/api/CheckAllFirmwareRelease.ts +11 -81
  71. package/src/api/FirmwareUpdateV4.ts +59 -25
  72. package/src/api/GetPassphraseState.ts +11 -2
  73. package/src/api/OpenWalletSession.ts +16 -8
  74. package/src/api/device/DeviceSettings.ts +8 -1
  75. package/src/api/evm/EVMGetAddress.ts +2 -2
  76. package/src/api/evm/EVMGetPublicKey.ts +2 -2
  77. package/src/api/evm/EVMSignMessage.ts +2 -2
  78. package/src/api/evm/EVMSignTransaction.ts +2 -2
  79. package/src/api/evm/EVMSignTypedData.ts +35 -45
  80. package/src/api/evm/EVMVerifyMessage.ts +2 -2
  81. package/src/api/evm/protocol.ts +6 -0
  82. package/src/api/kaspa/KaspaSignTransaction.ts +7 -0
  83. package/src/api/protocol-v2/DeviceGetOnboardingStatus.ts +2 -2
  84. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +1 -1
  85. package/src/core/index.ts +7 -5
  86. package/src/data/messages/messages-protocol-v2.json +393 -485
  87. package/src/device/Device.ts +98 -44
  88. package/src/device/DeviceCommands.ts +12 -2
  89. package/src/device/DeviceConnector.ts +10 -7
  90. package/src/device/DevicePool.ts +5 -3
  91. package/src/device/DeviceStateMapper.ts +5 -3
  92. package/src/device/DeviceStateProjector.ts +0 -7
  93. package/src/device/DeviceWalletSessionStore.ts +43 -4
  94. package/src/deviceProfile/buildDeviceFeatures.ts +11 -4
  95. package/src/deviceProfile/protocolV2DeviceIdentity.ts +32 -0
  96. package/src/events/logBlockEvent.ts +84 -1
  97. package/src/protocols/protocol-v2/deviceSession.ts +1 -1
  98. package/src/protocols/protocol-v2/features.ts +58 -16
  99. package/src/protocols/protocol-v2/index.ts +5 -0
  100. package/src/protocols/protocol-v2/settingsUnlockPolicy.ts +8 -3
  101. package/src/protocols/protocol-v2/unlockRetry.ts +18 -7
  102. package/src/protocols/protocol-v2/walletSession.ts +144 -62
  103. package/src/types/api/protocolV2.ts +2 -2
  104. package/src/types/device.ts +8 -2
  105. package/src/types/params.ts +2 -1
  106. package/src/utils/deviceFeaturesCompat.ts +8 -1
  107. package/src/utils/pro2Wallpaper.ts +3 -9
  108. package/tsconfig.type-tests.json +5 -1
@@ -1,4 +1,5 @@
1
1
  import EVMSignTransaction from '../src/api/evm/EVMSignTransaction';
2
+ import TransportManager from '../src/data-manager/TransportManager';
2
3
 
3
4
  import type { EVMTransactionEIP7702 } from '../src/types';
4
5
 
@@ -17,13 +18,17 @@ jest.mock('../src/device/Device', () => ({
17
18
  Device: jest.fn(),
18
19
  }));
19
20
 
21
+ const mockTransportManager = jest.mocked(TransportManager);
22
+
20
23
  describe('EVMSignTransaction EIP-7702', () => {
21
24
  let mockDevice: any;
22
25
  let mockTypedCall: jest.Mock;
23
26
 
24
27
  beforeEach(() => {
28
+ mockTransportManager.getProtocolV1MessageSchema.mockReturnValue('v1CurrentSchema');
25
29
  mockTypedCall = jest.fn();
26
30
  mockDevice = {
31
+ isProtocolV2: jest.fn(() => false),
27
32
  commands: {
28
33
  typedCall: {
29
34
  bind: jest.fn(() => mockTypedCall),
@@ -32,6 +37,70 @@ describe('EVMSignTransaction EIP-7702', () => {
32
37
  };
33
38
  });
34
39
 
40
+ describe('Protocol-specific schema routing', () => {
41
+ const transaction: EVMTransactionEIP7702 = {
42
+ to: '0x4Cd241E8d1510e30b2076397afc7508Ae59C66c9',
43
+ value: '0x0',
44
+ gasLimit: '0x5208',
45
+ nonce: '0x0',
46
+ chainId: 1,
47
+ maxFeePerGas: '0xbebc200',
48
+ maxPriorityFeePerGas: '0x9502f900',
49
+ authorizationList: [
50
+ {
51
+ chainId: 1,
52
+ address: '0x4Cd241E8d1510e30b2076397afc7508Ae59C66c9',
53
+ nonce: '0x1',
54
+ },
55
+ ],
56
+ };
57
+
58
+ const createTransactionMethod = () => {
59
+ const method = new EVMSignTransaction({
60
+ id: 1,
61
+ payload: {
62
+ method: 'evmSignTransaction',
63
+ path: "m/44'/60'/0'/0/0",
64
+ transaction,
65
+ },
66
+ });
67
+ method.device = mockDevice;
68
+ method.init();
69
+ return method;
70
+ };
71
+
72
+ it('keeps Protocol V2 on current OneKey messages after a legacy V1 device', async () => {
73
+ mockTransportManager.getProtocolV1MessageSchema.mockReturnValue('v1LegacySchema');
74
+ mockDevice.isProtocolV2.mockReturnValue(true);
75
+ mockTypedCall.mockResolvedValue({
76
+ message: {
77
+ signature_v: 27,
78
+ signature_r: '01',
79
+ signature_s: '02',
80
+ authorization_signatures: [],
81
+ },
82
+ });
83
+
84
+ await expect(createTransactionMethod().run()).resolves.toMatchObject({
85
+ v: '0x1b',
86
+ });
87
+ expect(mockTypedCall).toHaveBeenCalledWith(
88
+ 'EthereumSignTxEIP7702OneKey',
89
+ 'EthereumTxRequestOneKey',
90
+ expect.any(Object)
91
+ );
92
+ });
93
+
94
+ it('continues to use legacy messages for a Protocol V1 legacy device', async () => {
95
+ mockTransportManager.getProtocolV1MessageSchema.mockReturnValue('v1LegacySchema');
96
+
97
+ await expect(createTransactionMethod().run()).rejects.toThrow(
98
+ 'EIP7702 not supported by Trezor'
99
+ );
100
+ expect(mockTypedCall).not.toHaveBeenCalled();
101
+ });
102
+ });
103
+
35
104
  describe('EIP-7702 Transaction Detection', () => {
36
105
  it('should detect EIP-7702 transaction with authorization list', () => {
37
106
  const transaction: EVMTransactionEIP7702 = {
@@ -1,7 +1,9 @@
1
1
  import { HardwareErrorCode } from '@onekeyfe/hd-shared';
2
+ import { DeviceType } from '@onekeyfe/hd-transport';
2
3
 
3
4
  import { Device } from '../src/device/Device';
4
5
  import { DEVICE } from '../src/events';
6
+ import { PROTOCOL_V2_DEVICE_STATUS_GET_MESSAGE_TYPE } from '../src/protocols/protocol-v2';
5
7
 
6
8
  jest.mock('../src/data/config', () => ({
7
9
  getSDKVersion: jest.fn(() => '1.0.0'),
@@ -28,6 +30,18 @@ const createV1Device = (typedCall: jest.Mock) => {
28
30
  return device;
29
31
  };
30
32
 
33
+ const protocolV2ApplicationInfo = {
34
+ version: 1,
35
+ build_fingerprint: 'application__5.0.0__abcdef0__PROD__RELEASE',
36
+ supported_messages: [PROTOCOL_V2_DEVICE_STATUS_GET_MESSAGE_TYPE],
37
+ };
38
+
39
+ const getProtocolV2LoaderInfo = (mode: 'bootloader' | 'romloader') => ({
40
+ version: 1,
41
+ build_fingerprint: `${mode}__1.0.0__abcdef0__PROD__RELEASE`,
42
+ supported_messages: [],
43
+ });
44
+
31
45
  describe('getDeviceState', () => {
32
46
  test('does not expose the internal wallet session', async () => {
33
47
  const device = createV2Device(jest.fn());
@@ -39,45 +53,55 @@ describe('getDeviceState', () => {
39
53
  });
40
54
 
41
55
  test('hydrates Protocol V2 with separate DeviceInfoGet and DeviceStatusGet', async () => {
42
- const typedCall = jest.fn().mockImplementation((requestType: string) => ({
43
- message:
44
- requestType === 'DeviceInfoGet'
45
- ? {
46
- protocol_version: 2,
47
- hw: { serial_no: 'SERIAL-1' },
48
- fw: { application: { version: '5.0.0' } },
49
- }
50
- : { init_states: true, unlocked: true },
51
- }));
56
+ const typedCall = jest.fn().mockImplementation((requestType: string) => {
57
+ if (requestType === 'DeviceInfoGet') {
58
+ return {
59
+ message: {
60
+ protocol_version: 2,
61
+ hw: { serial_no: 'SERIAL-1' },
62
+ fw: { application: { version: '5.0.0' } },
63
+ },
64
+ };
65
+ }
66
+ if (requestType === 'ProtocolInfoRequest') {
67
+ return { message: protocolV2ApplicationInfo };
68
+ }
69
+ return { message: { init_states: true, unlocked: true } };
70
+ });
52
71
  const device = createV2Device(typedCall);
53
72
 
54
73
  const state = await device.getDeviceState();
55
74
 
56
75
  expect(state.identity.serialNo).toBe('SERIAL-1');
57
- expect(typedCall).toHaveBeenCalledTimes(2);
76
+ expect(typedCall).toHaveBeenCalledTimes(3);
58
77
  expect(typedCall.mock.calls[0][0]).toBe('DeviceInfoGet');
59
78
  expect(typedCall.mock.calls[0][2]?.targets?.status).not.toBe(true);
60
79
  expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
61
80
  });
62
81
 
63
82
  test('uses one full DeviceInfoGet plus DeviceStatusGet when firmware refresh initializes state', async () => {
64
- const typedCall = jest.fn().mockImplementation((requestType: string) => ({
65
- message:
66
- requestType === 'DeviceInfoGet'
67
- ? {
68
- protocol_version: 2,
69
- hw: { serial_no: 'SERIAL-1' },
70
- fw: { application: { version: '5.0.0', hash: 'HASH-1' } },
71
- }
72
- : { init_states: true, unlocked: true },
73
- }));
83
+ const typedCall = jest.fn().mockImplementation((requestType: string) => {
84
+ if (requestType === 'DeviceInfoGet') {
85
+ return {
86
+ message: {
87
+ protocol_version: 2,
88
+ hw: { serial_no: 'SERIAL-1' },
89
+ fw: { application: { version: '5.0.0', hash: 'HASH-1' } },
90
+ },
91
+ };
92
+ }
93
+ if (requestType === 'ProtocolInfoRequest') {
94
+ return { message: protocolV2ApplicationInfo };
95
+ }
96
+ return { message: { init_states: true, unlocked: true } };
97
+ });
74
98
  const device = createV2Device(typedCall);
75
99
 
76
100
  await device.getDeviceState({
77
101
  refreshSections: ['identity', 'versions', 'verification'],
78
102
  });
79
103
 
80
- expect(typedCall).toHaveBeenCalledTimes(2);
104
+ expect(typedCall).toHaveBeenCalledTimes(3);
81
105
  expect(typedCall).toHaveBeenNthCalledWith(
82
106
  1,
83
107
  'DeviceInfoGet',
@@ -115,13 +139,13 @@ describe('getDeviceState', () => {
115
139
  });
116
140
 
117
141
  test.each(['bootloader', 'romloader'] as const)(
118
- 'temporarily treats failed DeviceStatusGet with %s metadata as bootloader',
142
+ 'uses ProtocolInfo to preserve %s mode without DeviceStatusGet',
119
143
  async mode => {
120
144
  const typedCall = jest.fn().mockImplementation((requestType: string) => {
121
145
  if (requestType === 'DeviceInfoGet') {
122
146
  return {
123
147
  message: {
124
- hw: { serial_no: 'SERIAL-1' },
148
+ hw: { Device_type: DeviceType.PRO2, serial_no: 'SERIAL-1' },
125
149
  fw:
126
150
  mode === 'romloader'
127
151
  ? { romloader: { version: '1.0.0' } }
@@ -129,24 +153,38 @@ describe('getDeviceState', () => {
129
153
  },
130
154
  };
131
155
  }
132
- throw new Error('DeviceStatusGet unavailable');
156
+ if (requestType === 'ProtocolInfoRequest') {
157
+ return { message: getProtocolV2LoaderInfo(mode) };
158
+ }
159
+ throw new Error(`Unexpected request: ${requestType}`);
133
160
  });
134
161
  const device = createV2Device(typedCall);
135
162
  device.updateState({ protocol: 'V2', status: { mode } }, 'initialize');
136
163
 
137
164
  const state = await device.getDeviceState({ refreshSections: ['status'] });
138
165
 
139
- expect(state.status.mode).toBe('bootloader');
166
+ expect(state.status.mode).toBe(mode);
140
167
  expect(typedCall.mock.calls.map(call => call[0])).toEqual([
141
168
  'DeviceInfoGet',
142
- 'DeviceStatusGet',
169
+ 'ProtocolInfoRequest',
143
170
  ]);
144
171
  }
145
172
  );
146
173
 
147
174
  test('refreshes Protocol V2 status only when explicitly requested in normal mode', async () => {
148
- const typedCall = jest.fn().mockResolvedValue({
149
- message: { init_states: true, unlocked: true, device_id: 'device-1' },
175
+ const typedCall = jest.fn().mockImplementation((requestType: string) => {
176
+ if (requestType === 'DeviceInfoGet') {
177
+ return {
178
+ message: {
179
+ hw: { serial_no: 'SERIAL-1' },
180
+ fw: { application: { version: '5.0.0' } },
181
+ },
182
+ };
183
+ }
184
+ if (requestType === 'ProtocolInfoRequest') {
185
+ return { message: protocolV2ApplicationInfo };
186
+ }
187
+ return { message: { init_states: true, unlocked: true, device_id: 'device-1' } };
150
188
  });
151
189
  const device = createV2Device(typedCall);
152
190
  device.updateState({ protocol: 'V2', status: { mode: 'normal' } }, 'initialize');
@@ -203,6 +241,9 @@ describe('getDeviceState', () => {
203
241
  message: { init_states: true, unlocked: false, device_id: 'device-1' },
204
242
  };
205
243
  }
244
+ if (requestType === 'ProtocolInfoRequest') {
245
+ return { message: protocolV2ApplicationInfo };
246
+ }
206
247
  if (requestType === 'DeviceSettingsGet') {
207
248
  return { message: { label: 'Renamed Pro 2' } };
208
249
  }
@@ -215,6 +256,7 @@ describe('getDeviceState', () => {
215
256
 
216
257
  expect(typedCall.mock.calls.map(call => call[0])).toEqual([
217
258
  'DeviceInfoGet',
259
+ 'ProtocolInfoRequest',
218
260
  'DeviceStatusGet',
219
261
  'DeviceSettingsGet',
220
262
  ]);
@@ -237,6 +279,9 @@ describe('getDeviceState', () => {
237
279
  message: { init_states: true, unlocked: false, device_id: 'device-1' },
238
280
  };
239
281
  }
282
+ if (requestType === 'ProtocolInfoRequest') {
283
+ return { message: protocolV2ApplicationInfo };
284
+ }
240
285
  throw Object.assign(new Error('Device locked'), {
241
286
  errorCode: HardwareErrorCode.DeviceLocked,
242
287
  });
@@ -383,7 +428,10 @@ describe('getDeviceState', () => {
383
428
  },
384
429
  };
385
430
  }
386
- throw new Error('DeviceStatusGet unavailable in bootloader');
431
+ if (requestType === 'ProtocolInfoRequest') {
432
+ return { message: getProtocolV2LoaderInfo('bootloader') };
433
+ }
434
+ throw new Error(`Unexpected request: ${requestType}`);
387
435
  });
388
436
  const device = createV2Device(typedCall);
389
437
  device.updateState(
@@ -135,6 +135,15 @@ describe('KaspaSignTransaction capability flags', () => {
135
135
  createMethod({ inputs: [buildInput()], outputs: [{ satoshis: 1 }] }).init()
136
136
  ).toThrow('outputs require either address/addressN');
137
137
  });
138
+
139
+ it('rejects outputs containing both address and addressN', () => {
140
+ expect(() =>
141
+ createMethod({
142
+ inputs: [buildInput()],
143
+ outputs: [{ satoshis: 1, address: ADDRESS, addressN: CHANGE_PATH }],
144
+ }).init()
145
+ ).toThrow('outputs must not contain both address and addressN');
146
+ });
138
147
  });
139
148
 
140
149
  describe('KaspaSignTransaction protocol negotiation', () => {
@@ -0,0 +1,3 @@
1
+ import type { Features, KnownDevice } from '../src/types';
2
+
3
+ export const requireKnownDeviceFeatures = ({ features }: KnownDevice): Features => features;
@@ -1,4 +1,10 @@
1
- import { UI_RESPONSE, getLogBlockLabel } from '../src/events';
1
+ import {
2
+ UI_REQUEST,
3
+ UI_RESPONSE,
4
+ formatLogMethodLabel,
5
+ getLogBlockLabel,
6
+ getSafeLogPayload,
7
+ } from '../src/events';
2
8
 
3
9
  describe('getLogBlockLabel', () => {
4
10
  it('blocks evmSignTypedData params before logging large typed data', () => {
@@ -35,6 +41,21 @@ describe('getLogBlockLabel', () => {
35
41
  expect(getLogBlockLabel({ type: UI_RESPONSE.RECEIVE_PIN })).toBe(UI_RESPONSE.RECEIVE_PIN);
36
42
  });
37
43
 
44
+ it.each([UI_REQUEST.REQUEST_PASSPHRASE, UI_REQUEST.REQUEST_PASSPHRASE_ON_DEVICE])(
45
+ 'blocks wallet identifiers in %s event logging',
46
+ type => {
47
+ expect(
48
+ getLogBlockLabel({
49
+ type,
50
+ payload: {
51
+ passphraseState: 'wallet-identifier',
52
+ expectedPassphraseState: 'expected-wallet-identifier',
53
+ },
54
+ })
55
+ ).toBe(type);
56
+ }
57
+ );
58
+
38
59
  it('blocks openWalletSession wallet identifiers in direct and iframe call logging', () => {
39
60
  const payload = {
40
61
  method: 'openWalletSession',
@@ -53,16 +74,51 @@ describe('getLogBlockLabel', () => {
53
74
  ).toBe('openWalletSession');
54
75
  });
55
76
 
77
+ it('keeps openWalletSession metadata while redacting wallet session identifiers', () => {
78
+ const safeResponse = getSafeLogPayload(
79
+ {
80
+ success: true,
81
+ payload: {
82
+ protocol: 'V2',
83
+ walletType: 'hidden',
84
+ deviceId: 'device-id',
85
+ passphraseState: 'wallet-identifier',
86
+ sessionId: 'wallet-session-id',
87
+ resumed: false,
88
+ },
89
+ },
90
+ 'openWalletSession'
91
+ );
92
+
93
+ expect(safeResponse).toEqual({
94
+ method: 'openWalletSession',
95
+ success: true,
96
+ payload: {
97
+ protocol: 'V2',
98
+ walletType: 'hidden',
99
+ deviceId: 'device-id',
100
+ passphraseState: '[REDACTED]',
101
+ sessionId: '[REDACTED]',
102
+ resumed: false,
103
+ },
104
+ });
105
+ });
106
+
56
107
  it.each(['deviceUploadWallpaper', 'uploadPortfolio', 'fileWrite', 'fileRead'])(
57
- 'blocks binary payload logging for %s',
108
+ 'keeps metadata and replaces binary payloads with their size for %s',
58
109
  method => {
59
- expect(getLogBlockLabel({ method, data: new Uint8Array(1024) })).toBe(method);
60
- expect(getLogBlockLabel({ payload: { method, data: new Uint8Array(1024) } })).toBe(method);
110
+ const request = { method, path: 'resource.bin', data: new Uint8Array(1024) };
111
+ expect(getLogBlockLabel(request)).toBe(method);
112
+ expect(getSafeLogPayload(request, method)).toEqual({
113
+ method,
114
+ path: 'resource.bin',
115
+ data: '[BINARY:1024]',
116
+ });
61
117
  }
62
118
  );
63
119
 
64
120
  it.each(['evmSignMessage', 'btcSignMessage', 'evmSignTransaction'])(
65
- 'blocks request and response payload logging for every API method including %s',
121
+ 'blocks request and response payload logging for signing method %s',
66
122
  method => {
67
123
  expect(getLogBlockLabel({ method, message: 'sensitive signing payload' })).toBe(method);
68
124
  expect(
@@ -74,6 +130,34 @@ describe('getLogBlockLabel', () => {
74
130
  }
75
131
  );
76
132
 
133
+ it('keeps ordinary API requests and responses visible', () => {
134
+ const request = { method: 'getDeviceState', connectId: 'connect-id', scope: 'runtime' };
135
+ const response = { success: true, payload: { protocol: 'V2', initialized: true } };
136
+
137
+ expect(getLogBlockLabel(request)).toBeUndefined();
138
+ expect(getSafeLogPayload(request)).toEqual(request);
139
+ expect(getSafeLogPayload(response)).toEqual(response);
140
+ });
141
+
142
+ it('redacts sensitive keys even for ordinary API payloads', () => {
143
+ expect(
144
+ getSafeLogPayload({
145
+ method: 'ordinaryMethod',
146
+ payload: { session_id: 'session-secret', nested: { pin: '1234' } },
147
+ })
148
+ ).toEqual({
149
+ method: 'ordinaryMethod',
150
+ payload: { session_id: '[REDACTED]', nested: { pin: '[REDACTED]' } },
151
+ });
152
+ });
153
+
154
+ it('puts sensitive API method names in the log label', () => {
155
+ expect(formatLogMethodLabel('response:', 'openWalletSession')).toBe(
156
+ 'response: [openWalletSession]'
157
+ );
158
+ expect(formatLogMethodLabel('response:')).toBe('response:');
159
+ });
160
+
77
161
  it('does not classify events without an API method as method calls', () => {
78
162
  expect(getLogBlockLabel({ event: 'DEVICE_EVENT', type: 'device-connect' })).toBeUndefined();
79
163
  });