@onekeyfe/hd-core 1.2.0-alpha.34 → 1.2.0-alpha.36

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 (91) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +74 -0
  2. package/__tests__/DeviceCommands.test.ts +84 -0
  3. package/__tests__/DeviceEventRegistration.test.ts +6 -0
  4. package/__tests__/device-settings.test.ts +3 -0
  5. package/__tests__/device-wallet-session-store.test.ts +80 -7
  6. package/__tests__/evmSignTransaction.test.ts +69 -0
  7. package/__tests__/get-device-state.test.ts +180 -40
  8. package/__tests__/open-wallet-session.test.ts +314 -55
  9. package/__tests__/protocol-v2-ui-lifecycle.test.ts +56 -0
  10. package/__tests__/protocol-v2.test.ts +768 -139
  11. package/dist/api/FirmwareUpdateV4.d.ts +1 -0
  12. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  13. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  14. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  15. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  16. package/dist/api/evm/EVMSignTypedData.d.ts.map +1 -1
  17. package/dist/api/evm/protocol.d.ts +3 -0
  18. package/dist/api/evm/protocol.d.ts.map +1 -0
  19. package/dist/api/protocol-v2/DeviceGetOnboardingStatus.d.ts +1 -1
  20. package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
  21. package/dist/core/deviceEventRegistration.d.ts +2 -0
  22. package/dist/core/deviceEventRegistration.d.ts.map +1 -1
  23. package/dist/core/index.d.ts.map +1 -1
  24. package/dist/device/Device.d.ts +25 -6
  25. package/dist/device/Device.d.ts.map +1 -1
  26. package/dist/device/DeviceCommands.d.ts.map +1 -1
  27. package/dist/device/DeviceWalletSessionStore.d.ts +0 -1
  28. package/dist/device/DeviceWalletSessionStore.d.ts.map +1 -1
  29. package/dist/events/device.d.ts +4 -0
  30. package/dist/events/device.d.ts.map +1 -1
  31. package/dist/events/ui-request.d.ts +27 -2
  32. package/dist/events/ui-request.d.ts.map +1 -1
  33. package/dist/index.d.ts +63 -12
  34. package/dist/index.js +1164 -904
  35. package/dist/protocols/protocol-v2/features.d.ts +17 -6
  36. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  37. package/dist/protocols/protocol-v2/index.d.ts +2 -2
  38. package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
  39. package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts +1 -0
  40. package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts.map +1 -1
  41. package/dist/protocols/protocol-v2/uiInteraction.d.ts +3 -3
  42. package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
  43. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -1
  44. package/dist/protocols/protocol-v2/walletSession.d.ts +5 -1
  45. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  46. package/dist/types/api/openWalletSession.d.ts +1 -1
  47. package/dist/types/api/openWalletSession.d.ts.map +1 -1
  48. package/dist/types/api/protocolV2.d.ts +2 -2
  49. package/dist/types/api/protocolV2.d.ts.map +1 -1
  50. package/dist/types/device.d.ts +2 -1
  51. package/dist/types/device.d.ts.map +1 -1
  52. package/dist/utils/deviceFeaturesUtils.d.ts +2 -0
  53. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  54. package/dist/utils/patch.d.ts +1 -1
  55. package/dist/utils/patch.d.ts.map +1 -1
  56. package/package.json +4 -4
  57. package/src/api/BaseMethod.ts +1 -1
  58. package/src/api/FirmwareUpdateV4.ts +39 -14
  59. package/src/api/GetPassphraseState.ts +17 -0
  60. package/src/api/OpenWalletSession.ts +31 -8
  61. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +24 -2
  62. package/src/api/device/DeviceUnlock.ts +1 -1
  63. package/src/api/evm/EVMGetAddress.ts +2 -2
  64. package/src/api/evm/EVMGetPublicKey.ts +2 -2
  65. package/src/api/evm/EVMSignMessage.ts +2 -2
  66. package/src/api/evm/EVMSignTransaction.ts +2 -2
  67. package/src/api/evm/EVMSignTypedData.ts +35 -45
  68. package/src/api/evm/EVMVerifyMessage.ts +2 -2
  69. package/src/api/evm/protocol.ts +6 -0
  70. package/src/api/protocol-v2/DeviceGetOnboardingStatus.ts +2 -2
  71. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +1 -1
  72. package/src/api/protocol-v2/ProtocolInfoRequest.ts +3 -1
  73. package/src/core/deviceEventRegistration.ts +4 -0
  74. package/src/core/index.ts +46 -4
  75. package/src/data/messages/messages-protocol-v2.json +408 -485
  76. package/src/device/Device.ts +266 -43
  77. package/src/device/DeviceCommands.ts +12 -2
  78. package/src/device/DeviceWalletSessionStore.ts +0 -14
  79. package/src/deviceProfile/buildDeviceFeatures.ts +1 -1
  80. package/src/events/device.ts +4 -0
  81. package/src/events/ui-request.ts +32 -2
  82. package/src/protocols/protocol-v2/features.ts +65 -16
  83. package/src/protocols/protocol-v2/index.ts +5 -0
  84. package/src/protocols/protocol-v2/settingsUnlockPolicy.ts +8 -3
  85. package/src/protocols/protocol-v2/uiInteraction.ts +31 -5
  86. package/src/protocols/protocol-v2/unlockRetry.ts +51 -18
  87. package/src/protocols/protocol-v2/walletSession.ts +172 -64
  88. package/src/types/api/openWalletSession.ts +1 -1
  89. package/src/types/api/protocolV2.ts +2 -2
  90. package/src/types/device.ts +2 -0
  91. package/src/utils/deviceFeaturesUtils.ts +4 -0
@@ -18,6 +18,79 @@ class TestAllNetworkMethod extends AllNetworkGetAddressBase {
18
18
  }
19
19
 
20
20
  describe('AllNetworkGetAddressBase tracing', () => {
21
+ test('resumes a Protocol V2 hidden wallet before running a nested chain method', async () => {
22
+ const calls: string[] = [];
23
+ const checkPassphraseStateSafety = jest.fn().mockImplementation(() => {
24
+ calls.push('resume-hidden-session');
25
+ return Promise.resolve(true);
26
+ });
27
+ const innerMethod = {
28
+ checkSafetyLevelOnTestNet: jest.fn().mockResolvedValue(false),
29
+ connectId: 'connect-id',
30
+ deviceId: 'device-id',
31
+ getVersionRange: jest.fn().mockReturnValue({}),
32
+ assertProtocolSupported: jest.fn(),
33
+ init: jest.fn(),
34
+ name: 'evmGetAddress',
35
+ responseID: 43,
36
+ run: jest.fn().mockImplementation(() => {
37
+ calls.push('run-chain-method');
38
+ return Promise.resolve([{ address: '0xhidden' }]);
39
+ }),
40
+ setDevice: jest.fn(),
41
+ strictCheckDeviceSupport: false,
42
+ };
43
+ (findMethod as jest.Mock).mockReturnValue(innerMethod);
44
+ const method = new TestAllNetworkMethod({
45
+ id: 1,
46
+ payload: {
47
+ method: 'allNetworkGetAddress',
48
+ connectId: 'connect-id',
49
+ deviceId: 'device-id',
50
+ passphraseState: 'hidden-state',
51
+ bundle: [],
52
+ },
53
+ });
54
+ method.device = {
55
+ checkPassphraseStateSafety,
56
+ commands: {
57
+ typedCall: jest.fn().mockResolvedValue({ message: { unlocked: true } }),
58
+ },
59
+ getCurrentFirmwareType: jest.fn(),
60
+ getProtocol: jest.fn().mockReturnValue('V2'),
61
+ getCurrentFirmwareVersionString: jest.fn().mockReturnValue('1.0.0'),
62
+ getCurrentMethodVersionRange: jest
63
+ .fn()
64
+ .mockImplementation((getRange: (type: string) => unknown) => getRange('pro2')),
65
+ instanceId: 'device-instance',
66
+ isProtocolV2: jest.fn().mockReturnValue(true),
67
+ isBootloader: jest.fn().mockReturnValue(false),
68
+ isRomloader: jest.fn().mockReturnValue(false),
69
+ off: jest.fn(),
70
+ on: jest.fn(),
71
+ state: { status: { unlocked: true } },
72
+ updateProtocolV2Status: jest.fn(),
73
+ } as any;
74
+
75
+ await method.callMethod(
76
+ 'evmGetAddress',
77
+ {
78
+ bundle: [
79
+ {
80
+ _originRequestParams: {
81
+ network: 'evm',
82
+ path: "m/44'/60'/0'/0/0",
83
+ },
84
+ },
85
+ ],
86
+ },
87
+ 0
88
+ );
89
+
90
+ expect(checkPassphraseStateSafety).toHaveBeenCalledWith('hidden-state', false, undefined);
91
+ expect(calls).toEqual(['resume-hidden-session', 'run-chain-method']);
92
+ });
93
+
21
94
  test('releases the nested request context when an unhandled error escapes', async () => {
22
95
  const deviceInstanceId = 'device-instance';
23
96
  const innerMethod = {
@@ -51,6 +124,7 @@ describe('AllNetworkGetAddressBase tracing', () => {
51
124
  getCurrentMethodVersionRange: jest
52
125
  .fn()
53
126
  .mockImplementation((getRange: (type: string) => unknown) => getRange('classic')),
127
+ isProtocolV2: jest.fn().mockReturnValue(false),
54
128
  off: jest.fn(),
55
129
  on: jest.fn(),
56
130
  } as any;
@@ -19,6 +19,90 @@ const createCommands = () => {
19
19
  };
20
20
 
21
21
  describe('DeviceCommands failure mapping', () => {
22
+ it('logs passphrase call request and response without exposing the passphrase', async () => {
23
+ const commands = createCommands();
24
+ const requestLog = getLogger(LoggerNames.DeviceCommands);
25
+ const responseLog = getLogger(LoggerNames.Core);
26
+ requestLog.messages.length = 0;
27
+ responseLog.messages.length = 0;
28
+ commands.mainId = 'main-id';
29
+ commands.transport = {
30
+ call: jest.fn().mockResolvedValue({
31
+ type: 'Success',
32
+ message: {
33
+ message: 'Passphrase accepted',
34
+ },
35
+ }),
36
+ } as any;
37
+
38
+ await expect(
39
+ commands.call('DeviceSessionAskPassphrase', {
40
+ passphrase: 'hidden-wallet-secret',
41
+ on_device: false,
42
+ })
43
+ ).resolves.toMatchObject({ type: 'Success' });
44
+
45
+ expect(requestLog.messages.at(-1)?.message).toEqual([
46
+ '[DeviceCommands] [call] Sending',
47
+ 'DeviceSessionAskPassphrase',
48
+ {
49
+ passphrase: '[REDACTED]',
50
+ on_device: false,
51
+ },
52
+ ]);
53
+ expect(responseLog.messages.at(-1)?.message).toEqual([
54
+ '[DeviceCommands] [call] Received',
55
+ 'Success',
56
+ {
57
+ message: 'Passphrase accepted',
58
+ },
59
+ ]);
60
+ expect(JSON.stringify([...requestLog.messages, ...responseLog.messages])).not.toContain(
61
+ 'hidden-wallet-secret'
62
+ );
63
+ });
64
+
65
+ it('logs DeviceStatus response fields without exposing the device ID', async () => {
66
+ const commands = createCommands();
67
+ const log = getLogger(LoggerNames.Core);
68
+ log.messages.length = 0;
69
+ commands.mainId = 'main-id';
70
+ commands.transport = {
71
+ call: jest.fn().mockResolvedValue({
72
+ type: 'DeviceStatus',
73
+ message: {
74
+ device_id: 'sensitive-device-id',
75
+ unlocked: true,
76
+ init_states: true,
77
+ backup_required: false,
78
+ passphrase_enabled: true,
79
+ attach_to_pin_enabled: false,
80
+ unlocked_by_attach_to_pin: false,
81
+ },
82
+ }),
83
+ } as any;
84
+
85
+ await expect(commands.call('DeviceStatusGet', {})).resolves.toMatchObject({
86
+ type: 'DeviceStatus',
87
+ });
88
+
89
+ const receivedLog = log.messages.at(-1)?.message;
90
+ expect(receivedLog).toEqual([
91
+ '[DeviceCommands] [call] Received',
92
+ 'DeviceStatus',
93
+ {
94
+ device_id: '[REDACTED]',
95
+ unlocked: true,
96
+ init_states: true,
97
+ backup_required: false,
98
+ passphrase_enabled: true,
99
+ attach_to_pin_enabled: false,
100
+ unlocked_by_attach_to_pin: false,
101
+ },
102
+ ]);
103
+ expect(JSON.stringify(receivedLog)).not.toContain('sensitive-device-id');
104
+ });
105
+
22
106
  it('does not log DeviceSessionGet response secrets', async () => {
23
107
  const commands = createCommands();
24
108
  const log = getLogger(LoggerNames.DeviceCommands);
@@ -3,6 +3,8 @@ import { registerHardwareUiEventListeners } from '../src/core/deviceEventRegistr
3
3
 
4
4
  const handlers = {
5
5
  pin: jest.fn(),
6
+ pinOnDevice: jest.fn(),
7
+ pinOnDeviceComplete: jest.fn(),
6
8
  button: jest.fn(),
7
9
  passphrase: jest.fn(),
8
10
  passphraseOnDevice: jest.fn(),
@@ -21,6 +23,8 @@ describe('hardware UI event registration', () => {
21
23
  expect(registerHardwareUiEventListeners(device as any, handlers)).toBe(true);
22
24
  expect(device.on.mock.calls.map(([type]) => type)).toEqual([
23
25
  DEVICE.PIN,
26
+ DEVICE.PIN_ON_DEVICE,
27
+ DEVICE.PIN_ON_DEVICE_COMPLETE,
24
28
  DEVICE.BUTTON,
25
29
  DEVICE.PASSPHRASE,
26
30
  DEVICE.PASSPHRASE_ON_DEVICE,
@@ -34,6 +38,8 @@ describe('hardware UI event registration', () => {
34
38
  expect(registerHardwareUiEventListeners(device as any, handlers)).toBe(true);
35
39
  expect(device.on.mock.calls.map(([type]) => type)).toEqual([
36
40
  DEVICE.PIN,
41
+ DEVICE.PIN_ON_DEVICE,
42
+ DEVICE.PIN_ON_DEVICE_COMPLETE,
37
43
  DEVICE.BUTTON,
38
44
  DEVICE.PASSPHRASE,
39
45
  DEVICE.PASSPHRASE_ON_DEVICE,
@@ -40,6 +40,9 @@ describe('DeviceSettings protocol routing', () => {
40
40
  ['language', { language: 'ja-JP' }],
41
41
  ['brightness', { brightness: 80 }],
42
42
  ['haptic feedback', { hapticFeedback: true }],
43
+ ['animation', { animationEnabled: true }],
44
+ ['tap to wake', { tapToWake: true }],
45
+ ['device name display', { deviceNameDisplayEnabled: true }],
43
46
  ])('does not unlock Protocol V2 before changing %s', (_name, settings) => {
44
47
  const method = new DeviceSettings({
45
48
  id: 1,
@@ -1,6 +1,7 @@
1
- import { HardwareErrorCode } from '@onekeyfe/hd-shared';
1
+ import { EDeviceType, HardwareErrorCode } from '@onekeyfe/hd-shared';
2
2
 
3
3
  import ClearSessionCache from '../src/api/ClearSessionCache';
4
+ import GetPassphraseState from '../src/api/GetPassphraseState';
4
5
  import DeviceWipe from '../src/api/device/DeviceWipe';
5
6
  import {
6
7
  DeviceWalletSessionStore,
@@ -35,15 +36,18 @@ describe('DeviceWalletSessionStore', () => {
35
36
  expect(store.get('device-2', 'hidden-a')).toBe('session-c');
36
37
  });
37
38
 
38
- test('evicts the oldest wallet session when a device exceeds the SE capacity of three', () => {
39
+ test('keeps SDK session handles without mirroring hardware eviction policy', () => {
39
40
  const store = new DeviceWalletSessionStore();
40
41
  store.setStandard('device-1', 'wallet-a', 'session-a');
41
42
  store.set('device-1', 'wallet-b', 'session-b');
42
43
  store.set('device-1', 'wallet-c', 'session-c');
43
44
  store.set('device-1', 'wallet-d', 'session-d');
44
45
 
45
- expect(store.get('device-1', 'wallet-a')).toBeUndefined();
46
- expect(store.getStandard('device-1')).toBeUndefined();
46
+ expect(store.get('device-1', 'wallet-a')).toBe('session-a');
47
+ expect(store.getStandard('device-1')).toEqual({
48
+ passphraseState: 'wallet-a',
49
+ sessionId: 'session-a',
50
+ });
47
51
  expect(store.get('device-1', 'wallet-b')).toBe('session-b');
48
52
  expect(store.get('device-1', 'wallet-c')).toBe('session-c');
49
53
  expect(store.get('device-1', 'wallet-d')).toBe('session-d');
@@ -94,7 +98,7 @@ describe('DeviceWalletSessionStore', () => {
94
98
  expect(store.getPending('stable-device-id')).toBe('pending-session');
95
99
  });
96
100
 
97
- test('keeps the three-session limit when descriptor sessions merge into a stable device id', () => {
101
+ test('keeps every known handle when descriptor sessions merge into a stable device id', () => {
98
102
  const store = new DeviceWalletSessionStore();
99
103
  store.setStandard('stable-device-id', 'wallet-a', 'session-a');
100
104
  store.set('stable-device-id', 'wallet-b', 'session-b');
@@ -106,8 +110,11 @@ describe('DeviceWalletSessionStore', () => {
106
110
  nextDeviceId: 'stable-device-id',
107
111
  });
108
112
 
109
- expect(store.get('stable-device-id', 'wallet-a')).toBeUndefined();
110
- expect(store.getStandard('stable-device-id')).toBeUndefined();
113
+ expect(store.get('stable-device-id', 'wallet-a')).toBe('session-a');
114
+ expect(store.getStandard('stable-device-id')).toEqual({
115
+ passphraseState: 'wallet-a',
116
+ sessionId: 'session-a',
117
+ });
111
118
  expect(store.get('stable-device-id', 'wallet-b')).toBe('session-b');
112
119
  expect(store.get('stable-device-id', 'wallet-c')).toBe('session-c');
113
120
  expect(store.get('stable-device-id', 'wallet-d')).toBe('session-d');
@@ -402,4 +409,70 @@ describe('Protocol V1 wallet identity initialization', () => {
402
409
  expect.any(Object)
403
410
  );
404
411
  });
412
+
413
+ test('selects the standard V1 wallet after a non-destructive live identity read', async () => {
414
+ const device = Device.fromDescriptor({ id: 'connect-a', path: 'connect-a' } as never);
415
+ device.features = {
416
+ protocol: 'V1',
417
+ deviceId: 'device-a',
418
+ unlocked: true,
419
+ passphraseProtection: false,
420
+ } as never;
421
+ const typedCall = jest
422
+ .fn()
423
+ .mockResolvedValueOnce({ type: 'Features', message: { device_id: 'device-a' } })
424
+ .mockResolvedValueOnce({ type: 'Features', message: { device_id: 'device-a' } });
425
+ device.commands = { typedCall } as never;
426
+ jest.spyOn(TransportManager, 'reconfigure').mockResolvedValue(undefined);
427
+
428
+ await device.initialize({ deviceId: 'device-a' });
429
+
430
+ expect(typedCall).toHaveBeenCalledTimes(2);
431
+ expect(typedCall).toHaveBeenNthCalledWith(1, 'GetFeatures', 'Features', {});
432
+ expect(typedCall).toHaveBeenNthCalledWith(
433
+ 2,
434
+ 'Initialize',
435
+ 'Features',
436
+ {
437
+ passphrase_state: undefined,
438
+ is_contains_attach: true,
439
+ },
440
+ expect.any(Object)
441
+ );
442
+ });
443
+ });
444
+
445
+ describe('Protocol V1 passphrase state classification', () => {
446
+ test('does not expose a standard-wallet address as passphrase state', async () => {
447
+ const typedCall = jest.fn().mockResolvedValue({
448
+ type: 'Address',
449
+ message: { address: 'standard-wallet-address' },
450
+ });
451
+ const method = new GetPassphraseState({
452
+ payload: {
453
+ method: 'getPassphraseState',
454
+ connectId: 'connect-a',
455
+ },
456
+ });
457
+ method.device = {
458
+ features: {},
459
+ commands: { typedCall },
460
+ isProtocolV2: () => false,
461
+ getCurrentDeviceType: () => EDeviceType.Classic,
462
+ getCurrentFirmwareVersionString: () => '1.0.0',
463
+ getCurrentPassphraseProtection: () => false,
464
+ getCurrentDeviceId: () => 'device-a',
465
+ getFeatures: jest.fn(),
466
+ getInternalState: () => undefined,
467
+ updateInternalState: jest.fn(),
468
+ } as never;
469
+
470
+ await expect(method.run()).resolves.toBeUndefined();
471
+ expect(typedCall).toHaveBeenCalledWith('GetAddress', 'Address', {
472
+ address_n: expect.any(Array),
473
+ coin_name: 'Testnet',
474
+ script_type: 'SPENDADDRESS',
475
+ show_display: false,
476
+ });
477
+ });
405
478
  });
@@ -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 = {